OpenTelemetry Go Logs API Is Almost Stable

OpenTelemetry Go's Logs API and SDK reached release candidate, making structured logs a first-class signal for Go services.

OpenTelemetry Go has spent years with tracing and metrics as the obvious production signals while logs stayed in a more awkward state: essential in every incident, but less stable as a native OpenTelemetry API surface.

That changed in a meaningful way this week. On August 31, 2026, OpenTelemetry published the release-candidate announcement for the Go Logs API and SDK. The actual opentelemetry-go v1.47.0-rc.1 tag was cut on August 28, 2026, but the project is now explicitly asking users to validate the release candidate before the stable release. For Go teams that already depend on OpenTelemetry, this is the moment to test log instrumentation as part of the same telemetry contract as traces and metrics.

What Happened

OpenTelemetry announced that the Go Logs API and SDK are now in release candidate. The candidate moves the go.opentelemetry.io/otel/log and go.opentelemetry.io/otel/sdk/log modules from v0.22.0 beta to v1.47.0-rc.1.

The project also started a release-candidate feedback window. According to the announcement, if no critical issues are reported for at least 14 days after August 31, 2026, the project plans to publish stable v1.47.0 versions of those modules. If a critical problem appears, the team will cut another release candidate and restart the clock.

The GitHub release notes contain the concrete engineering changes behind the milestone. The RC promotes the Logs API and SDK packages to release-candidate status, adds global log functions to the root otel package, deprecates the older go.opentelemetry.io/otel/log/global package, drops support for Go 1.25, and changes default cardinality-limit behavior to match the specification. The release also tightens several SDK behaviors, including log deduplication controls, record limits, and event-name handling.

This is not a blanket stabilization of every log-related Go package. The OpenTelemetry announcement is careful about scope: instrumentations, exporters, and bridges remain experimental until they are validated against the stable API and SDK. That distinction matters. Stable APIs define what application code and SDK integrations can depend on. Exporters and bridges are still where adoption details may change.

Why It Matters

For Go service owners, stable OpenTelemetry logging is less about replacing a favorite logging library and more about making logs part of the same observability graph as spans, metrics, resources, and baggage.

Traces answer "what path did this request take?" Metrics answer "how often and how much?" Logs answer "what discrete facts did the system emit while it was happening?" In real incidents, engineers move across all three. The pain starts when those signals use incompatible metadata models, different resource identity, different correlation IDs, or separate exporter pipelines.

That problem is sharper in AI engineering. Agent systems, RAG pipelines, evaluation harnesses, tool routers, and long-running workflow engines generate event-heavy telemetry. A single user task may involve a model call, retrieval query, reranker, tool invocation, code execution step, sandbox decision, approval prompt, and final write. Not all of that belongs in spans. Many of those facts are better represented as structured log records with timestamps, severity, trace context, resource attributes, and domain attributes such as model name, tool name, run ID, dataset ID, or policy result.

OpenTelemetry has also been moving teams away from overloading span events. In March 2026, the project published guidance that span events are being replaced by log-based events. That is the architectural direction: spans should describe units of work, while discrete events should use logs. The Go Logs API reaching RC makes that guidance much easier to apply in Go backends.

There is a compatibility angle too. OpenTelemetry's Go trace and metric APIs already give library authors a fairly stable target. Logging has been harder. If the Logs API becomes stable after this RC cycle, Go libraries can emit structured log records through OpenTelemetry without forcing every application to wire bespoke adapters. Platform teams can also build processors that normalize, redact, drop, batch, or route log records before export.

This is exactly where observability becomes a software architecture decision rather than a vendor setting. The interesting boundary is not "which dashboard receives my logs?" It is "what is the shape of operational truth emitted by this service, and can I preserve it across environments?"

How It Works

The OpenTelemetry Logs API defines the application-facing contract. Code obtains a LoggerProvider, creates loggers, and emits log records. Records carry a body, severity, timestamp, observed timestamp, attributes, trace context, and optional event name. The API is intentionally smaller than most logging frameworks because OpenTelemetry is not trying to own formatting, console output, or developer ergonomics. It is defining an interoperable telemetry signal.

The SDK is the pipeline side. It is responsible for creating loggers, applying limits, invoking processors, and exporting records. The Logs SDK specification defines processor hooks such as Enabled, OnEmit, ForceFlush, and Shutdown, plus common processor shapes like simple and batch processing. It also defines limits for attributes and value lengths, which matter because logs are easy to abuse as unbounded payload carriers.

The RC release is important because it firms up this boundary in Go. Application code can target the API. Platform code can configure the SDK. Exporters can send records to OTLP or another backend. Processors can apply local policy before records leave the process.

The release notes call out several details that are easy to miss:

The root otel package now has global log functions, aligning logs with the existing global-provider pattern developers already use for traces and metrics. The separate log/global package is deprecated.

Cardinality limits now default to zero, matching the OpenTelemetry specification. That means unlimited by default unless configured. In production, that should be a prompt to set explicit limits rather than a reason to accept arbitrary attribute growth.

Duplicate attribute handling is now configurable through sdk/log. By default, duplicate keys are deduplicated and the last value wins. That behavior is reasonable for many services, but teams that rely on repeated attributes should validate it during the RC period.

Event name handling changed too. If a log record has an event.name attribute but no explicit event name, the SDK migrates the attribute into the record's event name. That aligns Go with the broader OpenTelemetry event model, but it is exactly the kind of change that can affect downstream queries if a backend expects event.name only as a normal attribute.

Go 1.25 support was removed in this release line. That is routine for many ecosystems, but observability libraries often sit deep in shared platform modules. If your service fleet has lagging Go runtimes, this is a dependency-management issue before it is a telemetry issue.

Example

The first useful test is not to spray every existing application log through OTLP. It is to pick one service boundary where structured events already matter: request admission, job execution, retrieval, tool invocation, or payment state transitions.

For an AI service, imagine an agent runtime emitting a structured event every time it calls a tool. A trace span can represent the tool call duration. A log event can capture the normalized decision facts: which tool was selected, whether approval was required, which policy matched, how many bytes came back, and whether output was redacted before the next model turn.

A platform team could wire a processor around the SDK batch processor to enforce basic normalization before export:

package telemetry

import (
	"context"
	"strings"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	sdklog "go.opentelemetry.io/otel/sdk/log"
)

type normalizingProcessor struct {
	next sdklog.Processor
}

func (p normalizingProcessor) Enabled(ctx context.Context, params sdklog.EnabledParameters) bool {
	return p.next.Enabled(ctx, params)
}

func (p normalizingProcessor) OnEmit(ctx context.Context, record *sdklog.Record) error {
	record.WalkAttributes(func(kv attribute.KeyValue) bool {
		key := strings.ToLower(string(kv.Key))
		if strings.Contains(key, "token") || strings.Contains(key, "authorization") {
			record.AddAttributes(attribute.String(string(kv.Key), "redacted"))
		}
		return true
	})

	return p.next.OnEmit(ctx, record)
}

func (p normalizingProcessor) Shutdown(ctx context.Context) error {
	return p.next.Shutdown(ctx)
}

func (p normalizingProcessor) ForceFlush(ctx context.Context) error {
	return p.next.ForceFlush(ctx)
}

func ConfigureLogs(exporter sdklog.Exporter) func(context.Context) error {
	batch := sdklog.NewBatchProcessor(exporter)
	provider := sdklog.NewLoggerProvider(
		sdklog.WithAttributeCountLimit(64),
		sdklog.WithAttributeValueLengthLimit(4096),
		sdklog.WithProcessor(normalizingProcessor{next: batch}),
	)

	otel.SetLoggerProvider(provider)
	return provider.Shutdown
}

This is intentionally small. The point is the boundary. Instrumented code emits records. The SDK pipeline owns limits and processing. The exporter owns delivery. That gives you a place to enforce telemetry policy without burying it in every business handler.

During RC validation, I would test five things before rolling this into a shared Go platform package.

First, validate attribute cardinality and value limits with production-like payloads. Unlimited defaults are convenient during development, but agent logs can accumulate high-cardinality values quickly: prompt IDs, run IDs, tool arguments, file paths, dataset names, and user-provided labels.

Second, check downstream query behavior for event.name. If dashboards, alerts, or processors currently treat it as an attribute, the SDK's migration behavior may require query updates.

Third, run bridges and exporters in a staging environment but treat them as experimental until they are explicitly stabilized. The API and SDK reaching RC does not make every surrounding package stable.

Fourth, measure allocation and batch behavior on hot paths. Structured logging is still logging. If you emit records for every token chunk or every retrieval candidate, you can create a telemetry system that becomes noisier than the application.

Fifth, verify shutdown paths. Batch processors are useful because they protect request latency, but they also require disciplined ForceFlush and Shutdown handling in short-lived jobs, CLIs, serverless functions, and evaluation workers.

My Take

The important part of this release is not that Go finally gets one more logging option. Go has plenty of logging options. The important part is that OpenTelemetry is getting closer to a stable, signal-level contract for logs in Go.

That should change how teams model observability. Logs should not be treated as strings that happen to be searchable. They should be structured events with a lifecycle: emitted by application or library code, enriched with resource and trace context, constrained by limits, processed by policy, batched, exported, and queried as part of the same system as spans and metrics.

For AI engineering teams, the timing is useful. Many agent platforms are now rediscovering old distributed-systems problems under new names: runaway fanout, hidden retries, queue stalls, tool failures, authorization decisions, sandbox escapes, and context pollution. Traces alone cannot carry all of that detail cleanly. Logs alone cannot reconstruct causality. A stable OpenTelemetry log signal in Go makes it easier to design the two together.

I would not rush to rewrite every logging path this week. The release candidate is a validation window, not a victory lap. But I would absolutely test it in one real service now. If the stable release lands after the 14-day window, the teams that already know their attribute schema, processor behavior, exporter maturity, and query impact will have a much easier migration.

Conclusion

OpenTelemetry Go Logs API and SDK reaching release candidate is a practical milestone for backend engineers. It gives Go services a near-stable path for emitting structured log records through the same telemetry architecture used for traces and metrics.

The work left is not glamorous: test exporters, check bridge behavior, set limits, validate queries, and measure overhead. That is exactly why the RC matters. Stable observability APIs are only useful when platform teams can rely on them before incidents force the issue.

Sources

Enjoying this article?

Subscribe to get new NestJS, Node.js, and backend engineering posts directly in your inbox.

Subscribe for free