Documentation
¶
Overview ¶
Package otelkit is an OpenTelemetry SDK bootstrap for Go.
otelkit stands up the OTEL trace/metric/log pipeline — providers, exporters, resource, propagators, and an ordered shutdown — from one explicit constructor, so an application points at a backend (HyperDX, Grafana, Honeycomb, Datadog, New Relic, a collector, or stdout) and gets correct, shutdown-safe telemetry without re-writing the usual ~150 lines of fiddly, failure-silent setup.
otelkit is a bootstrap, not an SDK: it never reimplements spans, meters, or the log data model — it wires the official go.opentelemetry.io/otel SDK. Writing log lines is a logger's job (see github.com/ubgo/logger, which consumes the LoggerProvider this package builds). Auto-instrumentation lives in the OTEL contrib ecosystem.
Design goals ¶
- Spec-compliant: honors the standard OTEL_* environment variables.
- Loud, not silent: opt-in boot self-test, connectivity probe, an error handler that surfaces export failures, and a dry-run mode — because the SDK's default failure mode is silence.
- Vendor presets as data: switch backends by changing one line.
- One handle, one ordered Shutdown; a real no-op on OTEL_SDK_DISABLED.
- Zero application dependencies.
Quick start ¶
tel, err := otelkit.Init(ctx,
otelkit.WithService("checkout", "1.4.2"),
otelkit.WithPreset(otelkit.PresetCollector("localhost:4318", otelkit.TransportHTTP)),
)
if err != nil {
log.Fatal(err)
}
tel.SetGlobal()
defer tel.Shutdown(context.Background())
Index ¶
- Variables
- func ProbeEndpoint(ctx context.Context, endpoint string, transport Transport, mode TLSMode) error
- func RegisterGRPC(span SpanExporterFactory, metric MetricExporterFactory, log LogExporterFactory)
- type Config
- type LogExporterFactory
- type MetricExporterFactory
- type Option
- func WithConfig(c Config) Option
- func WithDryRun() Option
- func WithEnvOverrides(enabled bool) Option
- func WithEnvironment(env string) Option
- func WithErrorHandler(fn func(error)) Option
- func WithMetricInterval(d time.Duration) Option
- func WithMetricTemporality(t Temporality) Option
- func WithPreset(p Preset) Option
- func WithProtocol(t Transport) Option
- func WithResourceAttrs(attrs ...attribute.KeyValue) Option
- func WithResourceDetectors(tokens string) Option
- func WithSampler(s Sampler) Option
- func WithSamplerRatio(ratio float64) Option
- func WithSelfTest() Option
- func WithService(name, version string) Option
- func WithTLS(mode TLSMode) Option
- type Preset
- func PresetCollector(endpoint string, transport Transport) Preset
- func PresetDatadog(apiKey, endpoint string) Preset
- func PresetGrafanaCloud(instanceID, token, endpoint string) Preset
- func PresetHoneycomb(apiKey, dataset, endpoint string) Preset
- func PresetHyperDX(apiKey, endpoint string) Preset
- func PresetNewRelic(licenseKey, endpoint string) Preset
- func PresetStdout() Preset
- type Sampler
- type Signal
- type SignalConfig
- type SpanExporterFactory
- type TLSMode
- type Telemetry
- func (t *Telemetry) Disabled() bool
- func (t *Telemetry) ForceFlush(ctx context.Context) error
- func (t *Telemetry) LoggerProvider() otellog.LoggerProvider
- func (t *Telemetry) MeterProvider() otelmetric.MeterProvider
- func (t *Telemetry) RunOnSignal(ctx context.Context) error
- func (t *Telemetry) SelfTest(ctx context.Context) error
- func (t *Telemetry) SetGlobal()
- func (t *Telemetry) Shutdown(ctx context.Context) error
- func (t *Telemetry) Tracer(name string) oteltrace.Tracer
- func (t *Telemetry) TracerProvider() oteltrace.TracerProvider
- type Temporality
- type Transport
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMissingServiceName is returned when no service name is set via // WithService, the Config, or OTEL_SERVICE_NAME. otelkit still falls back // to "unknown_service:<binary>" for the resource per spec, but an // explicitly-required service name (validation mode) surfaces this. ErrMissingServiceName = errors.New("otelkit: service name is required") // ErrMissingEndpoint is returned when a network transport (HTTP/gRPC) is // selected for an enabled signal but no endpoint was provided. ErrMissingEndpoint = errors.New("otelkit: endpoint is required for http/grpc transport") // ErrInvalidProtocol is returned for an OTEL_EXPORTER_OTLP_PROTOCOL value // that is not one of grpc, http/protobuf, http/json. ErrInvalidProtocol = errors.New("otelkit: invalid OTLP protocol") // ErrContradictoryTLS is returned when the TLS mode contradicts the // supplied TLS material (e.g. plaintext with a client certificate). ErrContradictoryTLS = errors.New("otelkit: contradictory TLS configuration") // ErrGRPCNotLinked is returned when TransportGRPC is requested but no gRPC // exporter factory has been registered. Import the contrib/otelkit-grpc // module (which registers itself) to enable gRPC. ErrGRPCNotLinked = errors.New("otelkit: gRPC transport requires importing github.com/ubgo/otelkit/contrib/otelkit-grpc") )
Sentinel errors returned by Init and config validation. Callers can match them with errors.Is.
Functions ¶
func ProbeEndpoint ¶
ProbeEndpoint checks that the OTLP endpoint is reachable, turning the SDK's opaque "context deadline exceeded" / gRPC "Unavailable" failures into a specific, human-readable diagnosis at startup. It performs a TCP dial (and, for TLS modes, a TLS handshake) against the endpoint's host:port.
endpoint accepts the same forms as SignalConfig.Endpoint (host, host:port, or URL). mode selects whether a TLS handshake is attempted. A nil return means the first hop is reachable; it does not guarantee the backend will accept or store the data.
Example ¶
Probe an endpoint's reachability with a precise diagnosis.
package main
import (
"context"
"log"
"github.com/ubgo/otelkit"
)
func main() {
ctx := context.Background()
if err := otelkit.ProbeEndpoint(ctx, "localhost:4318", otelkit.TransportHTTP, otelkit.TLSModePlaintext); err != nil {
log.Printf("collector unreachable: %v", err)
}
}
Output:
func RegisterGRPC ¶
func RegisterGRPC(span SpanExporterFactory, metric MetricExporterFactory, log LogExporterFactory)
RegisterGRPC wires the OTLP/gRPC exporter factories. The contrib/otelkit-grpc module calls this from its package init, so applications enable gRPC simply by importing that module. Passing a nil factory leaves that signal's gRPC support unregistered.
Types ¶
type Config ¶
type Config struct {
// ServiceName populates the required service.name resource attribute.
// When empty, otelkit falls back to "unknown_service:<binary>".
ServiceName string
// ServiceVersion populates service.version (recommended).
ServiceVersion string
// Environment populates deployment.environment.name (optional).
Environment string
// Per-signal exporter configuration.
Traces SignalConfig
Metrics SignalConfig
Logs SignalConfig
// Sampler selects head sampling. Zero value = parentbased_always_on.
Sampler Sampler
// SamplerRatio is the [0,1] probability for ratio samplers. Default 1.0.
SamplerRatio float64
// MetricTemporality selects cumulative (default) or delta.
MetricTemporality Temporality
// MetricInterval is the periodic reader interval. 0 → 60s.
MetricInterval time.Duration
// TLS is the transport security mode for network exporters.
TLS TLSMode
// ResourceDetectors is a token list ("env,host,os,process,container",
// "all", or "none"). Empty → the default set (process, os, host).
ResourceDetectors string
// contains filtered or unexported fields
}
Config is the full bootstrap configuration. Build it directly, or start from a Preset and override fields with the With* options.
type LogExporterFactory ¶
type LogExporterFactory = func(ctx context.Context, sc SignalConfig, tlsMode TLSMode) (sdklog.Exporter, error)
LogExporterFactory builds an OTLP/gRPC log exporter.
type MetricExporterFactory ¶
type MetricExporterFactory = func(ctx context.Context, sc SignalConfig, tlsMode TLSMode, temp Temporality) (sdkmetric.Exporter, error)
MetricExporterFactory builds an OTLP/gRPC metric exporter.
type Option ¶
type Option func(*Config)
Option configures a Config before providers are built. Options are applied after any Preset and before the OTEL_* environment overlay (unless WithEnvOverrides(false) is set), giving the precedence: preset < options < env.
func WithConfig ¶
WithConfig replaces the working Config wholesale. Use it when you already have a fully-formed Config (e.g. mapped from PKL); later options still apply on top.
func WithDryRun ¶
func WithDryRun() Option
WithDryRun prints the resolved effective configuration (auth headers redacted) and routes telemetry to stdout instead of exporting. Use it to verify wiring without a backend.
Example ¶
Verify wiring with no backend.
package main
import (
"context"
"github.com/ubgo/otelkit"
)
func main() {
ctx := context.Background()
tel, _ := otelkit.Init(ctx,
otelkit.WithService("svc", "1"),
otelkit.WithPreset(otelkit.PresetGrafanaCloud("123456", "token", "https://otlp-gateway.grafana.net/otlp")),
otelkit.WithDryRun(),
otelkit.WithEnvOverrides(false),
)
defer tel.Shutdown(ctx)
}
Output:
func WithEnvOverrides ¶
WithEnvOverrides controls whether OTEL_* environment variables override programmatic/preset values. Default true (spec precedence). Set false to make options/PKL authoritative.
func WithEnvironment ¶
WithEnvironment sets deployment.environment.name (e.g. "prod", "staging").
func WithErrorHandler ¶
WithErrorHandler overrides the default stderr handler for telemetry export errors (the loud-by-default diagnostic). Pass your logger here to route export failures into your logging pipeline.
func WithMetricInterval ¶
WithMetricInterval sets the periodic metric reader interval.
func WithMetricTemporality ¶
func WithMetricTemporality(t Temporality) Option
WithMetricTemporality selects cumulative (default) or delta aggregation.
func WithPreset ¶
WithPreset applies a vendor Preset. Presets run before other options, so an explicit With* still overrides a preset value (precedence: preset < options < env). A nil preset is a no-op.
Example ¶
Switch backends in one line via a preset.
package main
import (
"context"
"log"
"github.com/ubgo/otelkit"
)
func main() {
ctx := context.Background()
tel, err := otelkit.Init(ctx,
otelkit.WithService("api", "2.0.0"),
otelkit.WithEnvironment("prod"),
otelkit.WithPreset(otelkit.PresetHyperDX("ingestion-key", "")),
)
if err != nil {
log.Fatal(err)
}
tel.SetGlobal()
defer tel.Shutdown(ctx)
}
Output:
func WithProtocol ¶
WithProtocol sets the transport for every enabled signal in one call, deriving the correct port and path. A convenience over setting each SignalConfig.Transport.
func WithResourceAttrs ¶
WithResourceAttrs appends extra resource attributes, merged over the detected resource.
func WithResourceDetectors ¶
WithResourceDetectors sets the detector token list ("env,host,os,process, container", "all", or "none").
func WithSampler ¶
WithSampler sets the head sampler. For ratio samplers, pair with WithSamplerRatio.
func WithSamplerRatio ¶
WithSamplerRatio sets the [0,1] probability used by the ratio samplers.
func WithSelfTest ¶
func WithSelfTest() Option
WithSelfTest enables the opt-in boot self-test: Init sends one span synchronously and surfaces any export error, so a misconfigured backend fails loudly at startup instead of silently dropping telemetry.
Example ¶
Catch a misconfigured backend at startup.
package main
import (
"context"
"log"
"github.com/ubgo/otelkit"
)
func main() {
ctx := context.Background()
tel, err := otelkit.Init(ctx,
otelkit.WithPreset(otelkit.PresetCollector("localhost:4318", otelkit.TransportHTTP)),
otelkit.WithSelfTest(),
)
if err != nil {
log.Printf("telemetry self-test failed: %v", err)
return
}
defer tel.Shutdown(ctx)
}
Output:
func WithService ¶
WithService sets service.name and service.version.
type Preset ¶
type Preset func(*Config)
Preset configures a Config for a specific backend, encoding that vendor's endpoint, transport, auth-header name/format, path quirk, and temporality as data — so switching backends is a one-line change. Apply with WithPreset.
func PresetCollector ¶
PresetCollector configures a generic OTLP collector with no auth — the vendor-neutral escape hatch. Choose the transport (HTTP or gRPC); gRPC requires importing contrib/otelkit-grpc.
func PresetDatadog ¶
PresetDatadog configures Datadog OTLP intake. Auth is the API key in "dd-api-key". Datadog's direct intake rejects cumulative metrics, so the preset forces delta temporality. A blank endpoint defaults to the intake host.
func PresetGrafanaCloud ¶
PresetGrafanaCloud configures Grafana Cloud OTLP. Auth is HTTP Basic with the base64 of "instanceID:token". The endpoint must be the "/otlp" base; otelkit appends "/v1/<signal>" automatically.
func PresetHoneycomb ¶
PresetHoneycomb configures Honeycomb ingest. Auth is the team/ingest key in "x-honeycomb-team"; metrics additionally require the dataset in "x-honeycomb-dataset". A blank endpoint defaults to the US gateway.
func PresetHyperDX ¶
PresetHyperDX configures HyperDX / ClickStack ingest. Auth is the raw ingestion key in the "authorization" header (no "Bearer" prefix). A blank endpoint defaults to the cloud gateway.
func PresetNewRelic ¶
PresetNewRelic configures New Relic OTLP. Auth is the ingest license key in "api-key"; New Relic strongly prefers delta temporality. A blank endpoint defaults to the US collector.
func PresetStdout ¶
func PresetStdout() Preset
PresetStdout routes all three signals to stdout — for local development.
type Sampler ¶
type Sampler int
Sampler selects the head sampling strategy. Tail sampling is a Collector concern and is intentionally out of scope.
const ( // SamplerParentBasedAlwaysOn samples every root span and respects the // parent's decision for child spans. This is the OTEL default and keeps // traces unbroken across services. SamplerParentBasedAlwaysOn Sampler = iota // SamplerAlwaysOn samples every span. SamplerAlwaysOn // SamplerAlwaysOff samples no spans. SamplerAlwaysOff // SamplerTraceIDRatio samples a fraction of root spans (see Config.SamplerRatio). SamplerTraceIDRatio // SamplerParentBasedTraceIDRatio is ratio sampling for roots, parent // decision for children. SamplerParentBasedTraceIDRatio )
type SignalConfig ¶
type SignalConfig struct {
// Enabled turns the signal on. A zero Config has all signals disabled;
// presets and With* options enable them.
Enabled bool
// Transport selects stdout / HTTP / gRPC.
Transport Transport
// Endpoint is a host, host:port, or URL. otelkit derives the correct port
// and OTLP path from it (see resolveEndpoint).
Endpoint string
// EndpointIsURL marks Endpoint as a complete, per-signal URL to be used
// verbatim — otelkit will NOT append /v1/<signal> to it. Mirrors the OTLP
// per-signal-endpoint rule.
EndpointIsURL bool
// Headers carries auth and routing headers (e.g. authorization, dataset).
Headers map[string]string
// BatchTimeout overrides the batch/schedule delay for this signal. 0 →
// the spec default.
BatchTimeout time.Duration
}
SignalConfig configures one signal's exporter. A disabled signal yields a no-op provider.
func (SignalConfig) GRPCTarget ¶
func (sc SignalConfig) GRPCTarget() (string, error)
GRPCTarget returns the bare host:port an OTLP/gRPC exporter should dial for this signal, applying the default OTLP gRPC port (4317) when none is present. It is exported for the contrib/otelkit-grpc module's exporter factories.
type SpanExporterFactory ¶
type SpanExporterFactory = func(ctx context.Context, sc SignalConfig, tlsMode TLSMode) (sdktrace.SpanExporter, error)
SpanExporterFactory builds an OTLP/gRPC span exporter. It is the seam the contrib/otelkit-grpc module registers; core never imports google.golang.org/grpc.
type TLSMode ¶
type TLSMode int
TLSMode is the transport security mode for OTLP exporters. The three modes are mutually exclusive, so a contradictory combination (e.g. plaintext with a client certificate) is rejected at construction rather than failing silently at export time.
const ( // TLSModeTLS uses TLS with full server-certificate verification (the // default for non-stdout transports). TLSModeTLS TLSMode = iota // TLSModePlaintext disables TLS entirely (cleartext). Use only for a // local collector on a trusted network. TLSModePlaintext // TLSModeSkipVerify uses TLS but skips server-certificate verification. // Insecure; for self-signed collectors in dev only. TLSModeSkipVerify )
type Telemetry ¶
type Telemetry struct {
// contains filtered or unexported fields
}
Telemetry is the single bootstrap handle. It owns the three providers and the propagator, exposes accessors, and drives a single ordered Shutdown.
func Init ¶
Init resolves configuration (preset < options < env), builds the enabled providers, and returns a handle. It performs no global registration; call SetGlobal to opt in. On OTEL_SDK_DISABLED=true it returns a fully no-op handle (never nil). When WithSelfTest is set it sends one span synchronously and returns the export error if the backend is unreachable.
Example ¶
Minimal: stdout in local dev.
package main
import (
"context"
"log"
"github.com/ubgo/otelkit"
)
func main() {
ctx := context.Background()
tel, err := otelkit.Init(ctx,
otelkit.WithService("checkout", "1.4.2"),
otelkit.WithPreset(otelkit.PresetStdout()),
otelkit.WithEnvOverrides(false),
)
if err != nil {
log.Fatal(err)
}
tel.SetGlobal()
defer tel.Shutdown(ctx)
}
Output:
func (*Telemetry) Disabled ¶
Disabled reports whether this handle is the no-op handle (OTEL_SDK_DISABLED).
func (*Telemetry) ForceFlush ¶
ForceFlush flushes any buffered telemetry across all providers.
func (*Telemetry) LoggerProvider ¶
func (t *Telemetry) LoggerProvider() otellog.LoggerProvider
LoggerProvider returns the log provider — the seam github.com/ubgo/logger's OTEL sink consumes.
func (*Telemetry) MeterProvider ¶
func (t *Telemetry) MeterProvider() otelmetric.MeterProvider
MeterProvider returns the metric provider (real or no-op).
func (*Telemetry) RunOnSignal ¶
RunOnSignal blocks until ctx is cancelled or one of SIGINT/SIGTERM arrives, then runs Shutdown and returns its error. It is the one-call graceful-shutdown helper for long-running services.
Shutdown runs on a fresh background context — not the (possibly already cancelled) ctx that ended the wait — so a cancelled app context cannot abort the flush. For a bounded shutdown deadline, call Shutdown(ctxWithTimeout) directly instead.
func (*Telemetry) SelfTest ¶
SelfTest sends one span synchronously and force-flushes it, surfacing the export error the async batch processor would otherwise hide. Used by WithSelfTest; safe to call directly.
func (*Telemetry) SetGlobal ¶
func (t *Telemetry) SetGlobal()
SetGlobal registers the providers and propagator on the OTEL globals.
func (*Telemetry) Shutdown ¶
Shutdown flushes and shuts down every provider in order — logs, then metrics, then traces — so a prior phase's errors still reach the collector before its provider closes. All providers are attempted regardless of individual failures; the errors are aggregated with errors.Join.
func (*Telemetry) TracerProvider ¶
func (t *Telemetry) TracerProvider() oteltrace.TracerProvider
TracerProvider returns the trace provider (real or no-op).
type Temporality ¶
type Temporality int
Temporality selects the metric aggregation temporality. Some backends (Datadog direct intake) reject cumulative and require delta; presets set this correctly per vendor.
const ( // TemporalityCumulative is the OTEL default for all instrument kinds. TemporalityCumulative Temporality = iota // TemporalityDelta reports deltas between collections. Required by // Datadog direct OTLP intake; preferred by New Relic and Uptrace. TemporalityDelta )
func (Temporality) String ¶
func (t Temporality) String() string
String returns the canonical temporality name.
type Transport ¶
type Transport int
Transport selects how a signal's OTLP exporter talks to the backend.
const ( // TransportStdout writes telemetry to stdout — for local dev and the // dry-run path. No network, no auth. TransportStdout Transport = iota // TransportHTTP is OTLP over HTTP (default OTLP port 4318). TransportHTTP // TransportGRPC is OTLP over gRPC (default OTLP port 4317). The gRPC // exporters ship in the contrib/otelkit-grpc module to keep core's // dependency graph free of google.golang.org/grpc. TransportGRPC )