Skip to content

What this module does not do

Most of the questions this module attracts are about something it deliberately does not have. This page is the list, with the reasoning, so "no" is an answer you can find rather than infer from silence.

OTLP/gRPC is not supported

Only OTLP/HTTP is built. All three signal packages construct otlptracehttp, otlpmetrichttp and otlploghttp exporters, and otelcore.ParseEndpoint accepts only the http and https schemes. A grpc://collector:4317 endpoint is rejected at startup with ErrInvalidEndpoint, not silently downgraded.

Nor is it a matter of pointing at the gRPC port: https://collector:4317 parses, and then fails at export because the collector's gRPC receiver does not answer OTLP/HTTP.

The reason is dependency weight. Adding the gRPC exporters means every consumer of this module links google.golang.org/grpc, whether or not they use it, and the module's first invariant is that it carries the OpenTelemetry SDK and nothing else. A service that needs gRPC export can build the exporter itself and use otelcore.Resource and otelcore.ResolveSettings for the rest.

No exporter other than OTLP

There is no Prometheus exporter, no stdout exporter, no Jaeger exporter, and no way to swap in one of your own — NewProvider builds its exporter internally and returns only the finished provider. Point OTLP at a collector and let the collector fan out; that is what collectors are for.

If you want Prometheus pull metrics rather than OTLP push, that is a different module: go/transport-metrics covers the /metrics endpoint family.

No automatic instrumentation

Building a provider produces zero telemetry on its own. This module wires up the export side and stops there — it creates no spans, records no metrics, and installs no middleware.

Getting data in is somebody else's job:

  • HTTP and gRPC server and client spans and metrics come from go.opentelemetry.io/contrib/instrumentation/… (otelhttp, otelgrpc).
  • Go runtime and process metrics come from go.opentelemetry.io/contrib/instrumentation/runtime.
  • Database, cache and business-operation instrumentation is hand-rolled against the metric.Meter you get from the provider.

None of those are dependencies here, and none is re-exported.

Nothing is registered globally for you

NewProvider returns a provider. It does not call otel.SetTracerProvider, otel.SetMeterProvider or global.SetLoggerProvider, and it does not install an error handler or a propagator.

That is deliberate — a library that mutates process-global state on construction cannot be used twice in one process, and cannot be tested in parallel. But it does mean instrumentation libraries reading the global provider see a no-op until you register one yourself.

Trace context propagation is a particular gap to be aware of: without otel.SetTextMapPropagator, the SDK's default propagator is a no-op, so trace context never crosses a service boundary and every service starts its own trace.

Settings.Enabled does nothing by itself

ResolveSettings computes it, Settings carries it, and no code in this module reads it. NewProvider builds a working provider — complete with a background export goroutine — for a signal whose Enabled is false.

Honouring it is the caller's job: check the field and skip the constructor. There is no "disabled provider" to build instead, and OTEL_SDK_DISABLED is not honoured either.

Sampling and export interval cannot be changed by an operator

Two settings are fixed at compile time in practice, because this module always passes the corresponding SDK option and an option always beats an environment variable:

  • OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG are overridden by tracing.WithSampling, which defaults to 0.1.
  • OTEL_METRIC_EXPORT_INTERVAL is overridden by metrics.WithInterval, which defaults to 60 seconds.

An operator who sets either variable sees no change, and gets no warning. If those need to be operator-tunable in your service, read them into your own configuration and pass them to the options. Full detail in Environment variables.

Only three exporter settings are configurable through Settings

Settings carries an endpoint, headers and an insecure flag. The OTLP/HTTP exporters accept considerably more, and this module exposes none of it:

Not exposed Reachable instead through
Compression OTEL_EXPORTER_OTLP_COMPRESSION
Request timeout OTEL_EXPORTER_OTLP_TIMEOUT
Custom CA, client certificates for mTLS OTEL_EXPORTER_OTLP_CERTIFICATE, …_CLIENT_CERTIFICATE, …_CLIENT_KEY
Proxy HTTPS_PROXY / HTTP_PROXY / NO_PROXY
Retry policy nothing — the SDK default is fixed
Max request size nothing — the SDK default is fixed
A custom *http.Client nothing

So a service needing mTLS to its collector can have it, but only as an environment-level decision, never as a value in its own configuration file.

An https:// endpoint does not guarantee TLS

ParseEndpoint validates the scheme, and the environment can then undo it. Both OTEL_EXPORTER_OTLP_INSECURE=true and a stale OTEL_EXPORTER_OTLP_ENDPOINT=http://… downgrade a configured https:// endpoint to plaintext, because the SDK applies environment configuration before this module's options and this module never asserts "secure".

The environment-variable reference has the full truth table. Treat it as a real constraint when threat-modelling: endpoint validation is a configuration-hygiene measure, not a transport guarantee.

Configured headers replace environment headers, they do not merge

A single entry in telemetry.headers discards everything in OTEL_EXPORTER_OTLP_HEADERS. There is no per-key merge, and no way to ask for one. If both need to contribute, combine them in your configuration layer before building Settings.

The resource carries two attributes and no detectors

otelcore.Resource(name, version) sets service.name and service.version, and runs no resource detectors. There is no telemetry.sdk.*, no host.name, no service.instance.id, no container or cloud detection. OTEL_RESOURCE_ATTRIBUTES is merged underneath, so an operator can add what a backend needs — but nothing is discovered automatically, and OTEL_SERVICE_NAME is always beaten by the name you pass in code.

The slog bridge replaces your output and filters nothing

logs.Handler returns a handler that writes only to OTLP. A logger built from it alone stops writing to stderr, and this module ships no fan-out handler to keep both.

It also applies no severity floor. slog's familiar "info and above" default belongs to the text and JSON handlers, not to the OTel bridge — logger.Debug(…) is exported.

And correlation is not automatic in the way the phrase suggests: a record only carries trace_id and span_id if the call passed a context containing an active span, which means the …Context variants. logger.Info(…) inside a span produces an uncorrelated record. See Correlate logs with traces.

No lifecycle management

Nothing here registers a shutdown hook, traps a signal, or ties a provider to a lifecycle manager. Every provider's Shutdown is the caller's to call, and skipping it drops whatever is buffered. Wiring that into a service's startup and shutdown ordering is the consuming application's job.

No RED/USE instrumentation helpers — yet

There are no ready-made helpers for databases, caches, circuit breakers or queues, and no generic business-operation wrapper. A design exists for them, but nothing is implemented, and the metrics package hands you a metric.Meter and stops. Anything domain-shaped is hand-rolled today.

Deliberately not a configuration library

The module reads no files, no flags and no environment of its own. It publishes the telemetry.* key names and a merge function, and the caller supplies the loader. That is the point of the extraction rather than an omission — see Endpoint resolution and the config model.