{"id":51541970,"url":"https://github.com/forge18/lua-zero-log","last_synced_at":"2026-07-09T14:30:47.021Z","repository":{"id":366187395,"uuid":"1275378061","full_name":"forge18/lua-zero-log","owner":"forge18","description":"Zero-allocation deferred-formatting ring logger for LuaJIT (FFI).","archived":false,"fork":false,"pushed_at":"2026-06-20T16:03:48.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T18:05:11.058Z","etag":null,"topics":["ffi","gamedev","logger","logging","lua","luajit","ring-buffer","zero-allocation"],"latest_commit_sha":null,"homepage":"","language":"Lua","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/forge18.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":"2026-06-20T15:59:38.000Z","updated_at":"2026-06-20T16:03:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/forge18/lua-zero-log","commit_stats":null,"previous_names":["forge18/lua-zero-log"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/forge18/lua-zero-log","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge18%2Flua-zero-log","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge18%2Flua-zero-log/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge18%2Flua-zero-log/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge18%2Flua-zero-log/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forge18","download_url":"https://codeload.github.com/forge18/lua-zero-log/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge18%2Flua-zero-log/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35303290,"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-09T02:00:07.329Z","response_time":57,"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":["ffi","gamedev","logger","logging","lua","luajit","ring-buffer","zero-allocation"],"created_at":"2026-07-09T14:30:44.759Z","updated_at":"2026-07-09T14:30:47.016Z","avatar_url":"https://github.com/forge18.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lua-zero-log\n\nA **zero-allocation, deferred-formatting ring logger for LuaJIT**.\n\nThe hot path never touches the GC. A log call writes a level, a pre-registered message id, and a few\nnumeric args into a preallocated `ffi` ring buffer; the expensive part — `string.format` — is\ndeferred until the ring is drained (on flush, on crash, periodically). This is the \"binary / deferred\nlogging\" technique (NanoLog, spdlog's backtrace) done in pure LuaJIT FFI: no native build, no\ncross-platform binary matrix.\n\nRequires **LuaJIT** (uses `ffi`). Not thread-safe — use one logger per thread.\n\n## Install\n\n**[Lux](https://github.com/nvim-neorocks/lux) (recommended)** — add it as a git dependency:\n\n```sh\nlx add github:forge18/lua-zero-log\n```\n\nor pin it yourself in `lux.toml`:\n\n```toml\n[dependencies.lua-zero-log]\ngit = \"github:forge18/lua-zero-log\"\nversion = \"\u003ccommit-sha-or-semver-tag\u003e\"   # omit to track the latest\n```\n\nThen `local zerolog = require(\"zerolog\")`. (A project with git dependencies can't be published to\nluarocks.org — that's a luarocks limitation, not this library's.)\n\n**Vendored (single file)** — there are no dependencies, so you can just copy `src/zerolog.lua` into\nyour project (e.g. `lib/zerolog.lua`) and `require` it.\n\n**LÖVE** — LÖVE already runs LuaJIT, so put `zerolog.lua` anywhere on `package.path` and require it.\n\n## Two tiers\n\n```lua\nlocal zerolog = require(\"zerolog\")\nlocal L = zerolog.LEVELS\n\nlocal log = zerolog.new({\n  capacity = 4096,                 -- ring size in records; oldest are overwritten\n  clock    = function() return game.minutes end,  -- timestamp source (your sim clock)\n  sink     = function(level, ts, message, seq)    -- where flush() sends entries\n    io.write(zerolog.line(level, ts, message), \"\\n\")\n  end,\n  level    = L.DEBUG,              -- minimum level to record\n})\n\n-- Cold / ergonomic: formats eagerly, takes any args. The ~99% path (lifecycle, errors).\nlog:info(\"loaded zone %s in %d ms\", zoneName, ms)\nlog:error(\"save failed: %s\", err)\n\n-- Hot / zero-alloc: register once, then call with exactly that many *numeric* args. No string work,\n-- no varargs, no garbage. Use inside per-frame / per-entity loops.\nlocal scored = log:def(L.TRACE, \"ai scored %.2f for unit %d\")  -- arity inferred = 2\nfor _, u in ipairs(units) do\n  scored(u.score, u.id)\nend\n\n-- Later — drain the buffer.\nlog:flush()                       -- to the sink, then clears\nlog:drain(function(level, ts, message, seq) ... end)  -- non-destructive\n```\n\n## API\n\n| Call | Description |\n|------|-------------|\n| `zerolog.new(opts)` | Create a logger. `opts`: `capacity`, `clock`, `sink`, `level`, `name`. |\n| `log:def(level, fmt [, argc])` | Register a deferred message; returns a fixed-arity, alloc-free emitter. Arity is inferred from `fmt` unless given. |\n| `log:log(level, fmt, ...)` | Eager, any-args log. |\n| `log:trace/debug/info/warn/error/fatal(fmt, ...)` | Level shorthands for `:log`. |\n| `log:drain(visit)` | Visit live entries oldest→newest as `visit(level, ts, message, seq)`. Non-destructive. Returns count. |\n| `log:flush()` | Drain to the sink, then `reset`. Returns count (0 with no sink). |\n| `log:reset()` | Forget all buffered entries. |\n| `log:count()` | Entries currently held (saturates at `capacity`). |\n| `log:setSink(fn)` · `log:setLevel(lvl)` | Mutators. |\n| `zerolog.LEVELS` · `zerolog.LEVEL_NAMES` | `TRACE…FATAL` = 1…6, and the reverse map. |\n| `zerolog.line(level, ts, message)` | Convenience one-line formatter for sinks. |\n\n## Notes \u0026 limits\n\n| Limit | Detail |\n|-------|--------|\n| Deferred args are numeric | Stored as doubles — integers exact to 2^53. For strings, use the cold path or bake them into the format string. |\n| The hot emitter is strict | Wrong arity or a non-number argument raises. |\n| ≤ 8 args per deferred message | The cap is `zerolog.MAXARGS`. |\n| Inject a JIT-friendly clock for hot loops | A plain Lua function returning a number. The `os.clock` default is convenience-only and can abort the trace. |\n| The library owns only the ring + formatting | A clock, a file sink, and a crash-time drain are the caller's job. |\n\n## Test\n\n```sh\nluajit spec/run.lua     # or: lx test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge18%2Flua-zero-log","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforge18%2Flua-zero-log","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge18%2Flua-zero-log/lists"}