自动插桩示例

此页面演示了如何在 OpenTelemetry 中使用 Python 自动插桩。本示例基于 OpenTracing 示例。您可以从 opentelemetry-python 存储库下载或查看本页面使用的 源文件

本示例使用三个不同的脚本。它们之间的主要区别在于插桩方式

  1. server_manual.py手动插桩的。
  2. server_automatic.py自动插桩的。
  3. server_programmatic.py程序化插桩的。

程序化插桩是一种插桩方式,只需在应用程序中添加少量插桩代码。只有一些插桩库提供了附加功能,允许您在程序化使用时对插桩过程进行更精细地控制。

在不使用自动插桩代理的情况下运行第一个脚本,然后在运行第二个脚本时使用代理。它们应该产生相同的结果,这表明自动插桩代理所做的与手动插桩完全相同。

自动插桩利用 Monkey Patching 通过 插桩库在运行时动态重写方法和类。这减少了将 OpenTelemetry 集成到应用程序代码所需的工作量。下面,您将看到手动、自动和程序化插桩的 Flask 路由之间的区别。

手动插桩服务器

server_manual.py

@app.route("/server_request")
def server_request():
    with tracer.start_as_current_span(
        "server_request",
        context=extract(request.headers),
        kind=trace.SpanKind.SERVER,
        attributes=collect_request_attributes(request.environ),
    ):
        print(request.args.get("param"))
        return "served"

自动插桩服务器

server_automatic.py

@app.route("/server_request")
def server_request():
    print(request.args.get("param"))
    return "served"

程序化插桩服务器

server_programmatic.py

instrumentor = FlaskInstrumentor()

app = Flask(__name__)

instrumentor.instrument_app(app)
# instrumentor.instrument_app(app, excluded_urls="/server_request")
@app.route("/server_request")
def server_request():
    print(request.args.get("param"))
    return "served"

准备

在单独的虚拟环境中执行以下示例。运行以下命令为自动插桩做准备

mkdir auto_instrumentation
cd auto_instrumentation
python -m venv venv
source ./venv/bin/activate

安装

运行以下命令安装相应的包。opentelemetry-distro 包依赖于其他几个包,例如用于自定义您自己代码插桩的 opentelemetry-sdk,以及提供多个命令来帮助自动插桩程序的 opentelemetry-instrumentation

pip install opentelemetry-distro
pip install flask requests

运行 opentelemetry-bootstrap 命令

opentelemetry-bootstrap -a install

接下来的示例会将插桩结果发送到控制台。了解有关安装和配置 OpenTelemetry Distro 以将遥测数据发送到其他目的地(例如 OpenTelemetry Collector)的更多信息。

注意:要通过 opentelemetry-instrument 使用自动插桩,您必须通过环境变量或命令行进行配置。该代理创建的遥测管道除了通过这些方式之外,无法进行修改。如果您需要更多自定义您的遥测管道,那么您需要放弃代理,并将 OpenTelemetry SDK 和插桩库导入到您的代码中并在那里进行配置。您还可以通过导入 OpenTelemetry API 来扩展自动插桩。有关更多详细信息,请参阅 API 参考

执行

本节将引导您完成手动插桩服务器的过程,以及执行自动插桩服务器的过程。

执行手动插桩服务器

在两个独立的控制台中执行服务器,一个控制台用于运行构成此示例的每个脚本

source ./venv/bin/activate
python server_manual.py
source ./venv/bin/activate
python client.py

运行 server_manual.py 的控制台将以 JSON 格式显示插桩生成的 Span。Span 应该与以下示例类似

{
  "name": "server_request",
  "context": {
    "trace_id": "0xfa002aad260b5f7110db674a9ddfcd23",
    "span_id": "0x8b8bbaf3ca9c5131",
    "trace_state": "{}"
  },
  "kind": "SpanKind.SERVER",
  "parent_id": null,
  "start_time": "2020-04-30T17:28:57.886397Z",
  "end_time": "2020-04-30T17:28:57.886490Z",
  "status": {
    "status_code": "OK"
  },
  "attributes": {
    "http.method": "GET",
    "http.server_name": "127.0.0.1",
    "http.scheme": "http",
    "host.port": 8082,
    "http.host": "localhost:8082",
    "http.target": "/server_request?param=testing",
    "net.peer.ip": "127.0.0.1",
    "net.peer.port": 52872,
    "http.flavor": "1.1"
  },
  "events": [],
  "links": [],
  "resource": {
    "telemetry.sdk.language": "python",
    "telemetry.sdk.name": "opentelemetry",
    "telemetry.sdk.version": "0.16b1"
  }
}

执行自动插桩服务器

Control+C 停止执行 server_manual.py,然后运行以下命令

opentelemetry-instrument --traces_exporter console --metrics_exporter none --logs_exporter none python server_automatic.py

在之前执行 client.py 的控制台中,再次运行以下命令

python client.py

运行 server_automatic.py 的控制台将以 JSON 格式显示插桩生成的 Span。Span 应该与以下示例类似

{
  "name": "server_request",
  "context": {
    "trace_id": "0x9f528e0b76189f539d9c21b1a7a2fc24",
    "span_id": "0xd79760685cd4c269",
    "trace_state": "{}"
  },
  "kind": "SpanKind.SERVER",
  "parent_id": "0xb4fb7eee22ef78e4",
  "start_time": "2020-04-30T17:10:02.400604Z",
  "end_time": "2020-04-30T17:10:02.401858Z",
  "status": {
    "status_code": "OK"
  },
  "attributes": {
    "http.method": "GET",
    "http.server_name": "127.0.0.1",
    "http.scheme": "http",
    "host.port": 8082,
    "http.host": "localhost:8082",
    "http.target": "/server_request?param=testing",
    "net.peer.ip": "127.0.0.1",
    "net.peer.port": 48240,
    "http.flavor": "1.1",
    "http.route": "/server_request",
    "http.status_text": "OK",
    "http.status_code": 200
  },
  "events": [],
  "links": [],
  "resource": {
    "telemetry.sdk.language": "python",
    "telemetry.sdk.name": "opentelemetry",
    "telemetry.sdk.version": "0.16b1",
    "service.name": ""
  }
}

您可以看到,两个输出是相同的,因为自动插桩所做的与手动插桩完全相同。

执行程序化插桩服务器

也可以单独使用插桩库(例如 opentelemetry-instrumentation-flask),这可能在自定义选项方面具有优势。但是,选择这样做意味着您放弃了通过 opentelemetry-instrument 启动应用程序进行自动插桩,因为这两者是互斥的。

像手动插桩一样执行服务器,在两个独立的控制台中执行,一个控制台用于运行构成此示例的每个脚本

source ./venv/bin/activate
python server_programmatic.py
source ./venv/bin/activate
python client.py

结果应该与运行手动插桩时相同。

使用程序化插桩功能

一些插桩库包含允许在程序化插桩时进行更精确控制的功能,Flask 的插桩库就是其中之一。

此示例中有一行被注释掉了,请将其更改为如下形式

# instrumentor.instrument_app(app)
instrumentor.instrument_app(app, excluded_urls="/server_request")

再次运行示例后,服务器端不应出现任何插桩。这是因为传递给 instrument_appexcluded_urls 选项有效地阻止了 server_request 函数被插桩,因为它的 URL 与传递给 excluded_urls 的正则表达式匹配。

调试时插桩

可以通过以下方式在 Flask 应用中启用调试模式

if __name__ == "__main__":
    app.run(port=8082, debug=True)

调试模式可能会中断插桩的发生,因为它启用了重新加载器。要在启用调试模式时运行插桩,请将 use_reloader 选项设置为 False

if __name__ == "__main__":
    app.run(port=8082, debug=True, use_reloader=False)

配置

自动插桩可以从环境变量中获取配置。

捕获 HTTP 请求和响应头

根据 语义约定,您可以将预定义的 HTTP 头捕获为 Span 属性。

要定义要捕获的 HTTP 头,请通过环境变量 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUESTOTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE 提供一个逗号分隔的 HTTP 头名称列表,例如

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST="Accept-Encoding,User-Agent,Referer"
export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE="Last-Modified,Content-Type"
opentelemetry-instrument --traces_exporter console --metrics_exporter none --logs_exporter none python app.py

以下 HTTP 插桩支持这些配置选项

  • Aiohttp-server
  • Django
  • Falcon
  • FastAPI
  • Pyramid
  • Starlette
  • Tornado
  • WSGI

如果这些头信息可用,它们将被包含在您的 Span 中

{
  "attributes": {
    "http.request.header.user-agent": [
      "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)"
    ],
    "http.request.header.accept_encoding": ["gzip, deflate, br"],
    "http.response.header.last_modified": ["2022-04-20 17:07:13.075765"],
    "http.response.header.content_type": ["text/html; charset=utf-8"]
  }
}

捕获头信息的清理

为了防止存储敏感数据,例如个人身份信息 (PII)、会话密钥、密码等,请将环境变量 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS 设置为一个逗号分隔的 HTTP 头名称列表,这些头将被清理。可以使用正则表达式,所有头名称都将以不区分大小写的方式进行匹配。

例如,

export OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS=".*session.*,set-cookie"

会将诸如 session-idset-cookie 等头的值替换为 Span 中的 [REDACTED]