OpenTelemetry Log Integration

In the modern observability landscape, logs have traditionally existed in silos, often disconnected from the traces and metrics that provide context to system behavior. OpenTelemetry (OTel) changes this paradigm by treating logs as a first-class citizen, standing on equal footing with traces and metrics.

The goal of OpenTelemetry log integration is not to force developers to abandon their preferred logging libraries. Instead, it aims to wrap existing logging outputs into a unified data model and a standardized transport pipeline. By doing so, logs become enriched with resource attributes, severity levels, and, most importantly, trace context. This correlation allows engineers to jump from a specific trace span directly to the relevant log lines, seeing the trace_id, span_id, service name, and environment metadata all in one view.

Core Components of the OpenTelemetry Logging Model

To understand how integration works, we must first look at the fundamental building blocks that make up the OTel logging ecosystem.

  • LogRecord: This is the atomic unit of the logging model. Unlike a simple string of text, a LogRecord is a structured object containing a timestamp, observed time, severity level, the log body, and a collection of attributes. Crucially, it also includes TraceId and SpanId fields to enable seamless correlation.
  • Resource: This represents the entity producing the logs. It contains metadata that describes the source, such as service.name, service.version, and deployment.environment. By attaching these attributes to every log, you gain powerful dimensions for filtering and grouping data in your backend.
  • Context: This is the mechanism that propagates trace information. When a log is generated within the scope of an active span, the Context ensures that the current trace_id and span_id are automatically injected into the LogRecord.
  • Logs Bridge API: For most existing applications, rewriting all logging statements is impractical. The Bridge API acts as a translation layer, intercepting logs from established libraries (like Python's logging, Java's Log4j, or .NET's ILogger) and converting them into OTel-compliant LogRecords.
  • Logs SDK: Once logs are converted, the SDK handles the heavy lifting: processing, batching, and exporting. It is important to note that while the API is stable, the maturity of the SDK varies across different programming languages.
  • OTLP (OpenTelemetry Protocol): This is the standardized communication protocol used to transmit logs, metrics, and traces to a backend or a collector.
  • OpenTelemetry Collector: A vendor-agnostic proxy that receives, processes, and exports telemetry data. In many logging workflows, the Collector uses a filelog receiver to ingest logs from local files and an otlp exporter to forward them to a centralized storage system.

Strategic Integration Approaches

Depending on your application architecture and the level of control you have over the code, there are three primary ways to integrate logs with OpenTelemetry.

1. Application-Level Bridging

This is the most common approach for mature applications. You continue to use your existing, familiar logging framework, but you add a "bridge" or "handler" provided by the OpenTelemetry SDK. This handler intercepts log calls and transforms them into OTel records. This method is highly effective because it preserves your existing logging logic while gaining the benefits of structured data and trace correlation.

2. Collector-Based File Collection

If you are dealing with legacy applications that cannot be modified, or if you need to collect logs from infrastructure components (like Nginx or systemd), the Collector approach is ideal. The application simply writes logs to a file or stdout. The OpenTelemetry Collector, running as a sidecar or a daemon, uses the filelog receiver to tail these files, parse them (e.g., from JSON or regex), and export them via OTLP.

3. Direct Logs API Usage

For greenfield projects, you can bypass existing libraries entirely and call the OpenTelemetry Logs API directly. While this provides the most "pure" and unified implementation, it requires developers to learn a new API and necessitates checking the stability of the language-specific SDK before moving to production.

Implementation Example: Python Logging Bridge

The following example demonstrates how to bridge the standard Python logging module with OpenTelemetry. This setup ensures that every log message emitted by the standard logger is automatically enriched with the current trace context.

Prerequisites:

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc

Code Implementation:

import logging
from opentelemetry import trace
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, ConsoleLogExporter

# 1. Define the Resource metadata
resource = Resource.create({"service.name": "demo-service"})

# 2. Set up the Tracing Provider
trace.set_tracer_provider(TracerProvider(resource=resource))

# 3. Set up the Logging Provider and Processor
logger_provider = LoggerProvider(resource=resource)
set_logger_provider(logger_provider)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(ConsoleLogExporter())
)

# 4. Bridge Python's standard logging to OpenTelemetry
handler = LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
logging.basicConfig(level=logging.INFO, handlers=[handler])

logger = logging.getLogger(__name__)
tracer = trace.get_tracer(__name__)

# 5. Generate logs within a trace span
with tracer.start_as_current_span("handle-request"):
    # The log below will automatically include trace_id and span_id
    logger.info("Processing request", extra={"user.id": "u-123"})

In a production environment, you would replace ConsoleLogExporter with OTLPLogExporter to send your logs to an OpenTelemetry Collector or a managed observability backend.

Infrastructure-Level Integration: Collector File Collection

For scenarios where code changes are not an option, the OpenTelemetry Collector can be configured to scrape logs from the filesystem. This is particularly useful for parsing structured JSON logs produced by containerized applications.

Below is a conceptual configuration for an OpenTelemetry Collector using the filelog receiver:

receivers:
  filelog:
    include:
      - /var/log/app/*.log
    operators:
      - type: json_parser
        timestamp:
          parse_from: attributes.time
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'

processors:
  batch:

exporters:
  otlp:
    endpoint: "otel-collector:4317"
    tls:
      insecure: true

service:
  pipelines:
    logs:
      receivers: [filelog]
      processors: [batch]
      exporters: [otlp]

By utilizing this configuration, the Collector acts as an intelligent agent that transforms raw text files into structured, enriched telemetry, ensuring that even your oldest legacy systems contribute to your modern observability strategy.