otel-python-style

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OTel 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(...)
works as both a decorator and a context manager — the same call. For a whole function, the decorator form is usually what you want:
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
trace.get_current_span()
to set attributes:
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
tracer.start_span(...); span.end()
for bounded work.
tracer.start_as_current_span(...)
既可作为装饰器,也可作为上下文管理器使用——调用方式相同。对于整个函数而言,通常优先使用装饰器形式:
python
@tracer.start_as_current_span("do_work")
def do_work():
    print("doing some work...")
它在异步函数和方法上的使用方式一致,你可以在函数体内通过
trace.get_current_span()
获取当前活跃的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})
    raise

Logs

日志

If logs are claimed as OTLP-forwarded, configure both:
  • an OTel LoggerProvider + OTLPLogExporter + LoggingHandler
  • set_logger_provider(logger_provider)
    from
    opentelemetry._logs
  • log correlation for existing records, e.g.
    LoggingInstrumentor().instrument(...)
Preserve existing
logging.basicConfig
, console/file handlers, and log levels.
如果日志需要通过OTLP转发,请同时配置以下内容:
  • an OTel LoggerProvider + OTLPLogExporter + LoggingHandler
  • 来自
    opentelemetry._logs
    set_logger_provider(logger_provider)
  • 现有日志记录的关联,例如
    LoggingInstrumentor().instrument(...)
保留现有的
logging.basicConfig
、控制台/文件处理器以及日志级别。

Init 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
_INITIALIZED
guard only when the app can realistically call this function more than once.
直接在初始化模块中内联端点和摄取密钥——不要从环境变量中读取。摄取密钥是项目级别的且仅可写入(格式类似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}"},
    )
    ...
仅当应用确实可能多次调用该函数时,才添加一个小型的
_INITIALIZED
保护机制。

Metrics

指标

Counters:
  • llm.tokens.input
  • llm.tokens.output
  • requests/events/jobs/errors
Use semantic units when the SDK supports them: token counters use
unit="tokens"
. Do not add app-side
llm.cost_usd
pricing metrics for normal LLM calls; Superlog estimates cost centrally from provider/model/token data.
Histograms:
  • 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.input
  • llm.tokens.output
  • requests/events/jobs/errors
当SDK支持时,请使用语义化单位:令牌计数器使用
unit="tokens"
。对于常规LLM调用,请勿在应用端添加
llm.cost_usd
定价指标;Superlog会根据供应商/模型/令牌数据集中估算成本。
直方图:
  • duration
  • latency
  • payload size
避免在指标属性中使用原始的高基数值。优先使用租户/组织/项目、操作/用例、供应商/模型以及结果维度,而非用户级别的指标标签。