mcpField
O
otel-python-style

otel-python-style

github.com/superloglabsDevelopment13377 installsUpdated 21w ago
Repository

About otel-python-style

A coding skill for applying OpenTelemetry conventions in Python, covering tracing, errors, logs, initialization, and metrics. It is intended for developers adding observability to Python applications.

Key features

  1. 01Module-level tracing and metrics
  2. 02Decorator and context-managed spans
  3. 03Exception recording and status
  4. 04OTLP log setup guidance
  5. 05Metric naming and cardinality guidance

Use cases

  1. 01Instrument bounded synchronous or async work
  2. 02Record exceptions on active spans
  3. 03Configure OTLP forwarding for logs
  4. 04Track LLM tokens and request metrics

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/superloglabs/skills --skill otel-python-style

After installing, type/otel-python-stylein Claude Code to confirm it loaded.

Details

From the project README

OTel Python Style

Acquire OTel objects at module scope.

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:

@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:

@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.).

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.

Error Paths

Record exceptions on the active span.

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.

Init Behavior

Use the source-level public Superlog configuration pattern from otel-onboarding-style in the init module. The public project token is write-only and belongs with the endpoint in the setup block, like a PostHog project token or Sentry DSN.

SUPERLOG_ENDPOINT = "https://intake.superlog.sh"
SUPERLOG_PUBLIC_TOKEN = "sl_public_..."


# The token MUST be sent as the `x-api-key` header. Ingest only reads
# `x-api-key` or `Authorization: Bearer <token>`; any other header name 401s.
def superlog_headers(token: str) -> dict[str, str]:
    return {"x-api-key": token}


def init_observability() -> None:
    exporter = OTLPSpanExporter(
        endpoint=f"{SUPERLOG_ENDPOINT}/v1/traces",
        headers=superlog_headers(SUPERLOG_PUBLIC_TOKEN),
    )
    ...

Add a small _INITIALIZED guard only when the app can realistically call this function more than once.

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.

Comments

Markdown supported