{"id":28790690,"url":"https://github.com/semihalev/zlog","last_synced_at":"2026-05-15T20:32:51.012Z","repository":{"id":57487086,"uuid":"138898528","full_name":"semihalev/zlog","owner":"semihalev","description":"A small, very fast, truly zero-allocation structured logger for Go","archived":false,"fork":false,"pushed_at":"2026-04-25T13:25:28.000Z","size":96,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-25T15:24:56.232Z","etag":null,"topics":["golang","logger","logging","zerolog"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semihalev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-06-27T15:13:00.000Z","updated_at":"2026-04-25T13:22:41.000Z","dependencies_parsed_at":"2025-07-26T14:04:50.541Z","dependency_job_id":"c2d50c8b-15f1-4db1-993b-3d44a855441b","html_url":"https://github.com/semihalev/zlog","commit_stats":null,"previous_names":["semihalev/zlog","semihalev/log"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/semihalev/zlog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semihalev%2Fzlog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semihalev%2Fzlog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semihalev%2Fzlog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semihalev%2Fzlog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semihalev","download_url":"https://codeload.github.com/semihalev/zlog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semihalev%2Fzlog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33078899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T20:25:35.270Z","status":"ssl_error","status_checked_at":"2026-05-15T20:25:34.732Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["golang","logger","logging","zerolog"],"created_at":"2025-06-17T23:07:52.555Z","updated_at":"2026-05-15T20:32:50.993Z","avatar_url":"https://github.com/semihalev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zlog\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/semihalev/zlog/v2.svg)](https://pkg.go.dev/github.com/semihalev/zlog/v2)\n[![Go Report Card](https://goreportcard.com/badge/github.com/semihalev/zlog)](https://goreportcard.com/report/github.com/semihalev/zlog)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA small, very fast, **truly zero-allocation** structured logger for Go.\n\nApple M5, Go 1.23+:\n\n```\nBenchmarkUltimateLogger              15.9 ns/op    0 B/op   0 allocs/op\nBenchmarkUltimateLoggerParallel       5.2 ns/op    0 B/op   0 allocs/op\nBenchmarkStructuredLogger            35.6 ns/op    0 B/op   0 allocs/op\nBenchmarkStructuredLoggerParallel    10.5 ns/op    0 B/op   0 allocs/op\nBenchmarkStructured + 5 fields       45.2 ns/op    0 B/op   0 allocs/op\nBenchmarkStructured + 10 fields      76.7 ns/op    0 B/op   0 allocs/op\nBenchmarkRealWorld → TerminalWriter  26.5 ns/op    0 B/op   0 allocs/op\nBenchmarkDisabledDebug                0.23 ns/op   0 B/op   0 allocs/op\n```\n\nThe interesting number is the last column on every row: every logging path is genuinely 0 allocs/op once the buffer pool is warm, including the colored terminal output. Parallel benchmarks are *faster* than serial because each `GOMAXPROCS` slot keeps its own pool localcache and there's no contention on a sequence counter.\n\n## Install\n\n```bash\ngo get github.com/semihalev/zlog/v2\n```\n\nRequires Go 1.23+.\n\n## Quick start\n\n```go\npackage main\n\nimport \"github.com/semihalev/zlog/v2\"\n\nfunc main() {\n    log := zlog.NewStructured()\n    log.SetWriter(zlog.StdoutTerminal())\n\n    log.Info(\"server started\",\n        zlog.String(\"addr\", \":8080\"),\n        zlog.Int(\"workers\", 4),\n    )\n    log.Warn(\"slow query\",\n        zlog.String(\"table\", \"users\"),\n        zlog.Float64(\"duration_ms\", 327.4),\n    )\n    log.Error(\"db connection lost\",\n        zlog.String(\"err\", \"i/o timeout\"),\n        zlog.Int(\"retry\", 3),\n    )\n}\n```\n\n```\nINFO  [04-25|15:51:30] server started                          addr=:8080 workers=4\nWARN  [04-25|15:51:30] slow query                              table=users duration_ms=327.4\nERROR [04-25|15:51:30] db connection lost                      err=\"i/o timeout\" retry=3\n```\n\n## Logger types\n\n| Type | Purpose | Cost |\n|---|---|---|\n| `zlog.NewUltimateLogger()` | Bare-message hot path. No fields. | ~16 ns/op serial, ~5 ns/op parallel |\n| `zlog.NewStructured()` | Typed fields, the recommended API. | ~36 ns/op + ~10 ns per extra field |\n| `zlog.New()` | Plain `Logger`. Same shape as Ultimate. | ~21 ns/op |\n\nFor most apps, `NewStructured()` is the right choice: zero alloc, typed fields, ~26ns end-to-end when the writer is a `TerminalWriter` (that path is direct-text, no binary intermediate).\n\n## Fields\n\n```go\nlog.Info(\"event\",\n    zlog.String(\"name\", \"Alice\"),\n    zlog.Int(\"age\", 30),\n    zlog.Int64(\"id\", 123456789),\n    zlog.Uint(\"count\", 42),\n    zlog.Uint64(\"total\", 9999999),\n    zlog.Float32(\"score\", 98.5),\n    zlog.Float64(\"pi\", 3.14159),\n    zlog.Bool(\"active\", true),\n    zlog.Bytes(\"data\", []byte{0x01, 0x02, 0x03}),\n)\n```\n\nAll field constructors are inlinable and allocation-free.\n\nThere's also a key/value-pair API for the global helper that's friendlier when you have `any` values:\n\n```go\nzlog.Info(\"user logged in\", \"username\", \"alice\", \"user_id\", 12345)\n```\n\nThe global helpers (`zlog.Info`, `zlog.Warn`, ...) accept either typed `Field` values or alternating key/value pairs.\n\n## Writers\n\n- `zlog.StdoutTerminal()` / `zlog.StderrTerminal()` — colored, padded, human-readable terminal output. The structured logger detects this writer and formats text directly into a pooled buffer (no binary intermediate).\n- `zlog.StdoutWriter()` / `zlog.StderrWriter()` — raw binary output to stdout/stderr.\n- `zlog.NewLogfmtWriter(io.Writer)` — `key=value` text format on top of the binary log.\n- `zlog.NewMMapWriter(path, size)` — memory-mapped file with no per-write syscall (Linux/macOS/Windows).\n- `zlog.NewAsyncWriter(io.Writer, bufSize)` — lock-free ring buffer with worker drains; for fan-out at high throughput.\n- Any `io.Writer` works. The package writes a compact binary record; the terminal/logfmt writers are the supported decoders.\n\n```go\nmmap, _ := zlog.NewMMapWriter(\"/var/log/app.log\", 100*1024*1024) // 100 MB\ndefer mmap.Close()\n\nlog := zlog.NewStructured()\nlog.SetWriter(mmap)\n```\n\n## Levels\n\n```go\nlog := zlog.NewStructured()\nlog.SetLevel(zlog.LevelWarn) // Only Warn / Error / Fatal are emitted.\n\nif log.GetLevel() \u003c= zlog.LevelDebug {\n    // expensive debug computation only when needed\n}\n```\n\nA disabled level call is ~0.23 ns/op (a single atomic load + compare).\n\n## Terminal output\n\n```\nDEBUG [01-02|15:04:05] starting up\nINFO  [01-02|15:04:05] server initialized\nWARN  [01-02|15:04:05] config not found, using defaults\nERROR [01-02|15:04:05] db connection failed                    error=timeout retry=3\n```\n\nColors:\n\n- DEBUG → cyan\n- INFO → green\n- WARN → yellow\n- ERROR → red\n- FATAL → magenta\n\nColor is auto-detected from the underlying `*os.File` (TTY check). It also respects `NO_COLOR` and `TERM=dumb`. To force-disable in code:\n\n```go\ntw := zlog.NewTerminalWriter(os.Stdout).(*zlog.TerminalWriter)\ntw.SetColorEnabled(false)\nlog.SetWriter(tw)\n```\n\nThe terminal writer caches the formatted timestamp by Unix second, so `time.Time` decomposition runs once per second, not per log line.\n\n### Windows\n\nANSI colors work on Windows 10 (build 14393+) automatically. On older Windows or when output isn't a TTY, plain text is used. Same env vars (`NO_COLOR`, `TERM=dumb`) apply.\n\n## How it stays zero-alloc\n\nA few of the things that matter:\n\n- Every record is built into a `sync.Pool`-backed buffer indexed by a power-of-two size class. Allocation only happens when the pool is cold.\n- The \"small message → stack buffer\" fast path that traditional loggers use was deliberately removed: passing a stack array to an `io.Writer.Write` interface call forces it onto the heap. The pool path is the actual zero-alloc path.\n- Field encoding writes int/float values as a single `*(*uint64)(unsafe.Pointer(\u0026buf[pos])) = ...` store in native byte order — one instruction per numeric field, not eight byte stores.\n- The structured logger detects when its writer is a `*TerminalWriter` and formats text directly into the pooled buffer, skipping the binary encode → re-decode round-trip. Type assertion is ~1ns; the saving is ~30-40ns.\n- Wall-clock timestamps are computed as `baseWallNs + (nanotime() - baseMonoNs)`, where `baseWallNs` and `baseMonoNs` are sampled once at `init()`. One VDSO call per log, ~5ns. Drifts from the kernel's wall clock if NTP adjusts the system time after init — fine for log timestamps.\n- Escape-detection scans use a 256-byte classifier table (Go compiles byte-LUT loads to NEON `tbl` / SSSE3 `pshufb` on supported architectures).\n- No `runtime.exit`, no panic-recovery in hot paths, no `defer` in the terminal writer's `Write`.\n\nIf you change a logger or writer and start seeing 1 alloc/op, run `go build -gcflags='-m=2'` and look for `escapes to heap` on the buffer — that's almost always an interface boundary somewhere.\n\n## Binary log format\n\nThe binary record on disk / on the wire is:\n\n```\n0..3   magic \"ZLOG\"   (uint32, little-endian)\n4      version        (1)\n5      level          (1)\n6..13  unix nanoseconds (uint64, native order)\n14..15 msgLen         (uint16, native order)\n16+    message bytes\n       optional: 1-byte fieldCount, then fields\n```\n\nEach field is `keyLen(1) + key + type(1) + value`. Numeric values are 4 or 8 bytes native order. String / bytes values are `len(uint16, native) + payload`.\n\nNative byte order is intentional: only this package's writers consume the binary form, so a `bswap` per field would buy nothing. The binary form is **not** stable across machines with different endianness — if you ship binary logs over the wire, decode on the producer's architecture.\n\n## Testing\n\n```bash\ngo test -race ./...\ngo test -bench=. -benchmem ./...\n```\n\nCI runs build + race + zero-alloc verification on Linux / macOS / Windows.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemihalev%2Fzlog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemihalev%2Fzlog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemihalev%2Fzlog/lists"}