{"id":46095997,"url":"https://github.com/zalgonoise/logx","last_synced_at":"2026-03-01T18:37:00.089Z","repository":{"id":63690095,"uuid":"569922653","full_name":"zalgonoise/logx","owner":"zalgonoise","description":"A blazing fast structured logger for Go","archived":false,"fork":false,"pushed_at":"2022-12-18T02:08:18.000Z","size":56,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-08-11T22:11:33.321Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/zalgonoise.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}},"created_at":"2022-11-23T23:38:24.000Z","updated_at":"2022-11-23T23:42:07.000Z","dependencies_parsed_at":"2023-01-29T18:30:45.413Z","dependency_job_id":null,"html_url":"https://github.com/zalgonoise/logx","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"purl":"pkg:github/zalgonoise/logx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Flogx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Flogx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Flogx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Flogx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zalgonoise","download_url":"https://codeload.github.com/zalgonoise/logx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Flogx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29979126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T16:35:47.903Z","status":"ssl_error","status_checked_at":"2026-03-01T16:35:44.899Z","response_time":124,"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":[],"created_at":"2026-03-01T18:36:59.680Z","updated_at":"2026-03-01T18:36:59.930Z","avatar_url":"https://github.com/zalgonoise.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# logx\n\n*A blazing fast structured logger for Go*\n\n___________\n\n\n## Overview\n\nAfter working on [`zlog`](https://github.com/zalgonoise/zlog), I've decided to do a second iteration of a structured logger with a simpler (but more meaningful) API, as well as a more performant solution.\n\nAs for the logger API, I followed most of the input shared in the discussion in [go#54763](https://github.com/golang/go/discussions/54763), on what I saw was useful and idiomatic. As for the implementation itself, although it is not as clear-cut as desired or as performant as [`zerolog`](https://github.com/rs/zerolog) or [`zap`](https://github.com/uber-go/zap), it is still going for a very low number of allocations for the amount of time put into. More information in the [benchmarks section](#benchmarks)\n\n___________\n\n## Installation\n\nTo fetch `logx` as a Go library, use `go get` or `go install`:\n\n```\ngo get -u github.com/zalgonoise/logx\n```\n\n```\ngo install github.com/zalgonoise/logx@latest\n```\n\n...or, simply import it in your Go file and run `go mod tidy`:\n\n```go\nimport (\n    // (...)\n\n    \"github.com/zalgonoise/logx\"\n)\n\n```\n\n__________\n\n## Features\n\n### Logger\n\nThe Logger is an interface that implements a Printer interface (with methods corresponding log printing actions like `Log()` and `Info()`) as well as a set of additional helper methods to make it easier to use and configure.\n\nTo spawn a Logger, you need to provide a [Handler](#handler).\n\n```go\n// Logger interface describes the behavior that a logger should\n// have\n//\n// This includes the Printer interface, as well as other methods\n// to give the logger more flexibility\ntype Logger interface {\n\t// Printer interface allows registering log messages\n\tPrinter\n\t// Enabled returns a boolean on whether the logger is accepting\n\t// records with log level `level`\n\tEnabled(level level.Level) bool\n\t// Handler returns this Logger's Handler interface\n\tHandler() handlers.Handler\n\t// With will spawn a copy of this Logger with the input attributes\n\t// `attrs`\n\tWith(attrs ...attr.Attr) Logger\n}\n\n\n// Printer interface describes the behavior that a (log) Printer\n// should have. This includes individual methods for printing log\n// messages for each log level, as well as a general-purpose `Log()`\n// method to customize the log level.\ntype Printer interface {\n\t// Trace prints a log message `msg` with attributes `attrs`, with\n\t// Trace-level\n\tTrace(msg string, attrs ...attr.Attr)\n\t// Debug prints a log message `msg` with attributes `attrs`, with\n\t// Debug-level\n\tDebug(msg string, attrs ...attr.Attr)\n\t// Info prints a log message `msg` with attributes `attrs`, with\n\t// Info-level\n\tInfo(msg string, attrs ...attr.Attr)\n\t// Warn prints a log message `msg` with attributes `attrs`, with\n\t// Warn-level\n\tWarn(msg string, attrs ...attr.Attr)\n\t// Error prints a log message `msg` with attributes `attrs`, with\n\t// Error-level\n\tError(msg string, attrs ...attr.Attr)\n\t// Fatal prints a log message `msg` with attributes `attrs`, with\n\t// Fatal-level\n\tFatal(msg string, attrs ...attr.Attr)\n\t// Log prints a log message `msg` with attributes `attrs`, with\n\t// `level` log level\n\tLog(level level.Level, msg string, attrs ...attr.Attr)\n}\n```\n\n\n### Handler\n\nA handler is the logging backend, responsible for writing the records with a certain format using an io.Writer. This library exposes a basic text handler that formats `any` types as string simply using `fmt.Sprintf(\"%v\", r.Value())`; as well as a JSON handler that uses [`goccy/go-json`](https://github.com/goccy/go-json).\n\nThe text handler is not optimized for performance and is not exactly most suitable for production. The JSON handler is reliable, however, and it is safe to use in production.\n\nThe data structures implementing these handlers are immutable. The handler is a simple interface which can be implemented with the following methods:\n\n```go\n// Handler describes a logging backend, capable of writing a Record to an\n// io.Writer (with its Handle() method).\n//\n// Beyond this feature, it also exposes methods of copying it with different\n// configuration options.\ntype Handler interface {\n\t// Enabled returns a boolean on whether the Handler is accepting\n\t// records with log level `level`\n\tEnabled(level level.Level) bool\n\t// Handle will process the input Record, returning an error if raised\n\tHandle(records.Record) error\n\t// With will spawn a copy of this Handler with the input attributes\n\t// `attrs`\n\tWith(attrs ...attr.Attr) Handler\n\n\t// WithSource will spawn a new copy of this Handler with the setting\n\t// to add a source file+line reference to `addSource` boolean\n\tWithSource(addSource bool) Handler\n\n\t// WithLevel will spawn a copy of this Handler with the input level `level`\n\t// as a verbosity filter\n\tWithLevel(level level.Level) Handler\n\n\t// WithReplaceFn will spawn a copy of this Handler with the input attribute\n\t// replace function `fn`\n\tWithReplaceFn(fn func(a attr.Attr) attr.Attr) Handler\n}\n```\n\n### Record\n\nA record is an interface exposes a set of getter methods for its elements, as well as additional helper methods to make it more granular. Although the built-in handlers already generate records themselves in their implementations, the point to the interface is to allow easy integration and extension of this library, with your own custom data types.\n\nA record is an immutable entity.\n\n```go\n// Record interface describes the behavior that a Record should have\n//\n// It expose getter methods for its elements, as well as two helper methods:\n//   - `AddAttr()` will return a copy of this Record with the input Attr appended\n//     to the existing ones\n//   - `AttrLen()` will return the length of the attributes in the record\ntype Record interface {\n\t// AddAttr returns a copy of this Record with the input Attr appended to the\n\t// existing ones\n\tAddAttr(a ...attr.Attr) Record\n\t// Attrs returns the slice of Attr associated to this Record\n\tAttrs() []attr.Attr\n\t// AttrLen returns the length of the slice of Attr in the Record\n\tAttrLen() int\n\t// Message returns the string Message associated to this Record\n\tMessage() string\n\t// Time returns the time.Time timestamp associated to this Record\n\tTime() time.Time\n\t// Level returns the level.Level level associated to this Record\n\tLevel() level.Level\n}\n\n```\n\n### Attribute\n\nAn attribute is a simple interface that exposes getter and setter methods for an attribute, a key-value pair where the key is `string` and value is `any`. Note that an attribute is an immutable entity.\n\n```go\n// Attr interface describes the behavior that a serializable attribute\n// should have.\n//\n// Besides retrieving its key and value, it also permits creating a copy of\n// the original Attr with a different key or a different value\ntype Attr interface {\n\t// Key returns the string key of the attribute Attr\n\tKey() string\n\t// Value returns the (any) value of the attribute Attr\n\tValue() any\n\t// WithKey returns a copy of this Attr, with key `key`\n\tWithKey(key string) Attr\n\t// WithValue returns a copy of this Attr, with value `value`\n\t//\n\t// It must be the same type of the original Attr, otherwise returns\n\t// nil\n\tWithValue(value any) Attr\n}\n```\n\nDespite being exposed and used as an interface, creating a new attribute with `attr.New[T](key string, value T) attr.Attr` uses a generic function that scopes this attribute to a certain type. \n\nThis means that when copying an attribute with the `WithValue()` method, the input value (as type `any`) must match the original attribute's type.\n\n### Level\n\nA level is an interface that exposes two methods, `String() string` and `Int() int`, which define different log levels in the records. While levels are used to resemble severity of the log record, they are also used by handlers (and likewise loggers) as a records filter. \n\n```go\n// Level interface describes the behavior that a log level should have\n//\n// It must provide methods to be casted as a string or as an int\ntype Level interface {\n\t// String returns the level as a string\n\tString() string\n\t// Int returns the level as an int\n\tInt() int\n}\n```\n\n### Context Logger\n\nA logger can be embeded into a `context.Context`, and retrieved from one, too:\n\n```go\n// CtxLoggerKey is a custom type to define context keys for this\n// library's logger\ntype CtxLoggerKey string\n\n// StandardCtxKey is an instance of CtxLoggerKey with value \"logger\"\nconst StandardCtxKey CtxLoggerKey = \"logger\"\n\n// InContext returns a copy of the input Context `ctx` with the input\n// Logger `logger` as a value (identified by `StandardCtxKey`)\nfunc InContext(ctx context.Context, logger Logger) context.Context\n\n// From returns a Logger from the input Context `ctx`. If not present,\n// it returns nil\nfunc From(ctx context.Context) Logger\n```\n\n\n________________\n\n## Disclaimer\n\nAlthough `logx` isn't *the world's fastest structured logger*, I am not aiming for it either. In reality, logging should be kept simple and the right tools should be used for the job.\n\nThis means if you're concerned about metrics, setup your observability accordingly. If you need alerts on certain events, setup your observability accordingly.\n\nAll in all, logging is part of your observability strategy but it should not be the center point nor should it be the only tool in your toolbox for it. \n\nI love structured logging but parsing millions of lines of logs to find a single event drives one not to use it as it is inteded. Keeping logging simple and *just the right amount* is key. \n\nThe point is, do not overburden your app with log entries as it will most certainly backfire, and you won't care about those logs. If you really need to retain big volumes of logs, surely you're using the right tool for it as well.\n\nGetting this out of the way, let's crunch some numbers:\n\n_________________\n\n\n## Benchmarks\n\nSetting up a quick and easy benchmark test file similar to [`zlog`'s](https://github.com/zalgonoise/zlog), in [`benchmark/benchmark_test.go`](./benchmark/benchmark_test.go):\n\n```\n# with `prettybench`:\n\ngoos: linux\ngoarch: amd64\npkg: github.com/zalgonoise/logx/benchmark\ncpu: AMD Ryzen 3 PRO 3300U w/ Radeon Vega Mobile Gfx\nPASS\ncoverage: [no statements]\nbenchmark                                       iter       time/iter   bytes alloc         allocs\n---------                                       ----       ---------   -----------         ------\nBenchmarkLogger/Writing/SimpleText/LogX-4    916345   1605.00 ns/op      537 B/op    4 allocs/op\nBenchmarkLogger/Writing/SimpleJSON/LogX-4    644287   1824.00 ns/op      352 B/op    5 allocs/op\nBenchmarkLogger/Writing/ComplexText/LogX-4   253484   4982.00 ns/op     1271 B/op   24 allocs/op\nBenchmarkLogger/Writing/ComplexJSON/LogX-4   208918   5727.00 ns/op     1544 B/op   18 allocs/op\nok      github.com/zalgonoise/logx/benchmark   7.869s\n```\n\nWhen comparing these results to the vendor benchmark test in [`zlog`'s benchmarks summary](https://github.com/zalgonoise/zlog/tree/master/benchmark), it's clear that there is a major improvement when comparing to `zlog`, as well as being close to `zap` in number of allocations. Adding the results above for context, in an ordered list of tests:\n\n```\ngoos: linux\ngoarch: amd64\npkg: github.com/zalgonoise/zlog/benchmark\ncpu: AMD Ryzen 3 PRO 3300U w/ Radeon Vega Mobile Gfx\n\nPASS\ncoverage: [no statements]\nbenchmark                                                         iter        time/iter   bytes alloc         allocs\n---------                                                         ----        ---------   -----------         ------\nBenchmarkVendorLoggers/Writing/SimpleText/ZeroLogger-4         2570126     471.30 ns/op      156 B/op    0 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleText/StdLibLogger-4       2948067     412.70 ns/op       24 B/op    1 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleText/ZapLogger-4           827116    1396.00 ns/op       64 B/op    3 allocs/op\nBenchmarkLogger/Writing/SimpleText/LogX-4    \t\t\t916345    1605.00 ns/op      537 B/op    4 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleText/ZlogLogger-4          945510    1336.00 ns/op      368 B/op    9 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleText/LogrusLogger-4        273914    4253.00 ns/op      480 B/op   15 allocs/op\n\nBenchmarkVendorLoggers/Writing/SimpleJSON/ZeroLogger-4         5817523     317.00 ns/op       92 B/op    0 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleJSON/ZapLogger-4          1000000    1152.00 ns/op        0 B/op    0 allocs/op\nBenchmarkLogger/Writing/SimpleJSON/LogX-4    \t\t\t644287    1824.00 ns/op      352 B/op    5 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleJSON/ZlogLogger-4          425534    2815.00 ns/op      376 B/op    6 allocs/op\nBenchmarkVendorLoggers/Writing/SimpleJSON/LogrusLogger-4        203432    5298.00 ns/op     1080 B/op   22 allocs/op\n\nBenchmarkVendorLoggers/Writing/ComplexText/ZeroLogger-4         382442    2906.00 ns/op      288 B/op   11 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexText/ZapLogger-4          171844    6609.00 ns/op      848 B/op   21 allocs/op\nBenchmarkLogger/Writing/ComplexText/LogX-4   \t\t\t253484    4982.00 ns/op     1271 B/op   24 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexText/ZlogLogger-4         121747   11129.00 ns/op     3756 B/op   50 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexText/LogrusLogger-4        71154   14105.00 ns/op     2168 B/op   43 allocs/op\n\nBenchmarkVendorLoggers/Writing/ComplexJSON/ZeroLogger-4         388226    3722.00 ns/op      288 B/op   11 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexJSON/ZapLogger-4          231116    6320.00 ns/op      784 B/op   18 allocs/op\nBenchmarkLogger/Writing/ComplexJSON/LogX-4   \t\t\t208918    5727.00 ns/op     1544 B/op   18 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexJSON/ZlogLogger-4         115693   11486.00 ns/op     2680 B/op   40 allocs/op\nBenchmarkVendorLoggers/Writing/ComplexJSON/LogrusLogger-4       116692   11029.00 ns/op     2592 B/op   44 allocs/op\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Flogx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzalgonoise%2Flogx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Flogx/lists"}