Secure application logging with trace correlation and shared redaction for logs and spans
The bb_ai_sdk.logging module scrubs secrets from log lines and stamps trace and span IDs on each record. The same redaction engine covers OpenTelemetry span attributes when you call bb_ai_sdk.logging.init() at startup.Tracing setup lives on the Observability page. This page covers log formats, redaction patterns, coverage, and how logging cooperates with configure_observability().
After logging.basicConfig (or dictConfig) and one call to bb_ai_sdk.logging.init():
Capability
What it does
Redaction at record creation
Scrubs every LogRecord at the factory level—including uvicorn.access and other loggers that do not propagate to root
Redaction at format time
Wraps root handler formatters so tracebacks and rendered extra fields are scrubbed before output
Trace correlation
Adds trace_id and span_id to each record from the active OpenTelemetry span (or "-" when no span is active)
Shared patterns for spans
The global filter from init() is what bb_ai_sdk.observability uses for span-attribute redaction—one pattern list for logs and traces
Built-in patterns cover common secrets: API keys, tokens, passwords, secrets, and Langfuse credentials. Replacements use typed tags such as [REDACTED:api_key] so audits show what was redacted.
Deep reference (architecture, performance, troubleshooting) lives in the bb-ai-sdk repository under docs/logging/ (OVERVIEW.md, LOGGING_GUIDE.md).
Use logging.getLogger(__name__) everywhere. This module does not export a shared logger object.
Call bb_ai_sdk.logging.init() even when you only care about span redaction. Without it, observability falls back to built-in default patterns only—BB_AI_SDK_REDACTION_PATTERNS* env vars and custom patterns from init(redaction_patterns=...) are not applied to spans.
Import ready-made format strings from bb_ai_sdk.logging and pass them to logging.basicConfig or dictConfig. Every preset includes %(trace_id)s and %(span_id)s—those fields are populated after you call init().
Recommended datefmt for STANDARD_FORMAT and COMPACT_FORMAT
LOG_FORMATS maps short names to the same strings: standard, compact, minimal, message_trace.When no OpenTelemetry span is active, trace_id and span_id appear as "-". That is expected for startup logs before the first request span.
Add patterns via environment variables (merged with built-ins when init() runs):
# JSON array of {name, pattern, replacement}BB_AI_SDK_REDACTION_PATTERNS='[{"name":"iban","pattern":"...","replacement":"..."}]'# Or shorthand: name|pattern|replacement; separate multiple with ;BB_AI_SDK_REDACTION_PATTERNS_SHORTHAND='ssn|(?P<ssn>ssn)\s*[:=]\s*\d{3}-\d{2}-\d{4}|\g<ssn>=[REDACTED:ssn]'
Or at runtime (applies to logs and spans immediately):
from bb_ai_sdk.logging import add_redaction_patternadd_redaction_pattern({ "name": "creditcard", "pattern": r"(?P<cc>(?:cc|card))\s*[:=]\s*\d{12,19}", "replacement": r"\g<cc>=[REDACTED:creditcard]",})
Trace IDs always show "-" in logs — No active OpenTelemetry span when the line was emitted (startup noise), or configure_observability() is not initialized. IDs populate inside instrumented request or agent spans.Secrets visible in Langfuse but redacted in stdout — logging.init() was not called; observability used the lazy default filter for spans only. Call logging.init() at startup.Custom env patterns ignored on spans — Same cause. Env vars are read inside logging.init(), not inside configure_observability().Malformed BB_AI_SDK_REDACTION_PATTERNS — init() raises ValueError at startup with the JSON index and reason. Fix the env var before serving traffic.
Wire bb_ai_sdk.logging.init() in your central logger.py (or main.py) after basicConfig. Use STANDARD_FORMAT and DEFAULT_DATEFMT, drive level from LOG_LEVEL in app settings, and call init(capture_warnings=True). See the Multi agent starter for a full example.