6.2 The Logging Bridge API & Log Appenders
Key Takeaways
OpenTelemetry does not require rewriting existing log statements: the recommended pattern keeps idiomatic frameworks such as Logback, Log4j2, SLF4J, Python logging, Winston, Zap, or Serilog and installs an OpenTelemetry appender.
The Logs API (formerly called the Logs Bridge API) exists mainly for appender authors; the current specification also lets instrumentation libraries and applications call it directly, most often to emit structured events.
OpenTelemetry Log Appenders bridge native logging events into the OpenTelemetry pipeline in-process, automatically extracting active TraceId and SpanId from ambient context and populating standardized severity and attribute structures.
The LoggerProvider orchestrates log processing pipelines via LogRecordProcessors: SimpleLogRecordProcessor delivers synchronous, blocking exports suitable for local debugging and serverless runtimes, while BatchLogRecordProcessor provides asynchronous buffering and batching for high-throughput production workloads.
When in-process appenders cannot be deployed, the OpenTelemetry Collector enables non-invasive out-of-process log ingestion using the filelog receiver, parsing operators, and the k8sattributes processor, trading in-process context propagation fidelity for zero application modifications.
6.2 The Logging Bridge API & Log Appenders
Quick Answer: Developers do not need to rewrite existing log statements against the OpenTelemetry Logs API. They keep their preferred, idiomatic logging frameworks (e.g., Logback, Log4j2, Python
logging, Winston, Zap, Serilog). OpenTelemetry provides Log Appenders / Bridges that plug into these native frameworks as handlers. The appender automatically intercepts log events in-process, extracts the activeTraceIdandSpanIdfrom ambient context, transforms the event into an OpenTelemetry Log Record, and forwards it to theLoggerProviderfor processing and OTLP export.
A frequent misconception is that adopting OpenTelemetry requires replacing all existing logger.info() or logger.error() calls with OpenTelemetry API methods. For distributed traces and metrics, developers directly invoke tracer.spanBuilder() or meter.createCounter(). For logs, however, OpenTelemetry enforces a completely different architectural philosophy.
The OpenTelemetry Logging Philosophy
Logging frameworks have evolved over more than twenty-five years. Modern logging libraries—such as Log4j2 and Logback in Java, Winston and Pino in Node.js, standard logging and Structlog in Python, Zap and Zerolog in Go, and Serilog in .NET—are extraordinarily mature. They offer sophisticated capabilities:
- Highly optimized memory allocation and zero-garbage serialization buffers.
- Granular hierarchical logger configuration and dynamic runtime level filtering.
- Rich formatting layouts, custom appenders, and asynchronous ring-buffer queues (e.g., LMAX Disruptor in Log4j2).
- Extensive community adoption and millions of existing lines of code.
Requiring developers to abandon these battle-tested frameworks to learn a new, generic logging API would impose massive migration friction for zero technical benefit.
The Role of the OpenTelemetry Log API
KEY CONCEPT: The stable Logs API specification (the API was previously called the Logs Bridge API) states two things:
- Its primary audience is logging library authors and appender implementers, who use it to bridge existing logging libraries into the OpenTelemetry log data model.
- It can also be called directly by instrumentation libraries, instrumented libraries, and applications, and a language may offer a more ergonomic API for that purpose.
In practice this gives a simple rule. Keep ordinary application logging in your existing framework and add an appender, because that is what gives you trace correlation without code churn. Call the Logs API directly when you are writing an appender or an instrumentation library, or when you need to emit a structured event (a log record with an EventName).
How the OpenTelemetry Logging Bridge Works
OpenTelemetry bridges the gap between idiomatic application logging and the OpenTelemetry telemetry pipeline through Log Appenders (also referred to as Log Bridges or Log Handlers).
An OpenTelemetry Log Appender is a native plugin that implements the appender/handler interface of the underlying logging framework:
- Java:
OpenTelemetryAppenderfor Logback (ch.qos.logback.core.Appender) or Log4j2 (org.apache.logging.log4j.core.Appender). - Python:
LoggingHandler(logging.Handler) registered with Python's standardloggingroot logger. - Node.js: Custom transports for Winston (
winston-transport) or stream destinations for Pino. - Go: Custom core for Uber Zap (
zapcore.Core) or handler for Go standard libraryslog(slog.Handler). - .NET:
OpenTelemetryLoggerProviderimplementingMicrosoft.Extensions.Logging.ILoggerProvider.
In-Process Execution Flow
When an application logs a message during execution, the bridge operates entirely in-process through a deterministic five-step lifecycle:
Application Code -> logger.info("Order processed", kv("order_id", 1024))
│
▼
Native Logging Framework (Logback / Log4j2 / Winston / Python logging)
│
▼
OpenTelemetry Log Appender (Plugin / Handler)
├── 1. Intercepts native log event
├── 2. Reads ambient in-process context (ThreadLocal / ContextVars)
│ └── Extracts active TraceId, SpanId, and TraceFlags
├── 3. Maps native level to OpenTelemetry SeverityNumber & SeverityText
├── 4. Extracts message body, parameters, and MDC / structured keys into Attributes
└── 5. Constructs OpenTelemetry LogRecord
│
▼
OpenTelemetry LoggerProvider (SDK Pipeline Factory)
│
▼
LogRecordProcessor (SimpleLogRecordProcessor or BatchLogRecordProcessor)
│
▼
LogRecordExporter (OTLP Exporter over gRPC or HTTP/Protobuf)
Step-by-Step Breakdown
- Interception: The application executes standard logging code (e.g.,
logger.error("Payment gateway timeout", ex)). The native logging framework evaluates level filters and dispatches the event to all configured appenders, including the OpenTelemetry appender. - Context Extraction: The OpenTelemetry appender accesses the ambient in-process context storage (such as
ThreadLocalin Java,ContextVarsin Python,AsyncLocalStoragein Node.js, orcontext.Contextin Go). It checks whether an active OpenTelemetry distributed span exists. If present, it extracts the currentTraceId,SpanId, andTraceFlags. - Severity Normalization: The appender converts the framework's native log level into the standardized OpenTelemetry
SeverityNumber(e.g., LogbackLevel.ERROR->SeverityNumber.ERROR = 17) and stores the original level name inSeverityText("ERROR"). - Attribute & Payload Mapping: The appender maps structured MDC (Mapped Diagnostic Context), thread names, logger category names, and method arguments into OpenTelemetry
Attributes. The primary log message or template is assigned toBody. - SDK Dispatch: The appender obtains a
Loggerinstance from the globalLoggerProviderand callsemit(logRecord). The log record now enters the standard OpenTelemetry SDK processing pipeline.
The LoggerProvider and LogRecordProcessors
The LoggerProvider is the central factory interface in the OpenTelemetry SDK for managing the logging pipeline. Similar to TracerProvider for traces and MeterProvider for metrics, the LoggerProvider:
- Associates common
Resourcemetadata (service.name,service.version,deployment.environment.name) with all emitted log records. - Manages one or more registered
LogRecordProcessorimplementations. - Exposes lifecycle control methods (
forceFlush(),shutdown()).
LogRecordProcessor Implementations
When a log record is submitted to the LoggerProvider, it passes sequentially through registered LogRecordProcessor instances. The OpenTelemetry SDK provides two standard processors with distinct operational profiles:
1. SimpleLogRecordProcessor
The SimpleLogRecordProcessor processes and exports each log record synchronously on the caller's thread the moment it is emitted:
- When
logger.info()is invoked, the application execution thread blocks until the configuredLogRecordExporterfinishes transmitting the record over the network or writing it to the console. - Use Cases: Local debugging, unit and integration tests, command-line utilities, and serverless environments (e.g., AWS Lambda, Google Cloud Run).
- Serverless Significance: In serverless runtimes, the cloud provider freezes execution threads immediately after the function handler returns. Background worker threads cannot reliably export buffered telemetry. Using
SimpleLogRecordProcessor(or callingforceFlush()before function exit) guarantees that logs are dispatched before the runtime freezes. - Production Warning:
SimpleLogRecordProcessoris an anti-pattern for high-throughput production microservices. Blocking application request threads on synchronous network I/O dramatically inflates request latency and degrades throughput.
2. BatchLogRecordProcessor
The BatchLogRecordProcessor is the standard processor for high-throughput production workloads:
- It decouples application execution from telemetry export by buffering incoming log records in an internal, bounded in-memory queue.
- A dedicated background worker thread periodically drains the queue and dispatches batches of log records to the configured exporter.
- Configurable Parameters:
- Maximum queue size (
OTEL_BLRP_MAX_QUEUE_SIZE, default 2048 records). If the queue fills due to downstream backpressure, new records are dropped to prevent application out-of-memory (OOM) crashes. - Schedule delay (
OTEL_BLRP_SCHEDULE_DELAY, default 1000 ms): the interval between export attempts. Note that this is shorter than the span processor's 5000 ms default. - Maximum export batch size (
OTEL_BLRP_MAX_EXPORT_BATCH_SIZE, default 512 records; must not exceed the queue size). - Export timeout (
OTEL_BLRP_EXPORT_TIMEOUT, default 30000 ms).
- Maximum queue size (
Ingesting Logs via the OpenTelemetry Collector
While deploying in-process OpenTelemetry log appenders is the recommended approach for modern services, organizations frequently operate legacy commercial off-the-shelf (COTS) applications, pre-compiled third-party containers, or environments where modifying application code or dependencies is prohibited.
In these environments, OpenTelemetry provides out-of-process log ingestion through the OpenTelemetry Collector:
filelogReceiver — Deployed as an agent or DaemonSet on the host or Kubernetes node, thefilelogreceiver tails container log files (e.g.,/var/log/pods/*/*/*.log), handles log rotation, and persists its read offsets across restarts when a storage extension such asfile_storageis configured.- Parsing Operators — The receiver applies an internal pipeline of parsing operators:
regex_parser/json_parser: Decodes structured JSON or extracts regex capture groups into distinct attributes.timestampparser: Extracts event time strings from the log body to populateTimestamp, while the Collector automatically populatesObservedTimestampwith the current arrival time.severity_parser: Translates textual level strings ("warning","err") into normalizedSeverityNumbervalues.
k8sattributesProcessor — Intercepts the raw log records, extracts the container ID or pod IP, queries the local Kubernetes API server, and automatically enriches the log record with rich metadata (k8s.pod.name,k8s.namespace.name,k8s.node.name,k8s.container.name).
Architectural Comparison: In-Process Appender vs. Out-of-Process Collector Scraping
Choosing between in-process log appenders and out-of-process Collector log scraping is a classic architectural trade-off:
| Architectural Dimension | In-Process Log Appender / Bridge | Out-of-Process Collector File Scraping |
|---|---|---|
| Distributed Context Correlation | Automatic & 100% reliable. Directly extracts TraceId, SpanId, and TraceFlags from in-memory ambient context (ThreadLocal). | Complex & indirect. Application must be configured to format trace IDs into text/JSON strings; Collector must use regex/JSON parsers to reconstruct correlation. |
| Application Performance Overhead | Moderate. Requires in-memory buffering and CPU cycles for serialization and OTLP network dispatch within the application process. | Zero application telemetry overhead. Application writes plain text to stdout/stderr; OS kernel buffers and independent Collector process handle all parsing. |
| Code & Dependency Modification | Requires adding the OpenTelemetry appender dependency to the application build and updating logging configuration (e.g., logback.xml). | Non-invasive. Zero application code, dependency, or configuration changes required; operates transparently on any container. |
| Structured Attribute Fidelity | Preserves native data types (numbers, booleans, nested maps, arrays) directly in AnyValue without serialization loss. | Must serialize attributes to text strings or JSON lines, requiring downstream deserialization and type inference. |
| Process Crash Resilience | In-flight logs buffered in application memory may be lost if the container process abruptly crashes or is killed (SIGKILL). | Highly resilient. Logs written to stdout are captured by the container engine and persisted to disk independently of application process survival. |
| Legacy & COTS Compatibility | Incompatible with closed-source, pre-compiled binaries or languages without OpenTelemetry SDK support. | Universal compatibility. Ingests logs from any binary, shell script, or legacy operating system daemon capable of writing to disk or stdout. |
| Resource Attribution Fidelity | Explicitly enriched with SDK Resource attributes defined at application initialization time. | Relies on the Collector's k8sattributes processor or host environment to discover and append entity metadata. |
A senior engineer reviews a pull request for a large Java microservice. The pull request replaces thousands of SLF4J calls (log.info, log.error) with direct calls to the OpenTelemetry Logs API, arguing that this is required for trace correlation. How should the reviewer respond?
Approve it, because OpenTelemetry only attaches TraceId and SpanId to records emitted through its own Logs API
Reject it and require the team to stop logging entirely and convert every statement into a span event
Request changes: keep SLF4J and add an OpenTelemetry Logback or Log4j2 appender, which provides the same correlation; the Logs API is mainly for appender authors, so the rewrite adds churn without benefit
Approve it only for ERROR and FATAL statements while INFO and DEBUG stay on SLF4J
A DevOps team is deploying an event-driven serverless application on AWS Lambda instrumented with the OpenTelemetry SDK. The Lambda functions handle low-frequency webhooks, executing in under 150 milliseconds before the cloud runtime freezes execution. During initial testing, the team notices that log records emitted immediately before the Lambda handler returns are consistently lost or delayed until subsequent invocations. Which configuration adjustment directly eliminates this telemetry loss in serverless runtimes?
Increase the BatchLogRecordProcessor max_queue_size to 50,000 to prevent buffer overflow during execution spikes
Switch the export protocol from OTLP/gRPC to uncompressed OTLP/JSON over plain HTTP/1.0
Replace the in-process appender with an asynchronous local disk file writer that flushes to ephemeral container storage
Configure the LoggerProvider with a SimpleLogRecordProcessor or explicitly invoke forceFlush() before the serverless handler terminates
An architecture team is comparing two strategies for capturing container logs in a Kubernetes cluster: (Strategy 1) embedding the OpenTelemetry Logback Appender within each container runtime, versus (Strategy 2) deploying the OpenTelemetry Collector as a DaemonSet using the filelog receiver to scrape container stdout log files from the node filesystem. Which statement accurately describes a major operational difference between these two approaches?
Strategy 1 automatically injects the active in-process TraceId and SpanId into every log record without requiring custom log formatting, whereas Strategy 2 requires the application to explicitly serialize trace identifiers into the log string and relies on regex/JSON parsing in the Collector to reconstruct correlation
Strategy 1 requires zero application dependencies or configuration changes, whereas Strategy 2 requires modifying container source code to integrate the Kubernetes downward API
Strategy 1 prevents memory exhaustion during downstream network outages, whereas Strategy 2 causes container process crashes if the node collector runs out of memory
Strategy 1 completely bypasses the OpenTelemetry LoggerProvider, whereas Strategy 2 requires every container to run an embedded OpenTelemetry SDK
Sections you finish are checked off in the contents.