在现代软件开发中,代码的追踪和监控是至关重要的。它不仅可以帮助理解代码的执行流程,还可以在出现问题时快速定位问题所在。OpenTelemetry是一个强大的追踪工具,它允许定义代码中的特定部分,称为‘Spans’,并在运行时跟踪它们的执行和依赖关系。然而,为了使这些信息可用,需要在代码中添加大量的追踪代码,这可能会变得相当繁琐。
Python的装饰器为提供了一种优雅的方式来简化这个过程。通过装饰器,可以自动地为函数添加追踪逻辑,而不需要在每个类、模块或函数中手动添加追踪代码。
使用追踪装饰器的好处是显而易见的。首先,它减少了重复代码。其次,它允许通过约定来命名Span,并且可以轻松地改变这个约定。最后,它使得整个类可以自动地被追踪,而不需要为每个函数单独添加装饰器。
要实现一个基本的追踪装饰器,首先需要创建一个装饰器函数,它可以自动地为函数添加追踪逻辑。这个装饰器函数会返回一个包装函数,这个包装函数会被解释器用作函数装饰器。
def instrument(_func=None, *, span_name: str = "", record_exception: bool = True, attributes: Dict[str, str] = None, existing_tracer: Tracer = None):
def span_decorator(func):
tracer = existing_tracer or trace.get_tracer(func.__module__)
def _set_attributes(span, attributes_dict):
if attributes_dict:
for att in attributes_dict:
span.set_attribute(att, attributes_dict[att])
@wraps(func)
def wrap_with_span(*args, **kwargs):
name = span_name or TracingDecoratorOptions.naming_scheme(func)
with tracer.start_as_current_span(name, record_exception=record_exception) as span:
_set_attributes(span, attributes)
return func(*args, **kwargs)
return wrap_with_span
if _func is None:
return span_decorator
else:
return span_decorator(_func)
在上面的代码中,首先检查是否已经为函数添加了追踪装饰器。如果是的话,就不再添加装饰器。否则,为函数添加装饰器,并返回函数本身。
为了确保装饰器工作正常,需要添加一些测试代码。
@classmethod
def setup_class(cls):
resource = Resource.create(attributes={SERVICE_NAME: "test"})
provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)
@instrument
def test_decorated_function_gets_instrumented_automatically_with_span():
assert trace.get_current_span().is_recording() is True
在上面的测试代码中,首先设置OTEL,使得追踪操作会生效。然后,验证在应用了新装饰器的测试方法中,是否有一个活动的追踪Span。
如果为每个函数单独添加装饰器,可能会变得有点繁琐和重复。为了解决这个问题,可以修改装饰器,使其遍历类中的每个函数,并为它们添加装饰器,忽略私有函数。
def instrument(_func_or_class=None, *, span_name: str = "", record_exception: bool = True, attributes: Dict[str, str] = None, existing_tracer: Tracer = None, ignore=False):
def decorate_class(cls):
for name, method in inspect.getmembers(cls, inspect.isfunction):
if not name.startswith('_'):
setattr(cls, name, instrument(record_exception=record_exception, attributes=attributes, existing_tracer=existing_tracer)(method))
return cls
if inspect.isclass(_func_or_class):
return decorate_class(_func_or_class)
def span_decorator(func_or_class):
if inspect.isclass(func_or_class):
return decorate_class(func_or_class)
...
if _func_or_class is None:
return span_decorator
else:
return span_decorator(_func_or_class)