自动插桩示例
此页面演示了如何在 OpenTelemetry 中使用 Python 自动插桩。本示例基于 OpenTracing 示例。您可以从 opentelemetry-python 存储库下载或查看本页面使用的 源文件。
本示例使用三个不同的脚本。它们之间的主要区别在于插桩方式
server_manual.py是手动插桩的。server_automatic.py是自动插桩的。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_app 的 excluded_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_REQUEST 和 OTEL_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-id 和 set-cookie 等头的值替换为 Span 中的 [REDACTED]。