Skip to main content
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().

What the SDK provides

After logging.basicConfig (or dictConfig) and one call to bb_ai_sdk.logging.init():
CapabilityWhat it does
Redaction at record creationScrubs every LogRecord at the factory level—including uvicorn.access and other loggers that do not propagate to root
Redaction at format timeWraps root handler formatters so tracebacks and rendered extra fields are scrubbed before output
Trace correlationAdds trace_id and span_id to each record from the active OpenTelemetry span (or "-" when no span is active)
Shared patterns for spansThe 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).

Startup

Use the same order as What you call at startup on the Observability page:
  1. logging.basicConfig (or dictConfig) with a format preset that includes %(trace_id)s and %(span_id)s
  2. bb_ai_sdk.logging.init() — once, before serving traffic
  3. configure_observability(...) — tracing and instrumentors
import logging
from dotenv import load_dotenv
from fastapi import FastAPI

load_dotenv()

from bb_ai_sdk.logging import DEFAULT_DATEFMT, STANDARD_FORMAT, init as init_logging
from bb_ai_sdk.observability import configure_observability

app = FastAPI(title="my-agent-api")

logging.basicConfig(
    level=logging.INFO,
    format=STANDARD_FORMAT,
    datefmt=DEFAULT_DATEFMT,
)
init_logging(capture_warnings=True)

configure_observability(
    fastapi_app=app,
    framework="agno",
    service_name="my-agent-api",
    environment="development",
)
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.

init() parameters

ParameterDefaultPurpose
redactTrueInstall global redaction at record creation and wrap formatters
correlate_tracesTrueStamp trace_id / span_id on each record
capture_warningsFalseRoute warnings.warn(...) through logging (and redaction). Set True for agent services
wrap_existing_formattersTrueScrub tracebacks and formatted extra on root handlers
record_redactionsFalseAttach pattern names that fired on each record (audit trail)
init() is idempotent—the first call wins. To reconfigure, call disable_redaction() first.

Log format presets

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().
ConstantFormat stringUse when
STANDARD_FORMAT%(asctime)s | %(name)s | %(levelname)s | [trace_id=%(trace_id)s span_id=%(span_id)s] %(message)sDefault — timestamp, logger name, level, trace/span IDs, message
COMPACT_FORMAT%(asctime)s | %(levelname)s | [trace_id=%(trace_id)s span_id=%(span_id)s] %(message)sShorter lines when the logger name is redundant
MINIMAL_FORMAT%(levelname)s [trace_id=%(trace_id)s span_id=%(span_id)s] %(message)sLevel, correlation IDs, and message only
MESSAGE_TRACE_FORMAT%(message)s | trace_id=%(trace_id)s span_id=%(span_id)sMessage-first layout (tests or REPL demos)
DEFAULT_DATEFMT%Y-%m-%d %H:%M:%SRecommended 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.

Operator-driven patterns (no redeploy)

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_pattern

add_redaction_pattern({
    "name": "creditcard",
    "pattern": r"(?P<cc>(?:cc|card))\s*[:=]\s*\d{12,19}",
    "replacement": r"\g<cc>=[REDACTED:creditcard]",
})

What gets redacted

SurfaceCovered?
logger.info("api_key=sk-...")Yes — LogRecord factory
Formatted args, tracebacksYes — RedactingFormatter on root handlers
logger.info("x", extra={"token": "..."})Yes — formatter / record scrubbing
warnings.warn(...)Yes if init(capture_warnings=True)
OpenTelemetry span attributesYes when logging.init() ran — same global filter
Raw print() / stdoutNo — route through logging (see SDK LOGGING_GUIDE)
Span attributes also redact by key name (for example api_key, token, password) even when the value would pass content patterns unchanged.

Optional: audit trail on records

Pass record_redactions=True on the first init() call:
init(capture_warnings=True, record_redactions=True)
Each redacted record gets metadata listing which pattern names fired—useful for security reviews.

Troubleshooting

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 stdoutlogging.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_PATTERNSinit() raises ValueError at startup with the JSON index and reason. Fix the env var before serving traffic.

Starters

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.

Observability

Tracing, bb.* attributes, and export setup