{"id":51436448,"url":"https://github.com/ubgo/logger","last_synced_at":"2026-07-05T07:01:53.154Z","repository":{"id":358912189,"uuid":"1243621205","full_name":"ubgo/logger","owner":"ubgo","description":"Pluggable, adapter-based, slog-native zero-allocation structured logging for Go — sampling, redaction, OpenTelemetry, FingersCrossed debug-on-error, rotation, audit. A zap/zerolog/logrus/slog alternative.","archived":false,"fork":false,"pushed_at":"2026-05-19T15:31:36.000Z","size":171,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T17:57:03.983Z","etag":null,"topics":["go","golang","kubernetes","log","logger","logging","logrus","observability","opentelemetry","otel","slog","structured-logging","zap","zero-allocation","zerolog"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/ubgo/logger","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":"CODE_OF_CONDUCT.md","threat_model":null,"audit":"audit.go","citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-19T14:04:39.000Z","updated_at":"2026-05-19T15:48:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ubgo/logger","commit_stats":null,"previous_names":["ubgo/logger"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ubgo/logger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Flogger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Flogger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Flogger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Flogger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ubgo","download_url":"https://codeload.github.com/ubgo/logger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ubgo%2Flogger/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":["go","golang","kubernetes","log","logger","logging","logrus","observability","opentelemetry","otel","slog","structured-logging","zap","zero-allocation","zerolog"],"created_at":"2026-07-05T07:01:50.807Z","updated_at":"2026-07-05T07:01:52.393Z","avatar_url":"https://github.com/ubgo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ubgo/logger — the last Go logging library you'll need\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/ubgo/logger.svg)](https://pkg.go.dev/github.com/ubgo/logger) [![Go Report Card](https://goreportcard.com/badge/github.com/ubgo/logger)](https://goreportcard.com/report/github.com/ubgo/logger) [![test](https://github.com/ubgo/logger/actions/workflows/test.yml/badge.svg)](https://github.com/ubgo/logger/actions/workflows/test.yml) [![lint](https://github.com/ubgo/logger/actions/workflows/lint.yml/badge.svg)](https://github.com/ubgo/logger/actions/workflows/lint.yml) ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen) [![tag](https://img.shields.io/github/v/tag/ubgo/logger?sort=semver)](https://github.com/ubgo/logger/tags) [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE) ![Go](https://img.shields.io/badge/go-1.24-00ADD8?logo=go)\n\n**ubgo/logger is a pluggable, adapter-based, `log/slog`-native structured logging library for Go** — zero-allocation on the hot path, batteries included, and a drop-in upgrade path from `zap`, `zerolog`, `logrus`, `slog`, and `logr`.\n\nIt is the consolidation of the best ideas from the Go, JVM, .NET, Rust, JavaScript, and Python logging ecosystems into one coherent, benchmarked package: **structured logging + debug-on-error buffering + secret redaction + sampling + OpenTelemetry trace correlation + log rotation + tamper-evident audit logs + spans + message templates**, behind one small API.\n\n\u003e If you've ever asked \"which Go logging library should I use — zap, zerolog, logrus, or slog?\", this is the answer that ends the question.\n\n---\n\n## Table of contents\n\n- [Why ubgo/logger](#why-ubgologger)\n- [Feature highlights](#feature-highlights)\n- [Install](#install)\n- [Quick start (step by step)](#quick-start-step-by-step)\n- [Core concepts](#core-concepts)\n- [Recipes](#recipes)\n  - [Structured fields (zero-allocation)](#structured-fields-zero-allocation)\n  - [Fan-out to multiple sinks](#fan-out-to-multiple-sinks)\n  - [Debug-on-error (FingersCrossed)](#debug-on-error-fingerscrossed)\n  - [Secret/PII redaction](#secretpii-redaction)\n  - [Sampling under load](#sampling-under-load)\n  - [Context, tracing, and request scoping](#context-tracing-and-request-scoping)\n  - [Spans (causal log trees)](#spans-causal-log-trees)\n  - [Message templates](#message-templates)\n  - [Events, not messages](#events-not-messages)\n  - [Log file rotation](#log-file-rotation)\n  - [Async delivery \u0026 backpressure](#async-delivery--backpressure)\n  - [Tamper-evident audit logs](#tamper-evident-audit-logs)\n  - [Runtime log level (HTTP / signal / file)](#runtime-log-level-http--signal--file)\n  - [The slog bridge](#the-slog-bridge)\n  - [Testing your logs](#testing-your-logs)\n- [Migrating from zap / zerolog / logrus / slog](#migrating-from-zap--zerolog--logrus--slog)\n- [Contrib modules](#contrib-modules)\n- [Performance](#performance)\n- [FAQ](#faq)\n- [Documentation](#documentation)\n- [License](#license)\n\n---\n\n## Why ubgo/logger\n\n`log/slog` won the Go logging interface war — the whole ecosystem now writes `slog.Handler` backends. But `slog` is deliberately minimal: **no sampling, no log rotation, no async/backpressure, no PII redaction, no dedup, no runtime level control**, and writing a *correct* `slog.Handler` is a documented footgun. The community filled the gaps with 50+ tiny, single-purpose dependencies.\n\n`ubgo/logger` is **the slog backend that fills every gap** — one dependency, one mental model, honest benchmarks:\n\n- ✅ **slog-native** — it *is* a correct `slog.Handler` (passes the standard library's `testing/slogtest`). The entire slog ecosystem composes on top.\n- ✅ **Zero-allocation** typed hot path (CI-enforced), competitive with `zap` and `zerolog`.\n- ✅ **One extension seam** — a processor pipeline. Redaction, sampling, enrichment, dedup are all the same concept.\n- ✅ **Batteries included** — rotation, redaction, sampling, OTEL correlation, FingersCrossed, audit, network/cloud sinks — built in, not 50 dependencies.\n- ✅ **Drop-in migration** from zap, zerolog, logrus, std `log`, and `logr`.\n\n## Feature highlights\n\n| Category | What you get |\n|---|---|\n| **API** | `slog`-native · type-safe generic fields (`String`, `Int[T]`, …) · message templates · named events |\n| **Performance** | zero-allocation typed path (~295 ns/op, 0 B, 0 allocs, CI-gated) · object pooling |\n| **Transports** | sync · bounded-channel · **lock-free Disruptor ring**; explicit `Block`/`DropNewest`/`DropOldest` backpressure + dropped-count |\n| **Reliability** | per-sink level + encoder + failure isolation · honest drop accounting |\n| **Differentiators** | **FingersCrossed** debug-on-error buffering · **compiled path-DSL redaction** · **spans-as-context** causal trees · **tamper-evident audit chain** |\n| **Context** | `context.Context` propagation · OTEL `trace_id`/`span_id` correlation · MDC-equivalent bound fields |\n| **Sinks** | console (TTY-aware) · JSON · logfmt · file (rotation/retention/gzip) · syslog · TCP/UDP/TLS · Loki · Datadog · Elasticsearch · OTLP · Sentry |\n| **Ops** | runtime level via HTTP / signal / config file · self-metrics endpoint |\n| **DX** | `Development()`/`Production()` presets · `logtest` assertion kit · panic-recovery helpers |\n\n## Install\n\nRequires **Go 1.24+**.\n\n```bash\ngo get github.com/ubgo/logger\n```\n\nOptional adapter modules (only pull the heavy dependency you use):\n\n```bash\ngo get github.com/ubgo/logger/contrib/zap      # migrate from uber-go/zap\ngo get github.com/ubgo/logger/contrib/logrus   # migrate from sirupsen/logrus\ngo get github.com/ubgo/logger/contrib/zerolog  # migrate from rs/zerolog\ngo get github.com/ubgo/logger/contrib/phuslu   # migrate from phuslu/log\ngo get github.com/ubgo/logger/contrib/logr     # Kubernetes / controller-runtime\ngo get github.com/ubgo/logger/contrib/otel     # OpenTelemetry Logs bridge\ngo get github.com/ubgo/logger/contrib/sentry   # Sentry error events\n```\n\n## Quick start (step by step)\n\n### 1. The simplest possible logger\n\n```go\npackage main\n\nimport logger \"github.com/ubgo/logger\"\n\nfunc main() {\n\tlog := logger.New() // JSON to stderr at Info\n\tdefer log.Close()\n\n\tlog.Info(\"server started\", logger.String(\"addr\", \":8080\"), logger.Int(\"pid\", 4242))\n}\n```\n\n```json\n{\"time\":\"2026-05-19T12:00:00Z\",\"level\":\"info\",\"msg\":\"server started\",\"addr\":\":8080\",\"pid\":4242}\n```\n\n### 2. Use a preset\n\n```go\nlog := logger.Development() // pretty, colored, Debug, caller — for local dev\n// or\nlog := logger.Production()  // JSON, Info, async, sampled — for services\ndefer log.Close()\n```\n\n### 3. Add request context\n\n```go\nreqLog := log.With(logger.String(\"request_id\", \"abc-123\"))\nreqLog.Info(\"handling request\") // request_id on every line\n```\n\n### 4. Wire it as the standard `slog` logger (so all libraries benefit)\n\n```go\nimport \"log/slog\"\n\nslog.SetDefault(log.NewSlog())\nslog.Info(\"now every slog call in your deps flows through ubgo/logger\")\n```\n\n### 5. Build a production pipeline\n\n```go\nlog := logger.New(\n\tlogger.WithLevel(logger.LevelInfo),\n\tlogger.WithProcessors(\n\t\tlogger.NewPathRedactor(logger.Mask, \"[REDACTED]\", \"*.password\", \"*.token\"),\n\t\tlogger.NewSampleProcessor(100, 100), // first 100, then 1/100 — never drops ERROR\n\t),\n\tlogger.WithTransport(logger.NewDisruptorTransport(\n\t\tlogger.NewWriterSink(os.Stderr, logger.NewJSONEncoder(), logger.LevelInfo),\n\t\t8192, logger.DropNewest,\n\t)),\n)\ndefer log.Close() // drains the async ring\n```\n\nThat's the whole setup. The sections below show each capability.\n\n## Core concepts\n\nThere are five nouns:\n\n- **Logger** — what you call (`log.Info(...)`). Immutable; `With()` returns a child.\n- **Field** — a type-safe key/value (`logger.String`, `logger.Int[T]`, `logger.Err`, …). Scalars are unboxed → zero allocation.\n- **Processor** — the single extension seam: `func(ctx, *Record) error`. Enrichment, redaction, sampling, dedup are all processors. Returning `logger.ErrDrop` drops the record (this is how sampling works).\n- **Transport** — how a record gets from the call site to the sink: `Sync` (inline), `Channel` (bounded queue), or `Disruptor` (lock-free ring) — each with an explicit overflow policy.\n- **Sink** — the destination (console, file, network, cloud). Each sink owns its own level + encoder; a `Fanout` broadcasts to many with failure isolation.\n\nFull design rationale: [`docs/architecture.md`](./docs/architecture.md).\n\n## Recipes\n\n### Structured fields (zero-allocation)\n\n```go\nlog.Info(\"payment processed\",\n\tlogger.String(\"user\", userID),\n\tlogger.Int(\"amount_cents\", 1999),\n\tlogger.Bool(\"captured\", true),\n\tlogger.Dur(\"latency\", elapsed),\n\tlogger.Err(err), // nil-safe; emits \"error\":null\n)\n```\n\nUse `logger.Any(key, v)` for arbitrary values (reflection, off the hot path).\n\n### Fan-out to multiple sinks\n\n```go\nconsole := logger.NewConsoleSink(os.Stdout, logger.LevelDebug) // pretty, TTY-aware\njsonF, _ := logger.NewRotatingFile(\"/var/log/app.log\")\nfile := logger.NewFileSink(jsonF, logger.NewJSONEncoder(), logger.LevelInfo)\n\nlog := logger.New(logger.WithSink(logger.NewFanout(console, file)))\n```\n\nEach sink keeps its own level and encoder; one failing sink never blocks the others.\n\n### Debug-on-error (FingersCrossed)\n\nThe killer feature. A successful request logs **nothing** below the activation level. The first error flushes the entire buffered debug trail — so you get full forensics exactly when something breaks, and silence when it doesn't.\n\n```go\nfc := logger.NewFingersCrossed(\n\tlogger.NewWriterSink(os.Stderr, logger.NewJSONEncoder(), logger.LevelTrace),\n)\nlog := logger.New(logger.WithTransport(logger.NewSyncTransport(fc)), logger.WithLevel(logger.LevelTrace))\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tctx := logger.FCScope(r.Context()) // one buffer per request\n\tlog.DebugContext(ctx, \"loaded config\")\n\tlog.DebugContext(ctx, \"queried db\")\n\t// if everything succeeds → nothing is emitted\n\t// if log.ErrorContext(ctx, \"boom\") fires → the two Debug lines + the error are all flushed\n}\n```\n\n### Secret/PII redaction\n\nRedaction happens **in-process, before bytes reach any sink** — the only place raw values and structure coexist.\n\n```go\npr := logger.NewPathRedactor(logger.Mask, \"[REDACTED]\",\n\t\"*.password\",                  // any password field at any depth\n\t\"req.headers.authorization\",   // exact dotted path\n\t\"user.**\",                     // everything under user\n)\nlog := logger.New(logger.WithProcessors(pr))\n```\n\nStrategies: `logger.Mask` (replace), `logger.Hash` (sha256 prefix — keeps correlation), `logger.Drop` (remove).\n\n### Sampling under load\n\n```go\n// keep the first 100, then 1 in every 100 — but NEVER sample ERROR and above\nlog := logger.New(logger.WithProcessors(logger.NewSampleProcessor(100, 100)))\n```\n\n`DedupProcessor` collapses identical repeated lines and annotates the survivor with `deduped_count`.\n\n### Context, tracing, and request scoping\n\n```go\nctx = logger.ContextWith(ctx, logger.String(\"tenant\", \"acme\")) // MDC-style bound field\nlog.InfoContext(ctx, \"doing work\")                              // tenant included automatically\n```\n\nFor OpenTelemetry trace correlation, add the enricher with the OTEL extractor (see [`contrib/otel`](./contrib/otel)):\n\n```go\nlog := logger.New(logger.WithProcessors(\n\tlogger.NewEnrichProcessor(otellogger.TraceExtractor()), // adds trace_id/span_id from the active span\n))\n```\n\n### Spans (causal log trees)\n\n```go\nctx, span := log.StartSpan(ctx, \"checkout\", logger.String(\"order\", id))\ndefer span.End() // emits span.end with duration + ok\n\nlog.InfoContext(ctx, \"charging card\") // inherits span identity + fields\n_, child := log.StartSpan(ctx, \"charge_gateway\")\n// ... span_path \"1.1\" lets you reconstruct the tree from a flat log stream\nchild.Fail(err) // span.end becomes level=error, ok=false\nchild.End()\n```\n\n### Message templates\n\nSerilog-style: one call gives you readable text **and** structured fields **and** a stable grouping key.\n\n```go\nlog.Infot(\"processed {count} files for {user}\", 12, \"ada\")\n// msg=\"processed 12 files for ada\"\n// msg_template=\"processed {count} files for {user}\"  ← stable for alerting/grouping\n// count=12, user=\"ada\"                               ← structured\n```\n\n### Events, not messages\n\n```go\nlog.Event(\"user.signup\", logger.String(\"plan\", \"pro\"), logger.Int(\"uid\", 7))\n// no prose — the event name is the primary index (great for analytics/AI)\n```\n\n### Log file rotation\n\nBuilt in. No `lumberjack` dependency.\n\n```go\nrf, _ := logger.NewRotatingFile(\"/var/log/app.log\")\nrf.MaxSizeBytes = 100 \u003c\u003c 20 // 100 MiB\nrf.MaxBackups = 7\nrf.MaxAge = 14 * 24 * time.Hour\nrf.Compress = true // gzip rotated segments\nlog := logger.New(logger.WithSink(logger.NewFileSink(rf, logger.NewJSONEncoder(), logger.LevelInfo)))\n\n// logrotate-friendly: reopen on SIGHUP\nstop := logger.OnSIGHUP(func() { _ = rf.Reopen() })\ndefer stop()\n```\n\n### Async delivery \u0026 backpressure\n\n```go\nsink := logger.NewWriterSink(os.Stderr, logger.NewJSONEncoder(), logger.LevelInfo)\n\n// bounded channel + worker\nt := logger.NewChannelTransport(sink, 4096, logger.DropNewest)\n// or lock-free Disruptor ring for max throughput\nt := logger.NewDisruptorTransport(sink, 8192, logger.Block)\n\nlog := logger.New(logger.WithTransport(t))\ndefer log.Close() // drains the queue\n\n// dropped records are counted, never silent:\nn := t.Dropped()\n```\n\n### Tamper-evident audit logs\n\n```go\nf, _ := os.Create(\"/var/log/audit.log\")\naudit := logger.NewAuditSink(f, logger.NewJSONEncoder())\nlog := logger.New(logger.WithTransport(logger.NewSyncTransport(audit)))\n\nlog.Info(\"user deleted record\", logger.String(\"actor\", \"admin\"), logger.Int(\"id\", 42))\n```\n\nEach line is hash-chained (`sha256(prev || record)`). Verify integrity later:\n\n```go\nres := logger.VerifyAudit(file)\nif !res.OK {\n\tfmt.Printf(\"tampered at seq %d: %s\\n\", res.BrokenAtSeq, res.Reason)\n}\n```\n\nDetects edits, deletions, and reordering.\n\n### Runtime log level (HTTP / signal / file)\n\n```go\nlv := logger.NewLevelVar(logger.LevelInfo)\nlog := logger.New(logger.WithLeveler(lv))\n\n// 1. HTTP: GET/PUT /loglevel?level=debug\nhttp.Handle(\"/loglevel\", logger.NewLevelHandler(lv))\n\n// 2. Signal: flip to debug on SIGUSR2, back on next\nstop := logger.CycleLevelOnSignal(lv, syscall.SIGUSR2, logger.LevelInfo, logger.LevelDebug)\ndefer stop()\n\n// 3. Config file: {\"level\":\"warn\"} hot-reloaded\n_, stopW := logger.WatchConfigFile(\"/etc/app/log.json\", lv, 5*time.Second)\ndefer stopW()\n```\n\nSelf-metrics (emitted/dropped/by-level) are exposed too:\n\n```go\nhttp.Handle(\"/logmetrics\", log.Metrics())\n```\n\n### The slog bridge\n\n```go\nslog.SetDefault(log.NewSlog())\n// every slog.Handler middleware (samber/slog-*, otelslog) composes on top of ubgo/logger\n```\n\n### Testing your logs\n\n```go\nimport \"github.com/ubgo/logger/logtest\"\n\nfunc TestSignup(t *testing.T) {\n\tlog, cap := logtest.New()\n\tsvc := NewService(log)\n\tsvc.Signup(\"ada\")\n\n\tcap.AssertLogged(t, logger.LevelInfo, \"signup complete\")\n\tcap.AssertField(t, \"user\", \"ada\")\n\tcap.AssertNoErrors(t)\n}\n```\n\n## Migrating from zap / zerolog / logrus / slog\n\nMigration is mechanical — keep your existing call sites, swap the engine.\n\n| From | How | Module |\n|---|---|---|\n| `log/slog` | `slog.SetDefault(log.NewSlog())` | core (no extra dep) |\n| std `log` | `logger.RedirectStdLog(log, logger.LevelInfo)` | core |\n| `uber-go/zap` | `zaplogger.New(core, zapcore.InfoLevel)` | [`contrib/zap`](./contrib/zap) |\n| `sirupsen/logrus` | `logruslogger.Attach(logrusLogger, core)` | [`contrib/logrus`](./contrib/logrus) |\n| `rs/zerolog` | `zerologlogger.New(zl, logger.LevelInfo)` | [`contrib/zerolog`](./contrib/zerolog) |\n| `phuslu/log` | `phulogger.New(pl, logger.LevelInfo)` | [`contrib/phuslu`](./contrib/phuslu) |\n| `go-logr/logr` | `logrlogger.New(core)` | [`contrib/logr`](./contrib/logr) |\n\nFull guide: [`docs/migration.md`](./docs/migration.md).\n\n## Contrib modules\n\nHeavy third-party dependencies are isolated in separate, independently-versioned submodules so the core stays dependency-free:\n\n| Module | Purpose |\n|---|---|\n| [`contrib/zap`](./contrib/zap) | Forward `zap` call sites through ubgo/logger |\n| [`contrib/logrus`](./contrib/logrus) | `logrus.Hook` + `Attach()` drop-in |\n| [`contrib/zerolog`](./contrib/zerolog) | Ship through a `zerolog.Logger` |\n| [`contrib/phuslu`](./contrib/phuslu) | Ship through a `phuslu/log` writer |\n| [`contrib/logr`](./contrib/logr) | `logr.Logger` for Kubernetes / controller-runtime |\n| [`contrib/otel`](./contrib/otel) | OpenTelemetry Logs bridge + W3C trace extractor |\n| [`contrib/sentry`](./contrib/sentry) | WARN+ records as Sentry events |\n\n## Performance\n\nMeasured on Apple M-series, Go 1.24, output to `io.Discard`. Allocation count is enforced by a CI gate (`TestZeroAlloc*`).\n\n| Path | ns/op | B/op | allocs/op |\n|---|--:|--:|--:|\n| **Typed hot path** | **~295** | **0** | **0** |\n| Disabled level (gated out) | ~7 | 0 | 0 |\n| Through the `slog` bridge | ~698 | 320 | 1 |\n| stdlib `slog` JSON (reference) | ~704 | 0 | 0 |\n\nThe slog-bridge row is the **honest through-bridge cost** (slog's own `Record`/attrs allocation for \u003e5 attrs) — published, not hidden. \"Portable via slog\" silently costing 10–40× is the ecosystem trap this library refuses to repeat.\n\nSee [`docs/performance.md`](./docs/performance.md) for the methodology and how to reproduce.\n\n## FAQ\n\n**Is ubgo/logger a replacement for zap / zerolog / logrus?**\nYes — it's a zero-allocation, slog-native superset with batteries included, plus drop-in migration shims so switching is mechanical.\n\n**Should I use it instead of `log/slog`?**\nUse slog's *API*; get ubgo/logger's *engine*. It implements `slog.Handler` (passing `testing/slogtest`) and adds sampling, rotation, redaction, async, FingersCrossed, audit, and trace correlation that slog deliberately omits.\n\n**Does it support OpenTelemetry?**\nYes — `contrib/otel` is an OTEL Logs bridge, and the core's level model *is* the OTEL `SeverityNumber`. Logs correlate with traces via `trace_id`/`span_id`.\n\n**Is it production-ready?**\nThe full feature set is implemented and race-tested with a CI matrix across all modules and an allocation-regression gate. APIs are stabilizing toward a `v1`.\n\n**Why not just import 50 `samber/slog-*` packages?**\nYou can — they compose on top, since ubgo/logger is a correct `slog.Handler`. But the things you actually need in production (rotation, redaction, sampling, backpressure, debug-on-error) are first-class here, in one dependency, benchmarked together.\n\n**Zero dependencies?**\nThe core module has **no third-party dependencies**. Heavy integrations live in opt-in `contrib/*` submodules.\n\n## Documentation\n\n- [Getting started](./docs/getting-started.md)\n- [Architecture \u0026 design](./docs/architecture.md)\n- [Sinks \u0026 transports](./docs/sinks.md)\n- [Processors \u0026 the pipeline](./docs/processors.md)\n- [Migration guide](./docs/migration.md)\n- [Performance](./docs/performance.md)\n- [Test coverage report](./COVERAGE.md)\n- [API reference (pkg.go.dev)](https://pkg.go.dev/github.com/ubgo/logger)\n\n## License\n\n[Apache-2.0](./LICENSE) © the ubgo authors.\n\n---\n\n\u003csub\u003eKeywords: Go logging library, golang structured logging, slog handler, zap alternative, zerolog alternative, logrus replacement, zero allocation logger, OpenTelemetry logging Go, log rotation, PII redaction, debug on error, tamper-evident audit log, Kubernetes logr.\u003c/sub\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fubgo%2Flogger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fubgo%2Flogger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fubgo%2Flogger/lists"}