otel-python-style
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOTel Python Style
OTel Python 使用风格
Acquire OTel objects at module scope.
python
from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode
tracer = trace.get_tracer("mugline.voice")
meter = metrics.get_meter("mugline.voice")
greetings = meter.create_counter("voice.greetings.delivered", unit="1")在模块级别获取OTel对象。
python
from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode
tracer = trace.get_tracer("mugline.voice")
meter = metrics.get_meter("mugline.voice")
greetings = meter.create_counter("voice.greetings.delivered", unit="1")Bounded Work
限定工作范围
tracer.start_as_current_span(...)python
@tracer.start_as_current_span("do_work")
def do_work():
print("doing some work...")It works the same way on async functions and on methods, and you can grab the
active span inside the body with to set attributes:
trace.get_current_span()python
@tracer.start_as_current_span("voice.deliver_initial_greeting")
async def _deliver_initial_greeting(*, tenant_id: str, user_id: str) -> None:
span = trace.get_current_span()
span.set_attributes({
"tenant.id": tenant_id,
"user.id": user_id,
"voice.use_case": "initial_greeting",
})Use a context manager when a decorator does not fit (partial scope, dynamic
span name, etc.).
python
with tracer.start_as_current_span("order.validate") as span:
span.set_attribute("tenant.id", tenant_id)
validate_order(order)Do not use detached for bounded work.
tracer.start_span(...); span.end()tracer.start_as_current_span(...)python
@tracer.start_as_current_span("do_work")
def do_work():
print("doing some work...")它在异步函数和方法上的使用方式一致,你可以在函数体内通过 获取当前活跃的span并设置属性:
trace.get_current_span()python
@tracer.start_as_current_span("voice.deliver_initial_greeting")
async def _deliver_initial_greeting(*, tenant_id: str, user_id: str) -> None:
span = trace.get_current_span()
span.set_attributes({
"tenant.id": tenant_id,
"user.id": user_id,
"voice.use_case": "initial_greeting",
})当装饰器不适用时(如部分代码范围、动态span名称等场景),可使用上下文管理器。
python
with tracer.start_as_current_span("order.validate") as span:
span.set_attribute("tenant.id", tenant_id)
validate_order(order)在限定工作范围的场景中,请勿使用分离式的 调用。
tracer.start_span(...); span.end()Error Paths
错误处理流程
Record exceptions on the active span.
python
try:
result = await client.messages.create(...)
except Exception as exc:
span = trace.get_current_span()
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
logger.exception("llm mug copy failed", extra={"tenant_id": tenant_id})
raise在当前活跃的span上记录异常。
python
try:
result = await client.messages.create(...)
except Exception as exc:
span = trace.get_current_span()
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
logger.exception("llm mug copy failed", extra={"tenant_id": tenant_id})
raiseLogs
日志
If logs are claimed as OTLP-forwarded, configure both:
- an OTel LoggerProvider + OTLPLogExporter + LoggingHandler
- from
set_logger_provider(logger_provider)opentelemetry._logs - log correlation for existing records, e.g.
LoggingInstrumentor().instrument(...)
Preserve existing , console/file handlers, and log levels.
logging.basicConfig如果日志需要通过OTLP转发,请同时配置以下内容:
- an OTel LoggerProvider + OTLPLogExporter + LoggingHandler
- 来自 的
opentelemetry._logsset_logger_provider(logger_provider) - 现有日志记录的关联,例如
LoggingInstrumentor().instrument(...)
保留现有的 、控制台/文件处理器以及日志级别。
logging.basicConfigInit Behavior
初始化行为
Inline the endpoint and ingest key directly in the init module — don't read
them from env. The ingest key is project-scoped + write-only (Sentry DSN
shaped), so source-level configuration is the right default; env indirection
just adds a class of "OTel didn't start because env wasn't set" deploy
failures.
python
SUPERLOG_ENDPOINT = "https://intake.superlog.sh"
SUPERLOG_KEY = "superlog_live_…" # set by superlog-onboard skill on pairing
def init_observability() -> None:
exporter = OTLPSpanExporter(
endpoint=f"{SUPERLOG_ENDPOINT}/v1/traces",
headers={"authorization": f"Bearer {SUPERLOG_KEY}"},
)
...Add a small guard only when the app can realistically call this
function more than once.
_INITIALIZED直接在初始化模块中内联端点和摄取密钥——不要从环境变量中读取。摄取密钥是项目级别的且仅可写入(格式类似Sentry DSN),因此在代码层面配置是合适的默认方式;通过环境变量间接读取只会增加一类部署失败情况,比如‘因未设置环境变量导致OTel无法启动’。
python
SUPERLOG_ENDPOINT = "https://intake.superlog.sh"
SUPERLOG_KEY = "superlog_live_…" # set by superlog-onboard skill on pairing
def init_observability() -> None:
exporter = OTLPSpanExporter(
endpoint=f"{SUPERLOG_ENDPOINT}/v1/traces",
headers={"authorization": f"Bearer {SUPERLOG_KEY}"},
)
...仅当应用确实可能多次调用该函数时,才添加一个小型的 保护机制。
_INITIALIZEDMetrics
指标
Counters:
llm.tokens.inputllm.tokens.output- requests/events/jobs/errors
Use semantic units when the SDK supports them: token counters use
. Do not add app-side pricing metrics for normal
LLM calls; Superlog estimates cost centrally from provider/model/token data.
unit="tokens"llm.cost_usdHistograms:
- duration
- latency
- payload size
Avoid raw high-cardinality values in metric attributes. Prefer
tenant/org/project, operation/use case, provider/model, and outcome dimensions
over user-level metric tags.
计数器:
llm.tokens.inputllm.tokens.output- requests/events/jobs/errors
当SDK支持时,请使用语义化单位:令牌计数器使用 。对于常规LLM调用,请勿在应用端添加 定价指标;Superlog会根据供应商/模型/令牌数据集中估算成本。
unit="tokens"llm.cost_usd直方图:
- duration
- latency
- payload size
避免在指标属性中使用原始的高基数值。优先使用租户/组织/项目、操作/用例、供应商/模型以及结果维度,而非用户级别的指标标签。