Errors¶
The two things that can fail at construction¶
tracing.NewProvider, metrics.NewProvider and logs.NewProvider return an error from
exactly two places:
- Endpoint validation —
otelcore.ParseEndpointrejectedSettings.Endpoint. Wrapsotelcore.ErrInvalidEndpoint. - Exporter construction — the OTLP/HTTP exporter constructor failed. Wrapped with
creating OTLP trace exporter,creating OTLP metric exporterorcreating OTLP log exporter.
Everything else — an unreachable collector, a rejected batch, an authentication failure — happens later, asynchronously, and is not returned to you.
ErrInvalidEndpoint¶
Every validation failure wraps this sentinel, so a caller can separate a misconfiguration from a transport problem:
tp, err := tracing.NewProvider(ctx, res, settings)
if errors.Is(err, otelcore.ErrInvalidEndpoint) {
// The operator's configuration is wrong. Fail startup with a clear message.
}
Errors are created with github.com/cockroachdb/errors, and the specific reason is
carried as a hint rather than in the message. errors.GetAllHints(err) retrieves it;
%+v prints it. The endpoint itself is deliberately never included in the error text,
because a rejected endpoint may carry the credentials that got it rejected.
Every endpoint rejection¶
Checked in this order. The first failure returns.
| Input | Hint | Why |
|---|---|---|
"" |
OTLP endpoint must not be empty |
Only reachable by calling ParseEndpoint directly. NewProvider treats an empty Settings.Endpoint as the env-fallback path and never calls it. |
| Longer than 2048 bytes | OTLP endpoint is N bytes; max is 2048 |
otelcore.MaxEndpointLength. Real collector URLs are well under 200 bytes. |
Contains a byte below 0x20, or 0x7F |
OTLP endpoint contains ASCII control characters |
Blocks header and log injection through a newline or a carriage return in a configured value. |
Unparseable as a URL — https://host/%zz, https://[::1 |
(no hint) | The message is parsing OTLP endpoint URL: invalid OTLP endpoint, with no echo of the input, because a parse error can quote attacker-influenced text. Note that a newline never reaches here: the control-character check above catches it first. |
https://user:pass@host or https://user@host |
OTLP endpoint must not contain credentials; use headers instead |
Any userinfo is rejected, with or without a password. Put the credential in Settings.Headers. |
grpc://host:4317, ftp://host, unix://… |
OTLP endpoint scheme "grpc" is not supported; use http or https |
This module builds OTLP/HTTP exporters only. |
collector:4318 (no scheme) |
OTLP endpoint scheme "collector" is not supported; use http or https |
url.Parse reads a bare host:port as scheme collector with opaque 4318. Write https://collector:4318. |
//collector:4318 |
OTLP endpoint scheme "" is not supported; use http or https |
A scheme-relative URL has no scheme to allow. |
https:// (no host) |
OTLP endpoint must include a host |
Two inputs that look wrong and are accepted:
- A trailing slash.
https://collector:4318/is normalised — the path is trimmed before the per-signal suffix is appended, so the request goes to/v1/tracesand not//v1/traces, which strict collectors answer with a 404. - A base path.
https://collector:4318/otlpis a supported shape; the signal suffix is appended to it, giving/otlp/v1/traces.
When endpoint validation runs¶
Only when Settings.Endpoint is non-empty. On the env-fallback path — an empty
Endpoint — no validation happens here at all, and a malformed
OTEL_EXPORTER_OTLP_ENDPOINT is the SDK's problem rather than this module's. The
hardening described above protects values that came from your configuration, not
values that came from the environment.
Exporter-construction failures¶
The OTLP/HTTP exporter constructors are strict about one combination in particular:
a TLS configuration together with an insecure endpoint. Supplying
OTEL_EXPORTER_OTLP_CERTIFICATE while the endpoint resolves to plaintext — an http://
scheme, Settings.Insecure: true, or OTEL_EXPORTER_OTLP_INSECURE=true — fails at
construction:
This is the failure the module's exporter_error_test.go files exercise, and it needs
neither a network nor a collector to reproduce.
Malformed environment values are handled differently: an unparseable
OTEL_EXPORTER_OTLP_TIMEOUT, a header without an =, or an unreadable certificate file
is written to the OTel internal logger and the default is used. Construction still
succeeds. Nothing surfaces unless you have called otel.SetLogger with a logger that
records at that verbosity.
Failures that are never returned¶
NewProvider returning nil means the provider was built. It does not mean the
collector exists, is reachable, or will accept anything you send it. Exporting is
asynchronous, and these all fail after construction has succeeded:
| Failure | What you see |
|---|---|
| Collector unreachable, DNS failure, connection refused | Retried for up to 60 seconds, then the batch is dropped. Reported through the OTel global error handler. |
| HTTP 401/403 from a missing or wrong header | Same — retried where the status allows, then dropped |
A 404 from a wrong base path |
Same |
| Queue full because export is slower than production | Spans and records are dropped silently at enqueue |
To see any of it, install a handler before building providers:
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
slog.Error("otel export", "err", err)
}))
Without one, the SDK writes to its own logger and a service that cannot reach its collector looks exactly like a service with nothing to report. The missing-telemetry checklist works through it.