跳转到主要内容

OpenTracing 工具库

项目描述

早期阶段 WIP + 试验性

Build status Code coverage Latest PyPI version License OpenTracing enabled

为您的 Python 项目添加 OpenTracing 支持的便捷工具。

特性

opentracing-utils 应提供并旨在以下

  • 无外部依赖,仅依赖 opentracing-python

  • 无线程局部变量。要么使用 tracer scoper 管理器,显式传递 spans 或回退到调用栈帧检查!

  • 上下文无关,因此没有外部 上下文实现 依赖(无 Tornado、Flask、Django 等 ...)。

  • 支持 OpenTracing 2.0 API,带有 scope_manager(opentracing-utils>0.21.0)。

  • 尽量简洁 - 只需添加 @trace 装饰器。

  • 在需要时可以更详细,但无复杂性——只需接受 **kwargs 并通过 @trace(pass_span=True) 将 span 传递给您的跟踪函数。

  • 支持 asyncio/async-await 协程。(停止支持 py2.7)

  • 支持 gevent

  • 能够将 OpenTracing 支持添加到外部库/框架/客户端。

    • Django(通过 OpenTracingHttpMiddleware

    • Flask(通过 trace_flask()

    • Requests(通过 trace_requests()

    • SQLAlchemy(通过 trace_sqlalchemy()

安装

使用 pip(尚未发布到 PyPi)

pip install -U opentracing-utils

或通过克隆仓库

python setup.py install

用法

init_opentracing_tracer

在 OpenTracing 仪表化中,第一步是初始化一个跟踪器。每个供应商都定义了跟踪器初始化的方式。目前支持以下跟踪器

BasicTracer

这是一个基本的空操作跟踪器。它可以与一个记录器(例如 Memory Recorder)一起初始化,这在调试和探索 OpenTracing 概念时可能很有用。

import opentracing
from opentracing_utils import OPENTRACING_BASIC, init_opentracing_tracer

# Initialize upon application start
init_opentracing_tracer(OPENTRACING_BASIC)

# It is possible to pass custom recorder
# init_opentracing_tracer(OPENTRACING_BASIC, recorder=custom_recorder)

# Now use the opentracing.tracer
root_span = opentracing.tracer.start_span(operation_name='root_span')

Instana

配置变量

以下配置变量可以在初始化时作为环境变量使用

OPENTRACING_INSTANA_SERVICE

服务名称。

import opentracing
from opentracing_utils import OPENTRACING_INSTANA, init_opentracing_tracer

# Initialize upon application start
init_opentracing_tracer(OPENTRACING_INSTANA)

# It is possible to pass args
# init_opentracing_tracer(OPENTRACING_INSTANA, service='python-server')

# Now use the opentracing.tracer
root_span = opentracing.tracer.start_span(operation_name='root_span')
依赖项

instana 添加到您项目的 dependencies.txt 文件中。

Jaeger

配置变量

以下配置变量可以在初始化时作为环境变量使用

OPENTRACING_JAEGER_SERVICE_NAME

服务名称。

import opentracing
from opentracing_utils import OPENTRACING_JAEGER, init_opentracing_tracer

# Initialize upon application start
init_opentracing_tracer(OPENTRACING_JAEGER)

# It is possible to pass args
# init_opentracing_tracer(OPENTRACING_JAEGER, service_name='python-server', config=custom_config_with_sampling)

# Now use the opentracing.tracer
root_span = opentracing.tracer.start_span(operation_name='root_span')
依赖项

jaeger_client 添加到您项目的 dependencies.txt 文件中。

LightStep

配置变量

以下配置变量可以在初始化时作为环境变量使用

OPENTRACING_LIGHTSTEP_COMPONENT_NAME

组件名称。

OPENTRACING_LIGHTSTEP_ACCESS_TOKEN

LightStep 收集器的访问令牌。

OPENTRACING_LIGHTSTEP_COLLECTOR_HOST

LightStep 收集器主机。默认:collector.lightstep.com

OPENTRACING_LIGHTSTEP_COLLECTOR_PORT

LightStep 收集器端口(整数)。默认:443

OPENTRACING_LIGHTSTEP_VERBOSITY

跟踪器的详细程度(整数)。默认:0

import opentracing
from opentracing_utils import OPENTRACING_LIGHTSTEP, init_opentracing_tracer

# Initialize upon application start
init_opentracing_tracer(OPENTRACING_LIGHTSTEP)

# It is possible to pass args
# init_opentracing_tracer(OPENTRACING_LIGHTSTEP, component_name='python-server', access_token='123', collector_host='production-collector.com')

# Now use the opentracing.tracer
root_span = opentracing.tracer.start_span(operation_name='root_span')
依赖项

lightstep 添加到您项目的 dependencies.txt 文件中。

@trace 装饰器

@trace 装饰器支持 OpenTracing scope_manager API(opentracing-utils > 0.21.0 新增)。

检测父 span 的顺序如下

  1. 如果存在,则使用 span_extractor

  2. 从传递的 kwargs 中检测。

  3. 检测活动的 scope_manager span(opentracing.tracer.active_span)。

  4. 使用调用堆栈帧进行检测。

import opentracing
from opentracing_utils import trace, extract_span_from_kwargs

# decorate all your functions that require tracing

# Normal traced function
@trace()
def trace_me():
    pass


# Traced function with access to created span in ``kwargs``
@trace(operation_name='user.operation', pass_span=True)
def user_operation(user, op, **kwargs):
    current_span = extract_span_from_kwargs(**kwargs)

    current_span.set_tag('user.id', user.id)

    # Then do stuff ...

    # trace_me will have ``current_span`` as its parent.
    trace_me()

# Traced function using ``follows_from`` instead of ``child_of`` reference.
@trace(use_follows_from=True)
def trace_me_later():
    pass


# Start a fresh trace - any parent spans will be ignored
@trace(operation_name='epoch', ignore_parent_span=True)
def start_fresh():

    user = {'id': 1}

    # trace decorator will handle trace heirarchy
    user_operation(user, 'create')

    # trace_me will have ``epoch`` span as its parent.
    trace_me()

使用 scope_manager 的 @trace

如果需要始终使用 scope_manager,则可以将 use_scope_manager=True 传递给 @trace

# ``use_scope_manager=True`` will always use scope_manager API for activating the new span.
@trace(operation_name='traced', use_scope_manager=True)
def trace_me_via_scope_manager():
    # @trace will activate the current span using the ``scope_manager``.
    current_span = opentracing.tracer.active_span
    assert current_span.operation_name == 'traced'

    # @trace will detect parent span from the ``scope_manager`` active span and automatically activate the new nested span.
    @trace(operation_name='nested')
    def trace_and_detect_scope():
        nested_span = opentracing.tracer.active_span
        assert nested_span.operation_name == 'nested'

    trace_and_detect_scope()

    # current_span is back to be the active span.
    assert current_span == opentracing.tracer.active_span


# If the ``scope_manager`` API is activating the parent span, @trace will detect it and use the ``scope_manager`` for the child span as well.
@trace()
def trace_and_detect_parent_scope():
    current_span = opentracing.tracer.active_span
    assert current_span.operation_name == 'trace_and_detect_parent_scope'


with opentracing.tracer.start_active_span('top_span', finish_on_close=True):

    # the child span will depend on the ``scope_manager`` to detect the ``top_span`` as the parent span for the following function call.
    trace_and_detect_parent_scope()

跳过 span

在某些情况下,您可能需要在使用 @trace 装饰器时跳过某些 span。

def skip_this_span(arg1, arg2, **kwargs):
    if arg1 == 'special':
        # span should be skipped
        return True

    return False


@trace(skip_span=skip_this_span)
def traced(arg1, arg2):
    pass


top_span = opentracing.tracer.start_span(operation_name='top_trace')
with top_span:
    # this call will be traced and have a span!
    traced('open', 'tracing')

    # this call won't be traced and no span to be added!
    traced('special', 'tracing')

断开的跟踪

如果您计划断开嵌套跟踪,则建议将 span 传递给跟踪函数。

top_span = opentracing.tracer.start_span(operation_name='top_trace')
with top_span:

    # This one gets ``top_span`` as parent span
    call_traced()

    # Here, we break the trace, since we create a new span with no parents
    broken_span = opentracing.tracer.start_span(operation_name='broken_trace')
    with broken_span:
        # This one gets ``broken_span`` as parent span (not consistent in 2.7 and 3.5)
        call_traced()

        # pass span as safer/guaranteed trace here
        call_traced(span=broken_span)

    # ISSUE: Due to stack call inspection, next call will get ``broken_span`` instead of ``top_span``, which is wrong!!
    call_traced()

    # To get the ``top_span`` as parent span, then pass it to the traced call
    call_traced(span=top_span)

多个跟踪

如果您计划使用多个跟踪,则最好始终传递 span,因为它更安全/有保证。

注意:如果使用 scope_manager,则这不应该是一个问题。

first_span = opentracing.tracer.start_span(operation_name='first_trace')
with first_span:

    # This one gets ``first_span`` as parent span
    call_traced()

second_span = opentracing.tracer.start_span(operation_name='second_trace')
with second_span:

    # ISSUE: This one **could** get ``first_span`` as parent span (not consistent among Python versions)
    call_traced()

    # It is better to pass ``second_span`` explicitly
    call_traced(span=second_span)

生成器(yield)

使用生成器可能会变得复杂,并可能导致无效的父 span 检查。建议显式传递 span。

@trace(pass_span=True)
def gen(**kwargs):
    s = extract_span_from_kwargs(**kwargs)  # noqa

    # Extract and pass span to ``f2()`` otherwise it could get ``f1()`` as parent span instead of ``gen()``
    f2(span=s)

    for i in range(10):
        yield i

@trace()
def f2():
    pass

@trace()
def f1():
    list(gen())

first_span = opentracing.tracer.start_span(operation_name='first_trace')
with first_span:
    f1()

外部库和客户端

Django

用于跟踪 Django 应用程序。您可以使用以下内容:

  • OpenTracingHttpMiddleware:用于跟踪传入的HTTP请求

# In settings.py or equivalent Django config
from opentracing_utils import init_opentracing_tracer
init_opentracing_tracer(YOUR_TRACER)  # make sure opentracing.tracer is initialized properly.

MIDDLEWARE = (
'opentracing_utils.OpenTracingHttpMiddleware',  # goes first in the list
# ... more middlewares here
)

# Further options

# Add default tags to all incoming HTTP requests spans.
OPENTRACING_UTILS_DEFAULT_TAGS = {'my-default-tag': 'tag-value'}

# Add error tag on 4XX responses (default is ``True``).
OPENTRACING_UTILS_ERROR_4XX = False

# Override span operation_name (default is ``view_func.__name__``).
OPENTRACING_UTILS_OPERATION_NAME_CALLABLE = 'my_app.utils.span_operation_name'

# Use tracer scope manager (default is ``False``).
OPENTRACING_UTILS_USE_SCOPE_MANAGER = True

# Exclude certain requests from OpenTracing
OPENTRACING_UTILS_SKIP_SPAN_CALLABLE = 'my_app.utils.skip_span'

以下是覆盖跨度操作名称和跳过跨度的可调用示例

# my_app/utils.py
def span_operation_name(request, view_func, view_args, view_kwargs):
    return 'edge_{}'.format(view_func.__name__)

def skip_span(request, view_func, view_args, view_kwargs):
    if view_func.__name__.startswith('no_trace_'):
        return True
    return False

为了在视图中跟踪跟踪,您可以使用 extract_span_from_django_request 工具函数。

# my_app/views.py

from opentracing_utils import trace, extract_span_from_django_request

@trace(span_extractor=extract_span_from_django_request, operation_name='custom_view')
def my_traced_view(request):
    ...

Flask

用于跟踪 Flask 应用程序。此实用函数添加了一个中间件,用于处理Flask应用程序的所有传入请求。

from opentracing_utils import trace_flask, extract_span_from_flask_request
from flask import Flask

app = Flask(__name__)

trace_flask(app)

# You can use the ``scope_manager`` for managing all spans.
trace_flask(app, use_scope_manager=True)

# You can add default_tags or optionally treat 4xx responses as not an error (i.e no error tag in span)
# trace_flask(app, default_tags={'always-there': True}, error_on_4xx=False)

# Extract current span from request context
def internal_function():
    current_span = extract_span_from_flask_request()

    current_span.set_tag('internal', True)

# You can skip requests spans.
def skip_health_checks(request):
    return request.path == '/health'

# trace_flask(skip_span=skip_health_checks)

Requests

用于跟踪所有传出请求的 requests 客户端库。

# trace_requests should be called as early as possible, before importing requests
from opentracing_utils import trace_requests
trace_requests()  # noqa

# You can use the ``scope_manager`` for managing all spans.
trace_requests(use_scope_manager=True)  # noqa

# In case you want to include default span tags to be sent with every outgoing request.
# trace_requests(default_tags={'account_id': '123'}, set_error_tag=False)

# In case you want to keep the URL query args (masked by default in order to avoid leaking auth tokens etc...)
# trace_requests(mask_url_query=False)

# You can also mask URL path parameters (e.g. http://hostname/1 will be http://hostname/??/)
# trace_requests(mask_url_path=True)

# The library patches the requests library send functionality. This causes
# all requests to propagate the span id's in the headers. Sometimes this is
# undesireable so it's also possible to avoid tracing specific URL's or
# endpoints. trace_requests accepts a list of regex patterns and matches the
# request.url against these patterns, ignoring traces if any pattern matches.
# trace_requests(ignore_url_patterns=[r".*hostname/endpoint"])

import requests

def main():

    span = opentracing.tracer.start_span(operation_name='main')
    with span:
        # Following call will be traced as a ``child span`` and propagated via HTTP headers.
        requests.get('https://example.org')

SQLAlchemy

用于跟踪所有 SQL 查询的 SQLAlchemy 客户端库。

# trace_sqlalchemy can be used to trace all SQL queries.
# By default, span operation_name will be deduced from the query statement (e.g. select, update, delete).
from opentracing_utils import trace_sqlalchemy
trace_sqlalchemy()

# You can use the ``scope_manager`` for managing all spans.
trace_sqlalchemy(use_scope_manager=True)

# You can customize the span operation_name via supplying a callable
def get_sqlalchemy_span_op_name(conn, cursor, statement, parameters, context, executemany):
    # inspect statement and parameters etc...
    return 'custom_operation_name'
# trace_sqlalchemy(operation_name=get_sqlalchemy_span_op_name)

# By default, trace_sqlalchemy will not set error tags for SQL errors/exceptions. You can change that via ``set_error_tag`` param.
# trace_sqlalchemy(set_error_tag=True)

# you can skip spans for certain SQL queries.
def skip_inserts(conn, cursor, statement, parameters, context, executemany):
    return statement.lower().startswith('insert')

# trace_sqlalchemy(skip_span=skip_inserts)

# you can enrich the span with by supplying an ``enrich_span`` callable.
def enrich_sql_span_parameters(span, conn, cursor, statement, parameters, context, executemany):
    span.set_tag('parameters', parameters)

# trace_sqlalchemy(enrich_span=enrich_sql_span_parameters)

许可协议

MIT 许可证 (MIT)

版权所有 (c) 2017 Zalando SE, https://tech.zalando.com

任何人未经许可,不得以任何形式复制、传播、修改、合并、出版、分发、许可和/或出售本软件及其相关文档(“软件”),并允许向软件提供方提供本软件的人从事上述活动,但必须遵守以下条件:

上述版权声明和本许可声明应包含在软件的副本或实质部分中。

软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不侵犯版权的保证。在任何情况下,作者或版权所有者不对任何索赔、损害或其他责任负责,无论基于合同、侵权或其他原因,是否因软件或其使用或其它方式产生。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分布

opentracing-utils-0.23.0.tar.gz (17.7 kB 查看哈希)

上传时间

构建分布

opentracing_utils-0.23.0-py3.6.egg (44.9 kB 查看哈希)

上传时间

opentracing_utils-0.23.0-py3.5.egg (45.6 kB 查看哈希)

上传时间

opentracing_utils-0.23.0-py3-none-any.whl (29.8 kB 查看哈希)

上传时间 Python 3

支持