otel

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 18 Imported by: 0

README

otel

OpenTelemetry SDK bootstrap for traces, metrics, and logs over OTLP/HTTP. Returns a *Provider that implements lifecycle.Resource so the SDK is shut down with the rest of the service — last in the LIFO chain so other components can finish flushing telemetry first.

Once Setup has run, the global TracerProvider, MeterProvider, and LoggerProvider are non-noop and discoverable by every other core/* package via their WithOtel() options.

When to use

Always, in production. For local dev or tests without a collector, set Config.Disabled = true — Setup returns a noop Provider and the same code path works. An empty OTelHost with Disabled=false is an error (ErrEmptyOTelHost) so a missing env var fails loudly instead of silently disabling observability.

Quickstart

package main

import (
    "context"
    "log"

    "github.com/sergeyslonimsky/core/app"
    "github.com/sergeyslonimsky/core/otel"
)

func main() {
    ctx := context.Background()

    p, err := otel.Setup(ctx, otel.Config{
        OTelHost:      "otel-collector:4318",
        ServiceName:   "myservice",
        ServiceVersion: "v1.2.3",
        K8SNamespace:  "production",
        K8SPodName:    os.Getenv("HOSTNAME"),
        EnableTracer:  true,
        EnableMetrics: true,
        EnableLogger:  true,
    })
    if err != nil {
        log.Fatal(err)
    }

    a := app.New()
    a.Add(p)               // FIRST → shuts down LAST → final telemetry flush
    // ... add other Resources and Runners
    log.Fatal(a.Run())
}

Configuration

type Config struct {
    Disabled       bool    // explicit opt-out: returns noop Provider when true

    OTelHost       string  // OTLP/HTTP endpoint (host:port). Required unless Disabled.
    ServiceName    string  // service.name resource attr (required when enabled)
    ServiceVersion string  // service.version
    HostName       string  // host.name
    K8SNamespace   string  // k8s.namespace.name
    K8SNodeName    string  // k8s.node.name
    K8SPodName     string  // k8s.pod.name

    EnableTracer   bool
    EnableMetrics  bool
    EnableLogger   bool
}

Options

  • WithMetricsSendInterval(d time.Duration) — exporter cadence. Default: 3s.
  • WithHistogramBuckets(boundaries []float64) — explicit histogram bucket boundaries (ms). Default: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000].

Lifecycle ordering — critical

OTel must be registered first with app.App so it shuts down last. Otherwise the SDK closes before other components finish emitting their final telemetry, and you lose the most interesting spans (the shutdown ones).

a.Add(otelProvider)  // ← first, shuts down LAST
a.Add(db)
a.Add(redisClient)
a.Add(httpServer)    // ← last, shuts down FIRST

Why opt-in (WithOtel) per package, not auto-discover?

Every other core/* package has an opt-in WithOtel() option that reads the global providers Setup installs. We chose explicit opt-in over auto-detect so:

  • A code reviewer sees exactly which servers/clients are instrumented.
  • Disabling instrumentation is a localised one-line change, not a global flag.
  • Tests can construct a server without inadvertently picking up real exporters via the global state.

The flow:

  1. otel.Setup runs first, sets globals.
  2. Each per-package WithOtel() reads otel.GetTracerProvider() / otel.GetMeterProvider() at construction time.
  3. Without Setup, the globals are noop and all WithOtel() calls become free.

Disabling explicitly (Disabled = true)

For local dev or tests without a collector:

p, err := otel.Setup(ctx, otel.Config{Disabled: true})
  • Setup logs "otel disabled via Config.Disabled" and returns a *Provider with no shutdown funcs.
  • All WithOtel() calls in other packages still work — they just attach noop providers.

If Disabled=false but OTelHost is empty, Setup returns ErrEmptyOTelHost. This is intentional: a missing OTEL_HOST env var should fail the boot, not silently disable observability.

Lifecycle

*Provider implements lifecycle.Resource. Shutdown invokes every registered SDK shutdown (trace, metrics, logger) in registration order and joins errors. Idempotent and concurrent-safe (guarded by sync.Once): repeat calls return the same cached error.

Testing

For tests that need to verify spans, run a Jaeger / OTLP collector in a container or use an in-memory exporter:

exp := tracetest.NewInMemoryExporter()
tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exp))
otel.SetTracerProvider(tp)
// ... run code under test, then exp.GetSpans() returns recorded spans

Skip core/otel.Setup entirely in tests — it creates real network exporters.

See also

Documentation

Overview

Package otel bootstraps the OpenTelemetry SDK for traces, metrics, and logs over OTLP/HTTP. It returns a *Provider that implements lifecycle.Resource so it can be registered with app.App and shut down last in the LIFO chain (allowing other components to flush telemetry before the exporters close).

Typical usage:

p, err := otel.Setup(ctx, otel.Config{
    OTelHost:      "otel-collector:4318",
    ServiceName:   "myservice",
    EnableTracer:  true,
    EnableMetrics: true,
})
if err != nil { log.Fatal(err) }

a := app.New()
a.Add(p)   // register first → shuts down last → final flush happens AFTER
           // db/redis/etc have stopped emitting.
a.Add(httpServer, grpcServer)

Once Setup has run, the global TracerProvider, MeterProvider, and LoggerProvider are non-noop and can be used by every other core/* package via WithOtel() options.

Index

Constants

This section is empty.

Variables

View Source
var ErrEmptyOTelHost = errors.New("otel: OTelHost is empty (set Disabled=true to opt out)")

ErrEmptyOTelHost is returned by Setup when Config.Disabled is false but Config.OTelHost is empty. Flip Disabled=true to opt out explicitly.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Disabled explicitly turns off OTel setup. When true, Setup returns a
	// noop Provider regardless of the other fields. Use for local
	// development / tests without a collector.
	Disabled bool

	// OTelHost is the OTLP/HTTP endpoint (host:port). Required when
	// Disabled is false.
	OTelHost string

	// ServiceName is the value of resource.attribute service.name.
	// Required when Disabled is false.
	ServiceName string

	// ServiceVersion populates service.version. Optional.
	ServiceVersion string

	// HostName populates host.name. Optional.
	HostName string

	// K8SNamespace populates k8s.namespace.name. Optional.
	K8SNamespace string

	// K8SNodeName populates k8s.node.name. Optional.
	K8SNodeName string

	// K8SPodName populates k8s.pod.name. Optional.
	K8SPodName string

	// EnableTracer turns on the trace provider with an OTLP/HTTP exporter.
	EnableTracer bool

	// EnableMetrics turns on the meter provider with an OTLP/HTTP exporter
	// and a 3s default send interval.
	EnableMetrics bool

	// EnableLogger turns on the logger provider with an OTLP/HTTP exporter.
	EnableLogger bool
}

Config controls which OpenTelemetry signals are enabled and what resource attributes describe the producing service. Plain fields, no struct tags — consumer apps map their viper keys to fields explicitly inside their own config.NewConfig().

To intentionally run without a collector (local dev, tests), set Disabled=true — Setup will return a noop Provider. An empty OTelHost without Disabled=true is treated as a misconfiguration and returns ErrEmptyOTelHost, so a missing environment variable fails loudly instead of silently disabling observability.

type Option added in v1.3.0

type Option func(*options)

Option configures Setup.

func WithHistogramBuckets added in v1.3.0

func WithHistogramBuckets(boundaries []float64) Option

WithHistogramBuckets overrides the explicit histogram bucket boundaries (in milliseconds). Default: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000].

Tune to match the latency distribution of your service. Buckets that are too coarse hide regressions; too fine wastes storage.

func WithMetricsSendInterval added in v1.3.0

func WithMetricsSendInterval(d time.Duration) Option

WithMetricsSendInterval overrides how often metrics are exported. Default: 3 seconds. Smaller values give finer resolution at the cost of more network traffic.

type Provider added in v1.3.0

type Provider struct {
	// contains filtered or unexported fields
}

Provider holds the SDK shutdown closures registered during Setup. Implements lifecycle.Resource so it integrates with app.App.

func Setup added in v1.3.0

func Setup(ctx context.Context, cfg Config, opts ...Option) (*Provider, error)

Setup bootstraps the configured OpenTelemetry signals and returns a Provider that owns them. Sets the global propagator (W3C TraceContext + Baggage) and the global TracerProvider / MeterProvider / LoggerProvider for all other otel-aware libraries to discover.

If cfg.Disabled is true, Setup returns a noop Provider. An empty cfg.OTelHost with Disabled=false is an error (ErrEmptyOTelHost) — this prevents silently running without observability because of a missing env var.

On failure, any partially-initialised SDK is shut down before returning.

func (*Provider) Shutdown added in v1.3.0

func (p *Provider) Shutdown(ctx context.Context) error

Shutdown invokes every registered SDK shutdown in registration order (trace → metrics → logger), joining errors. Implements lifecycle.Resource.

Idempotent and concurrent-safe: the shutdown runs exactly once; every subsequent call returns the same result (nil on clean shutdown, the joined error otherwise).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL