DEV Community

Cover image for Stable OpenTelemetry logs: should you migrate your log stack?
Span Observer
Span Observer

Posted on Originally published at span.observer

Stable OpenTelemetry logs: should you migrate your log stack?

Do stable OpenTelemetry logs change anything for your current log stack? According to the OpenTelemetry specification status page, the Logs Bridge API, the SDK and the OTLP protocol are all marked « Stable ». For an SRE or platform team, the question is no longer « is it ready? » but « what do we migrate, and in which order? ».

This article gives a decision grid: what « stable » guarantees, what it does not, three migration strategies, and how to check the result. It builds on the tutorial about log and trace correlation with trace_id, which covers the hands-on steps. Here the goal is to decide. The Dynatrace points apply to Dynatrace SaaS; for Dynatrace Managed, check the documentation of your version.

In brief

  • The Bridge API, the SDK and OTLP for logs are « Stable »; the Collector follows the protocol status (OpenTelemetry status page).
  • Maturity varies by language: Java, .NET, C++, PHP and Rust are « Stable », Go is a « Release candidate », Python and JavaScript are still « Development ».
  • Nothing forces a full replacement: OpenTelemetry aims to work with the logs you already produce.
  • The best first step is the smallest one: keep the current logging library and add an OpenTelemetry appender or a file receiver.

Stable OpenTelemetry logs: what « stable » means

The Specification Status Summary rates each signal by component. In the Logging section, the Bridge API, the SDK and the protocol are « Stable ». The data model is released with OTLP, and the logs data model page also shows it as « Stable ». The status page adds that the Collector status matches the protocol status for each signal.

Table from the Logging section of the OpenTelemetry status page: Bridge API, SDK and OTLP protocol Stable, Collector aligned with the protocol

In practice, a stable specification status means the contract of the model and protocol should no longer break. A log record carries, among others, Timestamp, TraceId, SpanId, SeverityText, SeverityNumber, Body, Resource and Attributes. Severity is numeric: 1 to 4 for TRACE, 9 to 12 for INFO, 17 to 20 for ERROR, 21 to 24 for FATAL.

Note that the specification is not the implementation. A stable specification says nothing about the maturity of the SDK in your language, or about semantic conventions.

What stable does not guarantee

The language changes everything

The Language APIs & SDKs page gives a status per language and per signal. For logs, the table is uneven.

OpenTelemetry documentation table: traces, metrics and logs status per language, logs Stable in Java, .NET, C++, PHP and Rust, Release candidate in Go, Development in Python and JavaScript

If your services run on Java or .NET, the SDK path is mature. If they run on Python or JavaScript, the logs signal is still « Development » at the time that page was read: the API may still change. That does not forbid using it, but it means pinning versions and reading release notes on each upgrade. The correlation tutorial uses Python: a good testing ground, not a foundation to generalise without safeguards.

Semantic conventions

The same page warns that instrumentation relying on experimental semantic conventions may face breaking changes, whatever the API or SDK status. In practice, an attribute name can change between two versions. Queries, alerts and dashboards should go through a normalisation layer rather than rely on raw names everywhere.

The file receiver

Many migrations go through a Collector that reads files. The README of the filelog receiver lists a « beta » level for logs. That is separate from the protocol status: the protocol is stable, this specific component is not yet.

Three migration strategies

Strategy 1: change nothing, add a bridge

The OpenTelemetry logs documentation describes the principle: keep using your logging library and configure it with an appender (or bridge) that emits to an exporter. The Bridge API targets library authors; applications do not need to call it directly. Benefit: application code does not change. Risk: maturity depends on the language (see above).

The concepts page also notes that once auto-instrumentation or an SDK is active, OpenTelemetry correlates logs with the active trace and span. This is the most concrete gain: the trace_id arrives without matching by timestamp.

Strategy 2: keep the files, read them with the Collector

If your applications write JSON logs to files or standard output, the Collector can read them without touching the code. The excerpt below comes from the receiver README: it reads JSON files and extracts the timestamp.

YAML excerpt from the filelog receiver README: include on /var/log/myservice/\*.json and a json\_parser operator with timestamp parse\_from attributes.time

receivers:
  file_log:
    include: [ /var/log/myservice/*.json ]
    operators:
      - type: json_parser
        timestamp:
          parse_from: attributes.time
          layout: '%Y-%m-%d %H:%M:%S'
Enter fullscreen mode Exit fullscreen mode

Details to know, from the README: include is required, start_at defaults to end (a file that is no longer written is therefore not read), and without a storage extension (storage) read offsets do not survive a restart. That last point matters for reliability: restarting the Collector without storage can cause gaps or duplicates. The receiver name varies across versions (filelog or file_log): follow the documentation of the version you deploy.

Strategy 3: full migration to OTLP

Every service emits OTLP, the Collector centralises, and the old log agent disappears. It is the cleanest target and also the riskiest: it combines code, pipeline and destination changes. It makes sense when traces and metrics already use OpenTelemetry, as described in the OpenTelemetry Collector to Dynatrace guide, and running duplicate agents costs more than the migration.

What happens at the destination: the Dynatrace example

In Dynatrace SaaS, the OTLP log ingestion documentation describes how an OTLP record becomes a Dynatrace record: Body feeds content, severity feeds loglevel (SeverityText first, then SeverityNumber, otherwise NONE), and TraceId and SpanId become trace_id and span_id in hexadecimal. Resource, instrumentation scope and log attributes are merged. A timestamp more than ten minutes in the future is replaced by the current time, and events older than the age limit are discarded (see the OTLP logs ingestion page).

The practical consequence: what you put in SeverityText and Body directly determines the level and content you will see. To process, route or mask afterwards, see OpenPipeline pipelines. If you currently use a third-party agent, Fluent Bit to Dynatrace on Kubernetes remains a valid path: « stable » forces nobody to migrate.

How to decide: a five-question grid

  1. Which languages? Mostly Java, .NET, PHP or C++: the SDK path is mature. Python or JavaScript: start with the Collector on files, or test in pre-production.
  2. Do you already have OpenTelemetry traces? If so, trace_id correlation alone justifies the log bridge.
  3. Are your logs structured? OpenTelemetry documentation prefers structured logs in production; unstructured logs are much harder to analyse at scale.
  4. What does one more agent cost? Two agents in parallel during the transition double the load and the risk of duplicates.
  5. What is the rollback plan? Migrating one service at a time, with a way back, protects your error budget.

How to check that it works

After each batch of migrated services, compare volumes before and after, then check correlation. In a Dynatrace SaaS Notebook:

fetch logs, from: now() - 30m
| filter isNotNull(trace_id)
| summarize logs = count(), by: {loglevel}
Enter fullscreen mode Exit fullscreen mode

If the count of logs with a trace_id is zero while the services are traced, trace context is not being passed: review the appender or the trace_parser operator. For per-level queries, see counting log errors by level in DQL.

Best practices

  • Migrate service by service, measuring MTTR on real incidents, not on a demo.
  • Pin SDK and Collector versions while the language is not « Stable ».
  • Enable offset storage for the file receiver.
  • Do not mix signals: a log is not a span or a metric. Each signal keeps its own pipeline.
  • Keep logs structured: it is the best insurance against format changes.

Frequently asked questions

Are OpenTelemetry logs stable in every language?

No. The languages page lists « Stable » for C++, .NET, Java, PHP and Rust, « Release candidate » for Go, and « Development » for Python, JavaScript, Ruby, Swift, Kotlin and Erlang/Elixir. Check that page before choosing the SDK path.

Should you drop your current log agent?

Not necessarily. OpenTelemetry states it aims to work with the logs you already produce. An appender or the Collector can be added to what exists, then the old agent is removed service by service if the benefit is proven.

What concrete benefit does migration bring?

The main one is native correlation between logs and traces through trace_id and span_id, then a single output format (OTLP) for several destinations. For how OpenTelemetry sits next to other tools, read OpenTelemetry and Prometheus, two complementary tools.

Conclusion

The stable status of OpenTelemetry logs removes the main underlying obstacle: the model and the protocol are fixed. It does not excuse you from checking each language and each Collector component. The sensible path is gradual: a bridge or a receiver on one pilot service, a DQL check, then wider rollout.

This blog is independent and not affiliated with Dynatrace LLC. The statuses quoted come from the official OpenTelemetry and Dynatrace pages read on 31 October 2026; they change, so verify them before any decision.


Originally published on span.observer. French version: https://span.observer/fr/logs-opentelemetry-stables-migrer/.

Top comments (0)