Skip to content

ubgo/otelkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ubgo/otelkit

Go Reference Go Report Card coverage License Go

The boring, correct, loud way to turn OpenTelemetry on in a Go service — one constructor, vendor presets, and failures you can actually see.

otelkit stands up the OpenTelemetry trace/metric/log pipeline — providers, exporters, resource, propagators, and an ordered shutdown — from one explicit constructor. Point it at a backend (HyperDX, Grafana Cloud, Honeycomb, Datadog, New Relic, an OTLP collector, or stdout) and get correct, shutdown-safe telemetry without re-writing the usual ~150 lines of fiddly, failure-silent setup. The core has zero application dependencies; OTLP/gRPC ships as an opt-in contrib/ module so your dependency graph stays lean.

It is a bootstrap, not an SDK: it wires the official go.opentelemetry.io/otel SDK rather than reimplementing it. Writing log lines is a logger's job — otelkit exposes the LoggerProvider that github.com/ubgo/logger (and any OTEL log bridge) consumes.

Why otelkit

OpenTelemetry's Go SDK ships excellent primitives and no opinionated bootstrap, and its dominant failure mode is silence: a wrong port (4317 vs 4318), wrong protocol, a missing /v1/<signal> path, an unflushed batch on exit, or a cumulative-vs-delta mismatch all fail without an error — producing empty dashboards and no clue why. otelkit fixes that:

  • Spec-compliant — honors the standard OTEL_* environment variables and defaults.
  • Vendor presets as data — switch backends in one line; the preset encodes the endpoint, auth header name/format, path quirk, and metric temporality.
  • Loud, not silent — an export-error handler, a connectivity probe, a dry-run mode, and an opt-in boot self-test turn silent misconfiguration into a specific startup error.
  • One knob, no footgunsotelkit owns all port + /v1/<signal> path construction.
  • One handle, one ordered Shutdown + a ready-made signal helper; a real no-op on OTEL_SDK_DISABLED.
  • Future-proof — delegates to the now-stable declarative config (otelconf) when OTEL_CONFIG_FILE is set.
  • Zero application dependencies.

Install

go get github.com/ubgo/otelkit

OTLP/gRPC support is an opt-in module (keeps google.golang.org/grpc out of the core):

go get github.com/ubgo/otelkit/contrib/otelkit-grpc

Quick start

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.WithEnvironment("prod"),
		otelkit.WithPreset(otelkit.PresetHyperDX("<ingestion-key>", "")),
	)
	if err != nil {
		log.Fatal(err)
	}
	tel.SetGlobal()
	defer tel.Shutdown(ctx)

	// ... run your service; create spans/metrics via the OTEL globals ...
}

For a long-running service, replace the defer with the signal helper:

go runServer()
if err := tel.RunOnSignal(ctx); err != nil { // blocks until SIGTERM/SIGINT, then flushes
	log.Printf("shutdown errors: %v", err)
}

Modules

otelkit is split so the core stays dependency-light. Add the gRPC module only if your backend needs OTLP/gRPC.

Module Import
core github.com/ubgo/otelkit
gRPC exporters github.com/ubgo/otelkit/contrib/otelkit-grpc

The core ships OTLP/HTTP + stdout exporters and depends only on go.opentelemetry.io/otel/*. The gRPC module adds OTLP/gRPC (pulling in google.golang.org/grpc) and self-registers on a blank import. Selecting TransportGRPC without it returns otelkit.ErrGRPCNotLinked — loud, not silent.

Vendor presets

Every observability backend wants OTLP data in a slightly different shape — a different endpoint, a different auth header name (there's no Bearer consensus), a path quirk, and sometimes delta-vs-cumulative metrics. Get one detail wrong and your data silently never arrives. A preset is a one-line constructor that fills all of that in correctly for a specific vendor — so pointing at HyperDX vs Grafana vs Datadog is a single line, not a research project.

Preset Auth header Notes
PresetStdout() Local dev; all signals to stdout.
PresetHyperDX(key, endpoint) authorization (raw key, no Bearer) Defaults to https://in-otel.hyperdx.io.
PresetGrafanaCloud(instanceID, token, endpoint) Authorization: Basic <b64> Endpoint is the /otlp base; /v1/<signal> is appended automatically.
PresetHoneycomb(key, dataset, endpoint) x-honeycomb-team Metrics additionally send x-honeycomb-dataset.
PresetDatadog(key, endpoint) dd-api-key Forces delta temporality (Datadog rejects cumulative).
PresetNewRelic(key, endpoint) api-key Prefers delta temporality.
PresetCollector(endpoint, transport) Generic OTLP, no auth. The vendor-neutral escape hatch.

Switching backend is a one-line change:

otelkit.WithPreset(otelkit.PresetGrafanaCloud("123456", "<token>", "https://otlp-gateway-prod-eu-west-2.grafana.net/otlp"))

Full matrix and the reasoning behind each quirk: docs/presets.md.

Diagnostics — see failures instead of empty dashboards

The OpenTelemetry SDK drops exports silently: a wrong key, endpoint, protocol, or TLS setting just loses data with no error, and you find out hours later from blank dashboards. otelkit gives you four ways to make those failures visible — at startup, not in production:

tel, err := otelkit.Init(ctx,
	otelkit.WithPreset(otelkit.PresetHoneycomb(key, "metrics", "")),
	otelkit.WithSelfTest(),                 // send one span synchronously; error if the backend is unreachable
	otelkit.WithErrorHandler(myLogger),     // route export failures into your logs (default: stderr)
)
  • WithSelfTest() — sends one span through the real pipeline at startup and returns the export error the async batcher would otherwise hide.
  • Connectivity probeotelkit.ProbeEndpoint(ctx, endpoint, transport, tlsMode) diagnoses DNS / port / protocol / TLS problems with a human-readable message.
  • WithDryRun() — prints the resolved effective config (auth headers redacted) and routes telemetry to stdout, so you can verify wiring with no backend.
  • Export-error handler — installed by default (stderr); override with WithErrorHandler.

More: docs/diagnostics.md.

gRPC

import (
	"github.com/ubgo/otelkit"
	_ "github.com/ubgo/otelkit/contrib/otelkit-grpc" // blank import enables gRPC
)

tel, _ := otelkit.Init(ctx,
	otelkit.WithPreset(otelkit.PresetCollector("localhost:4317", otelkit.TransportGRPC)),
)

Without the contrib import, selecting TransportGRPC returns otelkit.ErrGRPCNotLinked — loud, not silent.

Configuration sources

otelkit accepts config from three independent routes (precedence: preset < options < env):

  1. ProgrammaticWithService, WithPreset, WithProtocol, WithSampler, WithTLS, … (map your own config system, e.g. PKL, into these).
  2. OTEL_* environment variables — the full standard surface (protocol, endpoint, headers, timeout, sampler, propagators, temporality). Set WithEnvOverrides(false) to make programmatic values authoritative.
  3. Declarative config file — set OTEL_CONFIG_FILE and otelkit delegates to the stable otelconf loader (file wins; flat env is ignored except ${ENV} substitution).

Migrating from a hand-rolled bootstrap

If you have the familiar ~150-line bootstrap (build three providers, attach OTLP exporters, set globals, wire shutdown): replace it with one otelkit.Init(...) call plus the matching preset. otelkit adds gRPC, vendor presets, loud diagnostics, the full OTEL_* surface, a real no-op on OTEL_SDK_DISABLED, declarative-config delegation, and an ordered Shutdown that flushes all three signals (instead of returning on the first error). Full before/after in docs/migration.md.

Documentation

Guide Covers
Getting started Zero to correlated telemetry in three lines.
Configuration Options, the full OTEL_* env surface, precedence.
Presets Vendor presets and what each encodes.
Diagnostics Self-test, probe, dry-run, error handler.
Declarative config OTEL_CONFIG_FILE delegation.
Migration Replacing a hand-rolled bootstrap.
Architecture How it fits; the endpoint/path rules.
ADRs · Snippets · Coverage Decisions, copy-paste, test coverage.

API reference: pkg.go.dev/github.com/ubgo/otelkit.

Examples

Runnable programs in examples/ — each in its own directory (go run ./01-basic):

01-basic · 02-all-signals · 03-k8s-prestop · 04-presets · 05-self-test · 06-dry-run · 07-declarative · 08-grpc

Quality

Both modules are held at 100% line coverage with the race detector, gated in CI. See COVERAGE.md.

FAQ

Does otelkit replace the OpenTelemetry SDK? No — it's a bootstrap that wires the official SDK. You still create spans/metrics the normal OTEL way.

Does it write my logs? No. It builds the LoggerProvider; a logger (e.g. github.com/ubgo/logger) writes log lines through it and correlates them with traces.

Why is gRPC a separate module? To keep google.golang.org/grpc out of the core dependency graph for HTTP-only deployments. A blank import of contrib/otelkit-grpc enables it.

My backend isn't a preset. Use PresetCollector(endpoint, transport) (no auth) or set headers/endpoint via WithConfig / OTEL_EXPORTER_OTLP_*. Presets are a convenience, not a requirement.

Nothing reaches my backend and there's no error. That's exactly what otelkit fixes — add WithSelfTest() to fail at startup, or WithDryRun() to print the resolved config. See diagnostics.md.

How it compares

otelkit is the vendor-neutral, batteries-included bootstrap. The honest landscape (otelconf is the OTEL declarative-config standard — otelkit delegates to it, so they're complementary):

otelkit setupOTelSDK (docs snippet) otelconf Vendor distros (uptrace-go, …)
Vendor-neutral ❌ backend-locked
Vendor presets — 1-line backend swap only their own
Owns endpoint/port//v1/ path (no footguns) partial
Loud diagnostics (self-test · probe · dry-run)
Full OTEL_* env compliance partial partial
Declarative OTEL_CONFIG_FILE ✅ (delegates to otelconf) ✅ (it is this)
Real no-op on OTEL_SDK_DISABLED
Ordered Shutdown + SIGTERM helper partial partial partial
All three signals (traces/metrics/logs) varies
Versioned library, not a copy-paste snippet
Coverage 100% n/a varies

License

Apache-2.0. See LICENSE.

About

Production-grade OpenTelemetry bootstrap for Go: one constructor for traces, metrics & logs with vendor presets (HyperDX, Grafana, Honeycomb, Datadog, New Relic), loud diagnostics, OTLP/HTTP + gRPC, and a zero-dependency core. 100% tested.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages