Python 应用在不同场景下有不同的接入方式,本文介绍 Python 应用通过 OpenTelemetry 接入的详细操作,接入后可以实现自动收集Trace信息、自动生成Metric指标、以及调用链检索、日志检索等功能。
通过 OpenTelemetry 接入
OpenTelemetry 是 CNCF 的一个可观测性项目,旨在提供可观测性领域的标准化方案,解决观测数据的数据模型、采集、处理、导出等的标准化问题,提供与三方 vendor 无关的服务。
如果您的应用已经接入了 OpenTelemetry 的 SDK,APM 服务端可以接收 OpenTelemetry 上报的数据。接入 APM 后,您将获得RED指标生成能力、Trace 多维检索、单链路检索、拓扑生成等多项能力。
使用限制
- 其中,APMPlus 采用 Prometheus 作为 OpenTelemetry Metrics 的存储,支持 promql 对 metrics 进行查询,具体应用请参见 自定义看板 和 报警管理。
- OpenTelemetry Metrics 转换为 Prometheus Metrics 时,存在一定限制:
- 不支持聚合方式为 delta 的数据。请不要修改 SDK 侧的默认聚合配置,保证 SDK 侧数据的聚合方式为 cumulative。
- 工作区拥有独立的数据上报凭证,如需使用工作区进行数据管理和数据隔离,请确保 apmplus-opentelemetry-collector 组件已经升级至 v3.1.0 及以上版本。
若使用 gevent 协程,需要配置环境变量 GEVENT_ENABLE=true。例如,程序中有使用如下协程时,需要设置环境变量 GEVENT_ENABLE=true。
from gevent import monkey
如果您的应用使用 Django 框架,在接入前需要注意如下事项:
- 要在 K8s 环境通过 OpenTelemetry 自动接入,需要确保 Python 版本为 3.8 及以上;
- OpenTelemetry 的引入,可能会导致 Django 应用不再使用默认的配置文件,需要通过环境变量重新指定配置文件:
export DJANGO_SETTINGS_MODULE=mysite.settings
- 可以在项目的启动脚本中加入此设置项,或者通过 YAML 文件添加环境变量:
- name: DJANGO_SETTINGS_MODULE
自动接入
APM OpenTelemetry Collector 是 APM 基于 OpenTelemetry Collector 二次开发的数据采集器,遵守 Apache2.0 协议,支持采集 OpenTelemetry 监控数据,同时支持采集 Prometheus 指标、应用监控数据和 Kubernetes 资源指标。
- 安装 APM OpenTelemetry Collector。
- 通过 VKE 集群组件中心,白屏安装 apmplus-opentelemetry-collector 组件。前往 VKE 集群界面,选择 组件管理 -> 监控,单击 apmplus-opentelemetry-collector 组件右上角...中的安装,完成 Apmplus OpenTelemetry Collector 的部署和管理。
-
- 在服务的 Kubernetes Deployment 配置中增加instrumentation.apmplus.volcengine.com/inject-python和instrumentation.apmplus.volcengine.com/workspace注解。代码示例如下:
instrumentation.apmplus.volcengine.com/inject-python: 'true'
instrumentation.apmplus.volcengine.com/workspace: default
- name: python-flask-demo
image: python-flask:latest
- name: CONTAINER_RUNTIME
采用社区提供的 Opentelemetry Python SDK 手动埋点的方式上报数据,可以参考社区提供的 接入文档 和 python demo 完成 Python 项目构建。 通过 opentelemetry-instrument 完成埋点上报
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-distro 包安装了 API、SDK,以及 opentelemetry-bootstrap 和 opentelemetry-instrument 工具。
- 如果指定数据直接上报至 ApmPlus 后端,示例如下:
opentelemetry-instrument \
--service_name <your-service-name> \
--exporter_otlp_endpoint=<apmplus_endpoint> \
--exporter_otlp_protocol=<protocol> \
--exporter_otlp_headers=X-ByteAPM-AppKey=<app_key> \
- 如果指定数据上报至 Collector,则无需指定 exporter_otlp_headers,示例如下:
opentelemetry-instrument \
--service_name <your-service-name> \
--exporter_otlp_endpoint=<collector_endpoint> \
--exporter_otlp_protocol=<protocol> \
OTEL_SERVICE_NAME=your-service-name \
OTEL_EXPORTER_OTLP_ENDPOINT=<apmplus_endpoint>/<collector_endpoint>
OTEL_EXPORTER_OTLP_PROTOCOL=<protocol>
OTEL_EXPORTER_OTLP_HEADERS=X-ByteAPM-AppKey=<app_key>
opentelemetry-instrument \
通过 opentelemetry-sdk 手动埋点
pip install opentelemetry-api
pip install opentelemetry-sdk
pip install opentelemetry-exporter-otlp
Opentelemetry 官方文档相关介绍参考 Instrumentation。官方文档中的 Demo 将 metrics 和 trace 打印到 console,需要上报到 collector 的数据,除了需要换用 grpc/http exporter 外,还需要设置 endPoint、 headers 等信息。 上报地址推荐使用 collector endpoint,参考 Demo 示例如下:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
endpoint = "apmplus-cn-beijing.ivolces.com:4317"
"x-byteapm-appkey": "<app_key>",
"service.name": "your-service-name",
resource = Resource.create(resource_attributes)
exporter = OTLPSpanExporter(endpoint=endpoint, insecure=True, headers=headers)
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("my.tracer.name")
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
PeriodicExportingMetricReader,
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
endpoint = "apmplus-cn-beijing.ivolces.com:4317"
"x-byteapm-appkey": "<app_key>",
"service.name": "your-service-name",
resource = Resource.create(resource_attributes)
exporter = OTLPMetricExporter(endpoint=endpoint, headers=headers)
metric_reader = PeriodicExportingMetricReader(exporter)
provider = MeterProvider(metric_readers=[metric_reader], resource=resource)
metrics.set_meter_provider(provider)
meter = metrics.get_meter("my.meter.name")
对于日志的上报,除了需要初始化 LoggerProvider, 建议使用 opentelemetry LoggingInstrumentor 对 logging 库进行插装,从而将 log 与 trace 关联起来。
pip install opentelemetry-instrumentation-logging
- 初始化 LoggerProvider 并添加 LoggingInstrumentor
from opentelemetry._logs import get_logger, set_logger_provider
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.instrumentation.logging import LoggingInstrumentor
endpoint = "apmplus-cn-beijing.ivolces.com:4317"
"x-byteapm-appkey": "<app_key>",
"service.name": "your-service-name",
resource = Resource.create(resource_attributes)
exporter = OTLPLogExporter(endpoint=endpoint, insecure=True, headers=headers)
provider = LoggerProvider(resource=resource)
processor = BatchLogRecordProcessor(exporter)
provider.add_log_record_processor(processor)
set_logger_provider(provider)
handler = LoggingHandler(level=logging.INFO, logger_provider=provider)
logging.basicConfig(handlers=[handler], level=logging.INFO)
LoggingInstrumentor().instrument(set_logging_format=True)
logging.info("This is an OpenTelemetry log record!")
Python 应用的日志采集依赖 Opentelemetry,完成上述 OpenTelemetry 接入 Python 应用的全部流程,即可自动采集和上报日志信息。
Opentelemetry Python 日志通过 logging 库插桩方式实现,通过 Log 信息增强和添加对应上报 handler,将日志上报到 APMPlus 后端并关联 Trace 和 Span 等信息。因此,只有通过 logging 库打印的日志,才能采集并发送到 APMPlus,控制台等其他方式无法采集日志信息。
Python 应用中的一条日志,从诞生到通过 OpenTelemetry 发送再到上报 APMPlus 后端的完整流程如下:
采集 Python 应用日志时,需要注意以下情况。
Python logging 库存在 Log level 配置,默认为 WARNING。 当记录日志小于 level 配置时,实际不会进行日志打印操作,同样也不会上报到 APMPlus 服务端。因此,如需采集 INFO、DEBUG 等日志,必须通过 logging.basicConfig 手动设置 Log level,示例代码如下:
logging.basicConfig(level=logging.INFO)
为了更好区分日志,业务通常不会直接使用 logging 进行日志记录,而是使用 logging.getLogger(name) 创建 logger,再通过 logger 进行日志记录。而 Opentelemetry logging 基于 root logger 进行埋点。因此,当使用 logger 进行日志记录,并且设置了 propagate = False 时,logger 记录的日志不会传播到 root logger, 也无法被 opentelemetry 采集上报。
每个通过 logging.getLogger(name) 创建的 logger 实例均存在 propagate 属性,默认为 True,工作原理如下。
- propagate = True (默认): 当 logger 处理完一条日志后,将进行 propagate 标志检查,若为 True,这条日志记录将继续向上传递给父 logger (parent logger)。此过程一直持续到 root logger。
- propagate = False: 当 logger 处理完一条日志后,将停止向上传播。其父 logger 及更高层级的祖先 logger 将完全不感知这条日志的存在。
日志记录需要关联日志所在的请求 Trace,因此,在 Opentelemetry LoggingInstrumentor 中会对 Log 信息进行增强,将获取日志打印时的上下文信息并存放在 Log 信息中。但当实际业务中采用异步方式记录日志时,可能丢失上下文信息,此时,日志无法关联到 Trace,将导致 APMPlus 日志页面的 TraceId 信息显示为空。
当日志采集时发现并没有日志数据采集时,排除注意事项中提及的情况后,可通过主动添加相应日志的方式进行问题排查。示例如下:
- 在 logging basicConfig 设置后和打印日志前添加以下代码,将可以打印出 logging 库的 handler 和 level 信息。
print(f"logging handlers: {logging.root.handlers}")
print(f"logging level: {logging.root.level}")
此时,若 LoggingInstrumentor 生效,则输出信息的 logging.root.handlers 中会包含以下 handler。
<LoggingHandler (NOTSET)>
- 若使用 logger 打印日志,可使用以下代码打印日志。
print(f"logger propagate: {your_logger.propagate}")
print(f"logger level: {your_logger.level}")
此时,若 LoggingInstrumentor 生效,则输出信息的 logging.root.handlers 中会包含以下 handler。
<LoggingHandler (NOTSET)>