{"id":51436584,"url":"https://github.com/ubgo/otelkit","last_synced_at":"2026-07-05T07:02:38.098Z","repository":{"id":366311030,"uuid":"1274354152","full_name":"ubgo/otelkit","owner":"ubgo","description":"Production-grade OpenTelemetry bootstrap for Go: one constructor for traces, metrics \u0026 logs with vendor presets (HyperDX, Grafana, Honeycomb, Datadog, New Relic), loud diagnostics, OTLP/HTTP + gRPC, and a zero-dependency core. 100% tested.","archived":false,"fork":false,"pushed_at":"2026-06-21T07:36:36.000Z","size":104,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-21T09:10:35.296Z","etag":null,"topics":["datadog","devops","distributed-tracing","go","golang","grafana","honeycomb","instrumentation","logging","metrics","monitoring","observability","opentelemetry","opentelemetry-go","otel","otlp","prometheus","sdk","telemetry","tracing"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/ubgo/otelkit","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ubgo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-19T12:30:20.000Z","updated_at":"2026-06-21T07:41:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ubgo/otelkit","commit_stats":null,"previous_names":["ubgo/otelkit"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ubgo/otelkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Fotelkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Fotelkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Fotelkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Fotelkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ubgo","download_url":"https://codeload.github.com/ubgo/otelkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Fotelkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35145900,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datadog","devops","distributed-tracing","go","golang","grafana","honeycomb","instrumentation","logging","metrics","monitoring","observability","opentelemetry","opentelemetry-go","otel","otlp","prometheus","sdk","telemetry","tracing"],"created_at":"2026-07-05T07:02:37.268Z","updated_at":"2026-07-05T07:02:38.092Z","avatar_url":"https://github.com/ubgo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ubgo/otelkit\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/ubgo/otelkit.svg)](https://pkg.go.dev/github.com/ubgo/otelkit) [![Go Report Card](https://goreportcard.com/badge/github.com/ubgo/otelkit)](https://goreportcard.com/report/github.com/ubgo/otelkit) ![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen) [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE) ![Go](https://img.shields.io/badge/go-1.25-00ADD8?logo=go)\n\n**The boring, correct, loud way to turn OpenTelemetry on in a Go service — one constructor, vendor presets, and failures you can actually see.**\n\n`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.\n\nIt 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`](https://github.com/ubgo/logger) (and any OTEL log bridge) consumes.\n\n## Why otelkit\n\nOpenTelemetry'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/\u003csignal\u003e` 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:\n\n- **Spec-compliant** — honors the standard `OTEL_*` environment variables and defaults.\n- **Vendor presets as data** — switch backends in one line; the preset encodes the endpoint, auth header name/format, path quirk, and metric temporality.\n- **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.\n- **One knob, no footguns** — `otelkit` owns all port + `/v1/\u003csignal\u003e` path construction.\n- **One handle, one ordered `Shutdown`** + a ready-made signal helper; a real no-op on `OTEL_SDK_DISABLED`.\n- **Future-proof** — delegates to the now-stable declarative config (`otelconf`) when `OTEL_CONFIG_FILE` is set.\n- **Zero application dependencies.**\n\n## Install\n\n```bash\ngo get github.com/ubgo/otelkit\n```\n\nOTLP/gRPC support is an opt-in module (keeps `google.golang.org/grpc` out of the core):\n\n```bash\ngo get github.com/ubgo/otelkit/contrib/otelkit-grpc\n```\n\n## Quick start\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/ubgo/otelkit\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\ttel, err := otelkit.Init(ctx,\n\t\totelkit.WithService(\"checkout\", \"1.4.2\"),\n\t\totelkit.WithEnvironment(\"prod\"),\n\t\totelkit.WithPreset(otelkit.PresetHyperDX(\"\u003cingestion-key\u003e\", \"\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttel.SetGlobal()\n\tdefer tel.Shutdown(ctx)\n\n\t// ... run your service; create spans/metrics via the OTEL globals ...\n}\n```\n\nFor a long-running service, replace the `defer` with the signal helper:\n\n```go\ngo runServer()\nif err := tel.RunOnSignal(ctx); err != nil { // blocks until SIGTERM/SIGINT, then flushes\n\tlog.Printf(\"shutdown errors: %v\", err)\n}\n```\n\n## Modules\n\notelkit is split so the core stays dependency-light. Add the gRPC module only if your backend needs OTLP/gRPC.\n\n| Module | Import |\n|---|---|\n| **core** | [`github.com/ubgo/otelkit`](https://pkg.go.dev/github.com/ubgo/otelkit) |\n| **gRPC exporters** | [`github.com/ubgo/otelkit/contrib/otelkit-grpc`](https://pkg.go.dev/github.com/ubgo/otelkit/contrib/otelkit-grpc) |\n\nThe **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.\n\n## Vendor presets\n\nEvery 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.\n\n| Preset | Auth header | Notes |\n|---|---|---|\n| `PresetStdout()` | — | Local dev; all signals to stdout. |\n| `PresetHyperDX(key, endpoint)` | `authorization` (raw key, no `Bearer`) | Defaults to `https://in-otel.hyperdx.io`. |\n| `PresetGrafanaCloud(instanceID, token, endpoint)` | `Authorization: Basic \u003cb64\u003e` | Endpoint is the `/otlp` base; `/v1/\u003csignal\u003e` is appended automatically. |\n| `PresetHoneycomb(key, dataset, endpoint)` | `x-honeycomb-team` | Metrics additionally send `x-honeycomb-dataset`. |\n| `PresetDatadog(key, endpoint)` | `dd-api-key` | **Forces delta temporality** (Datadog rejects cumulative). |\n| `PresetNewRelic(key, endpoint)` | `api-key` | Prefers delta temporality. |\n| `PresetCollector(endpoint, transport)` | — | Generic OTLP, no auth. The vendor-neutral escape hatch. |\n\nSwitching backend is a one-line change:\n\n```go\notelkit.WithPreset(otelkit.PresetGrafanaCloud(\"123456\", \"\u003ctoken\u003e\", \"https://otlp-gateway-prod-eu-west-2.grafana.net/otlp\"))\n```\n\nFull matrix and the reasoning behind each quirk: [docs/presets.md](./docs/presets.md).\n\n## Diagnostics — see failures instead of empty dashboards\n\nThe 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:\n\n```go\ntel, err := otelkit.Init(ctx,\n\totelkit.WithPreset(otelkit.PresetHoneycomb(key, \"metrics\", \"\")),\n\totelkit.WithSelfTest(),                 // send one span synchronously; error if the backend is unreachable\n\totelkit.WithErrorHandler(myLogger),     // route export failures into your logs (default: stderr)\n)\n```\n\n- **`WithSelfTest()`** — sends one span through the real pipeline at startup and returns the export error the async batcher would otherwise hide.\n- **Connectivity probe** — `otelkit.ProbeEndpoint(ctx, endpoint, transport, tlsMode)` diagnoses DNS / port / protocol / TLS problems with a human-readable message.\n- **`WithDryRun()`** — prints the resolved effective config (auth headers redacted) and routes telemetry to stdout, so you can verify wiring with no backend.\n- **Export-error handler** — installed by default (stderr); override with `WithErrorHandler`.\n\nMore: [docs/diagnostics.md](./docs/diagnostics.md).\n\n## gRPC\n\n```go\nimport (\n\t\"github.com/ubgo/otelkit\"\n\t_ \"github.com/ubgo/otelkit/contrib/otelkit-grpc\" // blank import enables gRPC\n)\n\ntel, _ := otelkit.Init(ctx,\n\totelkit.WithPreset(otelkit.PresetCollector(\"localhost:4317\", otelkit.TransportGRPC)),\n)\n```\n\nWithout the contrib import, selecting `TransportGRPC` returns `otelkit.ErrGRPCNotLinked` — loud, not silent.\n\n## Configuration sources\n\n`otelkit` accepts config from three independent routes (precedence: preset \u003c options \u003c env):\n\n1. **Programmatic** — `WithService`, `WithPreset`, `WithProtocol`, `WithSampler`, `WithTLS`, … (map your own config system, e.g. PKL, into these).\n2. **`OTEL_*` environment variables** — the full standard surface (protocol, endpoint, headers, timeout, sampler, propagators, temporality). Set `WithEnvOverrides(false)` to make programmatic values authoritative.\n3. **Declarative config file** — set `OTEL_CONFIG_FILE` and `otelkit` delegates to the stable [`otelconf`](https://pkg.go.dev/go.opentelemetry.io/contrib/otelconf) loader (file wins; flat env is ignored except `${ENV}` substitution).\n\n## Migrating from a hand-rolled bootstrap\n\nIf 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](./docs/migration.md).\n\n## Documentation\n\n| Guide | Covers |\n|---|---|\n| [Getting started](./docs/getting-started.md) | Zero to correlated telemetry in three lines. |\n| [Configuration](./docs/configuration.md) | Options, the full `OTEL_*` env surface, precedence. |\n| [Presets](./docs/presets.md) | Vendor presets and what each encodes. |\n| [Diagnostics](./docs/diagnostics.md) | Self-test, probe, dry-run, error handler. |\n| [Declarative config](./docs/declarative-config.md) | `OTEL_CONFIG_FILE` delegation. |\n| [Migration](./docs/migration.md) | Replacing a hand-rolled bootstrap. |\n| [Architecture](./docs/architecture.md) | How it fits; the endpoint/path rules. |\n| [ADRs](./docs/adr) · [Snippets](./docs/snippets) · [Coverage](./COVERAGE.md) | Decisions, copy-paste, test coverage. |\n\nAPI reference: [pkg.go.dev/github.com/ubgo/otelkit](https://pkg.go.dev/github.com/ubgo/otelkit).\n\n## Examples\n\nRunnable programs in [`examples/`](./examples) — each in its own directory (`go run ./01-basic`):\n\n[`01-basic`](./examples/01-basic) · [`02-all-signals`](./examples/02-all-signals) · [`03-k8s-prestop`](./examples/03-k8s-prestop) · [`04-presets`](./examples/04-presets) · [`05-self-test`](./examples/05-self-test) · [`06-dry-run`](./examples/06-dry-run) · [`07-declarative`](./examples/07-declarative) · [`08-grpc`](./examples/08-grpc)\n\n## Quality\n\nBoth modules are held at **100% line coverage** with the race detector, gated in CI. See [COVERAGE.md](./COVERAGE.md).\n\n## FAQ\n\n**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.\n\n**Does it write my logs?** No. It builds the `LoggerProvider`; a logger (e.g. [`github.com/ubgo/logger`](https://github.com/ubgo/logger)) writes log lines through it and correlates them with traces.\n\n**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.\n\n**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.\n\n**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](./docs/diagnostics.md).\n\n## How it compares\n\notelkit 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):\n\n| | **otelkit** | `setupOTelSDK` (docs snippet) | `otelconf` | Vendor distros (uptrace-go, …) |\n|---|:---:|:---:|:---:|:---:|\n| Vendor-neutral | ✅ | ✅ | ✅ | ❌ backend-locked |\n| Vendor presets — 1-line backend swap | ✅ | ❌ | ❌ | only their own |\n| Owns endpoint/port/`/v1/` path (no footguns) | ✅ | ❌ | ❌ | partial |\n| Loud diagnostics (self-test · probe · dry-run) | ✅ | ❌ | ❌ | ❌ |\n| Full `OTEL_*` env compliance | ✅ | partial | ✅ | partial |\n| Declarative `OTEL_CONFIG_FILE` | ✅ (delegates to `otelconf`) | ❌ | ✅ (it *is* this) | ❌ |\n| Real no-op on `OTEL_SDK_DISABLED` | ✅ | ❌ | ❌ | ❌ |\n| Ordered `Shutdown` + SIGTERM helper | ✅ | partial | partial | partial |\n| All three signals (traces/metrics/logs) | ✅ | ✅ | ✅ | varies |\n| Versioned library, not a copy-paste snippet | ✅ | ❌ | ✅ | ✅ |\n| Coverage | 100% | n/a | — | varies |\n\n## License\n\nApache-2.0. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fubgo%2Fotelkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fubgo%2Fotelkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fubgo%2Fotelkit/lists"}