{"id":37201615,"url":"https://github.com/pod32g/simple-logger","last_synced_at":"2026-01-14T23:15:04.748Z","repository":{"id":253842615,"uuid":"844674160","full_name":"pod32g/simple-logger","owner":"pod32g","description":"Simple logger implementation in Go Lang","archived":false,"fork":false,"pushed_at":"2025-11-08T07:39:45.000Z","size":58,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-08T08:24:05.291Z","etag":null,"topics":["go","golang","log","logger","logging","simple"],"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/pod32g.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2024-08-19T18:36:11.000Z","updated_at":"2025-11-08T06:30:17.000Z","dependencies_parsed_at":"2024-08-22T02:35:36.108Z","dependency_job_id":null,"html_url":"https://github.com/pod32g/simple-logger","commit_stats":null,"previous_names":["pod32g/simple-logger"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/pod32g/simple-logger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pod32g%2Fsimple-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pod32g%2Fsimple-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pod32g%2Fsimple-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pod32g%2Fsimple-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pod32g","download_url":"https://codeload.github.com/pod32g/simple-logger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pod32g%2Fsimple-logger/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28437946,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"last_error":"SSL_read: 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":["go","golang","log","logger","logging","simple"],"created_at":"2026-01-14T23:15:03.938Z","updated_at":"2026-01-14T23:15:04.703Z","avatar_url":"https://github.com/pod32g.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Simple Logger\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Go Reference](https://pkg.go.dev/badge/github.com/pod32g/simple-logger.svg)](https://pkg.go.dev/github.com/pod32g/simple-logger)\n[![Go Report Card](https://goreportcard.com/badge/github.com/pod32g/simple-logger)](https://goreportcard.com/report/github.com/pod32g/simple-logger)\n\n## Description\n\n**Simple Logger** is a lightweight, flexible logging library for Go (Golang) that supports multiple log levels, customizable output formats, including plain text and JSON, and allows for user-defined custom formats. It is designed to be easy to integrate into your projects, with minimal configuration required.\n\n## Features\n\n- Supports multiple log levels: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`.\n- Customizable output destinations (e.g., stdout, stderr, or files).\n- Supports plain text, JSON, and custom log formats.\n- Simple API for setting log levels, outputs, and formats.\n- Dynamic configuration updates at runtime.\n- Thread-safe logging: concurrent log calls are serialized to keep entries intact, with an opt-out switch when you control the writer.\n- Optional caller information, disabled by default to minimize overhead.\n- Secure file output: logs written via `ApplyConfig` use `0600` permissions.\n- Structured fields via helper functions (`String`, `Int`, `Bool`, `Any`, etc.) for nicer JSON output.\n- Context-aware logging helpers to pull request-scoped metadata from `context.Context`.\n- Configurable sampling controls to keep noisy hot paths under control.\n- Hooks and multi-sink outputs for forwarding logs to additional destinations.\n- Optional asynchronous mode with configurable buffers and drop policies.\n- Hook filters and async stats so you can limit callbacks and monitor queue health.\n- Hot reload helpers for watching config files or reacting to config pushes.\n\n## Installation\n\nYou can install the Simple Logger package using `go get`:\n\n```bash\ngo get github.com/pod32g/simple-logger\n```\n\n## Usage\n\n### Basic Example\n\nHere’s a simple example of how to use Simple Logger in your project:\n\n```go\npackage main\n\nimport (\n\tlog \"github.com/pod32g/simple-logger\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Create a new logger instance with the default formatter\n\tlogger := log.NewLogger(os.Stdout, log.INFO, \u0026log.DefaultFormatter{})\n\tdefer logger.Close()\n\n\t// Log messages at different levels\n\tlogger.Debug(\"This is a debug message\")\n\tlogger.Info(\"This is an info message\")\n\tlogger.Warn(\"This is a warning message\")\n\tlogger.Error(\"This is an error message\")\n\tlogger.Fatal(\"This is a fatal message\") // This will log the message and exit the application\n}\n```\n\n### Example: Using `LoggerConfig`\n\nYou can configure the logger using the `LoggerConfig` struct for more control over logging behavior:\n\n```go\npackage main\n\nimport (\n    log \"github.com/pod32g/simple-logger\"\n)\n\nfunc main() {\n    config := log.LoggerConfig{\n        Level:        log.DEBUG,\n        Output:       \"stdout\",\n        Format:       \"json\",\n        EnableCaller: true,\n        SyncWrites:   true, // disable when you control the writer and need maximum throughput\n    }\n\n    logger := log.ApplyConfig(config)\n    defer logger.Close()\n\n    logger.Debug(\"This is a debug message with caller info.\")\n    logger.Info(\"This is an info message in JSON format.\")\n    logger.Warn(\"This is a warning message.\")\n    logger.Error(\"This is an error message.\")\n}\n```\n\n### Configuring Log Levels\n\nYou can set the logging level to control the verbosity of the logger. Available levels are `DEBUG`, `INFO`, `WARN`, `ERROR`, and `FATAL`.\n\n#### Example: Changing Log Level at Runtime\n\n```go\npackage main\n\nimport (\n    log \"github.com/pod32g/simple-logger\"\n    \"os\"\n)\n\nfunc main() {\n    logger := log.NewLogger(os.Stdout, log.INFO, \u0026log.DefaultFormatter{})\n    defer logger.Close()\n\n    logger.Info(\"Initial log level is Info.\")\n\n    // Changing log level to Debug\n    logger.SetLevel(log.DEBUG)\n    logger.Debug(\"Now logging at Debug level.\")\n}\n```\n\n### Logging to a File\n\nYou can log messages to a file by specifying the filename in the `Output` field of the `LoggerConfig` struct:\n\n```go\npackage main\n\nimport (\n    log \"github.com/pod32g/simple-logger\"\n)\n\nfunc main() {\n    config := log.LoggerConfig{\n        Level:        log.INFO,\n        Output:       \"app.log\",  // Specify the filename here\n        Format:       \"text\",\n        EnableCaller: false,\n        SyncWrites:   true,\n    }\n\n    logger := log.ApplyConfig(config)\n    defer logger.Close()\n\n    logger.Info(\"This message will be logged to a file.\")\n}\n```\n\nAlternatively, you can change the log output to a file or any other `io.Writer`:\n\n```go\npackage main\n\nimport (\n\tlog \"github.com/pod32g/simple-logger\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Open a file for logging\n\tfile, err := os.OpenFile(\"app.log\", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to open log file\")\n\t}\n\tdefer file.Close()\n\n\t// Create a new logger instance that writes to the file with the default formatter\n\tlogger := log.NewLogger(file, log.INFO, \u0026log.DefaultFormatter{})\n\tdefer logger.Close()\n\n\tlogger.Info(\"Logging to a file now!\")\n}\n```\n\n### Using a Custom Formatter\n\nYou can create and use a custom formatter by implementing the `CustomFormatter` interface:\n\n```go\npackage main\n\nimport (\n\tlog \"github.com/pod32g/simple-logger\"\n\t\"fmt\"\n)\n\nfunc main() {\n\tconfig := log.DefaultConfig()\n\tconfig.Format = \"custom\"\n\tconfig.Custom = \u0026MyCustomFormatter{} // Provide your custom formatter\n\n\tlogger := log.ApplyConfig(config)\n\tdefer logger.Close()\n\n\tlogger.Info(\"This is an info message with a custom format.\")\n\tlogger.Debug(\"This is a debug message with a custom format.\")\n}\n\n// MyCustomFormatter is a sample custom formatter\ntype MyCustomFormatter struct{}\n\nfunc (f *MyCustomFormatter) Format(level log.LogLevel, message string) string {\n\treturn fmt.Sprintf(\"**CUSTOM LOG** [%s] %s\\n\", logLevelToString(level), message)\n}\n\nfunc logLevelToString(level log.LogLevel) string {\n\tswitch level {\n\tcase log.DEBUG:\n\t\treturn \"DEBUG\"\n\tcase log.INFO:\n\t\treturn \"INFO\"\n\tcase log.WARN:\n\t\treturn \"WARN\"\n\tcase log.ERROR:\n\t\treturn \"ERROR\"\n\tcase log.FATAL:\n\t\treturn \"FATAL\"\n\tdefault:\n\t\treturn \"UNKNOWN\"\n\t}\n}\n```\n\n### Structured Logging with Fields\n\nUse the field helpers to emit structured key/value pairs alongside your message.\nThe default formatter renders them as `key=value`, while the JSON formatter\nadds them as additional properties.\n\n```go\nlogger := log.NewLogger(os.Stdout, log.INFO, \u0026log.JSONFormatter{})\ndefer logger.Close()\n\nlogger.InfoFields(\n\t\"user login\",\n\tlog.String(\"user\", \"alice\"),\n\tlog.Int(\"attempt\", 3),\n\tlog.Bool(\"success\", true),\n)\n```\n\nField helpers include `String`, `Int`, `Int64`, `Uint`, `Float64`, `Bool`,\n`Error`, and `Any` for arbitrary values. You can mix traditional variadic calls\nwith structured logging as needed.\n\n#### Custom Field Encoders\n\nUse `RegisterFieldEncoder` to customize how specific types render in text and\nJSON output:\n\n```go\nlog.RegisterFieldEncoder[time.Duration](\n    func(d time.Duration) (string, bool) { return d.String(), true },\n    func(d time.Duration) (interface{}, bool) { return d.Seconds(), true },\n)\n\nlogger.InfoFields(\"timed\", log.Any(\"duration\", 150*time.Millisecond))\n```\n\nBuilt-in encoders already cover `time.Duration`, `time.Time`, and `error`.\n\n### Context-Aware Logging\n\nAttach request metadata to a `context.Context` and have it automatically included\nwith every log entry.\n\n```go\nctx := log.WithFields(context.Background(),\n\tlog.String(\"request_id\", rid),\n\tlog.String(\"user\", userID),\n)\n\nlogger.InfoContext(ctx, \"processing payment\",\n\tlog.String(\"invoice\", invoiceID),\n)\n```\n\nUse `SetContextExtractor` to plug in a custom extractor when you already embed\nstructured data in a different context key.\n\n### Sampling \u0026 Rate Limiting\n\nInstall a sampler when you want to suppress noisy log entries without changing call\nsites. For example, log only every other message:\n\n```go\nlogger.SetSampler(log.NewEveryNSampler(2))\n```\n\nProvide your own implementation by satisfying the `Sampler` interface or using\n`SamplerFunc` for quick custom logic.\n\n### Hooks \u0026 Multi-Sink Outputs\n\nForward log entries to additional systems—metrics, alerts, or alternate writers.\n\n```go\nlogger.SetOutputs(os.Stdout, auditFile)\n\nlogger.AddHook(log.HookFunc(func(level log.LogLevel, msg string, fields []log.Field) {\n\tmetrics.Inc(level, msg)\n}))\n```\n\nHooks run after sampling and before the final write, receiving the resolved\nmessage and structured fields.\n\n### Hook Filters \u0026 Async Stats\n\nLimit callbacks to specific levels or field predicates using hook options:\n\n```go\nlogger.AddHook(metricsHook,\n    log.WithHookLevels(log.ERROR, log.FATAL),\n    log.WithHookFilter(func(level log.LogLevel, message string, fields []log.Field) bool {\n        return strings.Contains(message, \"payment\")\n    }),\n)\n```\n\nAsync helpers expose queue metrics:\n\n```go\nstats := logger.AsyncStats()\nfmt.Printf(\"length=%d dropped=%d\\n\", stats.QueueLength, stats.Dropped)\n```\n\nUse `SetDropStrategy` to switch between dropping new entries, dropping oldest, or\nblocking when the queue is full.\n\n### Hot Reload Helpers\n\nReload configuration from a JSON file or your own control plane without wiring\nup boilerplate:\n\n```go\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n\nif err := log.WatchConfigFileForLogger(ctx, logger, \"./logger.json\", 2*time.Second, func(err error) {\n    logger.Error(\"config reload failed\", log.Error(\"error\", err))\n}); err != nil {\n    panic(err)\n}\n```\n\nYou can also push configs through a channel:\n\n```go\nconfigs := make(chan log.LoggerConfig, 1)\ngo log.ReloadLoggerFromChannel(ctx, logger, configs, func(err error) {\n    logger.Warn(\"config update rejected\", log.Error(\"error\", err))\n})\n\nconfigs \u003c- log.LoggerConfig{Level: log.DEBUG, Output: \"stdout\", Format: \"json\"}\n```\n\nBoth helpers reuse `ConfigureLogger` under the hood, so they understand rotation,\nformatters, and other options.\n\n### Additional Examples\n\nSeveral runnable examples demonstrate integrations beyond the basics:\n\n- `example/http_middleware`: Wrap standard `net/http` handlers with structured request logging.\n- `example/grpc_interceptor`: Attach a unary interceptor that records request IDs, latency, and errors.\n- `example/cli_dynamic`: Small CLI that watches config files and accepts live tweaks from STDIN.\n\n### End-to-End Tests\n\nIntegration tests under `e2e/` exercise configuration reload paths, async drop\nmetrics, hook filters, and bridge integrations end-to-end. Run\n\n```bash\ngo test ./e2e\n```\n\nor `go test ./...` to include them in the full suite.\n\n### Additional QA Checks\n\n- Concurrency safety: `go test -race ./...`\n- Static analysis: `staticcheck ./...` (install via `go install honnef.co/go/tools/cmd/staticcheck@latest`)\n- Fuzzing JSON formatter stability: `go test -fuzz=FuzzJSONFormatterFormat -run=^$`\n\n### Asynchronous Logging\n\nMove formatting/writes off the hot path by enabling the async worker:\n\n```go\nlogger.EnableAsync(log.AsyncOptions{\n    QueueSize:     1024,\n    DropStrategy:  log.DropNew,\n    BatchSize:     64,\n    FlushInterval: 10 * time.Millisecond,\n})\n\n// ... later\nlogger.DisableAsync() // flushes and stops the worker\n```\n\nChoose a drop strategy when the queue is full: `DropNew`, `DropOldest`, or\n`BlockWhenFull`. Batching lets the worker drain entries in chunks, while\n`FlushInterval` guarantees partially filled batches still reach the sink. Query\n`logger.AsyncStats()` to watch queue length and drops, and adjust behaviour at\nruntime with `SetDropStrategy`.\n\n### Bridging to slog\n\nUse the `bridge/slogbridge` package to route `slog` output into `simple-logger`:\n\n```go\nhandler := slogbridge.NewHandler(logger, slog.LevelInfo)\nlogger := slog.New(handler)\nlogger.Info(\"hello\", slog.String(\"user\", \"alice\"))\n```\n\nStructured attributes, groups, and context metadata are preserved.\n\n### OTLP Export Hook\n\nForward log entries to an OTLP collector by attaching the `otlp` hook:\n\n```go\nexp := otlpexporter.New(...)\nhook := otlp.NewHook(exp, otlp.WithServiceName(\"checkout\"))\nlogger.AddHook(hook)\n```\n\nThe hook converts log entries into OTLP `ResourceLogs`; you can adapt any exporter\nimplementing the simple `otlp.Exporter` interface.\n\n### File Rotation\n\nBuilt-in rotation mirrors `lumberjack.Logger` options:\n\n```go\ncfg := log.DefaultConfig()\ncfg.Output = \"app.log\"\ncfg.Rotation.Enable = true\ncfg.Rotation.MaxSize = 50   // MB\ncfg.Rotation.MaxAge = 14    // days\ncfg.Rotation.MaxBackups = 5\ncfg.Rotation.Compress = true\n\nlogger := log.ApplyConfig(cfg)\n```\n\nYou can also manage rotation manually by constructing your own `*lumberjack.Logger`\nand passing it to `SetOutputWithCloser`.\n\n## Managing Logger Lifecycle\n\nLoggers may own resources such as open files. When you create a logger via `ApplyConfig`—or use `SetOutputWithCloser`—always close it when you are finished:\n\n```go\nlogger := log.ApplyConfig(log.DefaultConfig())\ndefer logger.Close()\n```\n\nTo hand the logger a resource to manage explicitly, supply a closer:\n\n```go\nfile, err := os.OpenFile(\"app.log\", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)\nif err != nil {\n\tlog.Fatal(err)\n}\nlogger.SetOutputWithCloser(file, file)\n```\n\n`SetOutput` remains available for writers that do not require cleanup.\n\n## Tuning Synchronization\n\nBy default, `Logger` serializes writes to guarantee line integrity even when the underlying writer is not concurrency-safe. When you control the destination and know it can handle concurrent access (for example, a sharded writer or a buffered channel), you can disable synchronization to squeeze out a bit more throughput:\n\n```go\nlogger.SetSynchronized(false)\n```\n\nThe same setting is exposed in `LoggerConfig` as `SyncWrites` and via the environment variable `LOG_SYNC_WRITES`. Remember that disabling synchronization shifts the responsibility for atomic writes to your writer implementation.\n\n### Allocation-Free Helpers\n\nIf you already have preformatted strings or a single additional value, use the `*String`/`*1` helpers to avoid the slice allocation that variadic calls introduce:\n\n```go\nlogger.InfoString(\"application started\")\nlogger.Info1(\"user-count\", userCount)\n```\n\nThe classic variadic methods still work for convenience; mix and match based on your hot paths.\n\n## Comparison\n\n| Library          | Caller Info (default) | Write Sync (default) | JSON Support | Notable Focus               |\n|------------------|------------------------|----------------------|--------------|-----------------------------|\n| simple-logger    | off                    | on                   | built-in     | Lightweight, speed-first    |\n| uber-go/zap      | off                    | on                   | built-in     | High-performance structure  |\n| sirupsen/logrus  | on                     | on                   | via hook     | Feature-rich, pluggable     |\n| rs/zerolog       | off                    | off                  | native       | Zero-allocation structured  |\n| go.uber.org/zap* | off                    | on                   | built-in     | Structured logging (sugared)|\n\n`simple-logger` aims to bridge the gap between extremely fast structured loggers like `zerolog` and drop-in libraries such as `logrus`, keeping a minimal API while offering caller info and write serialization that can be toggled off when absolute throughput matters.\n\n## Benchmarks\n\nAll numbers below were gathered with `go test -bench Benchmark -benchmem` on\nmacOS (Apple M4 Pro, ARM64) using Go 1.22.3. The benchmark suite lives in\n`benchmark_compare_test.go` so you can reproduce locally.\n\n```bash\n$ go test -bench Benchmark -benchmem\nBenchmarkSimpleLogger-14           8703645       134.4 ns/op     200 B/op       5 allocs/op\nBenchmarkSimpleLoggerNoSync-14     9231789       131.5 ns/op     200 B/op       5 allocs/op\nBenchmarkZapSugar-14               6581017       185.1 ns/op      32 B/op       2 allocs/op\nBenchmarkLogrus-14                 1958559       600.5 ns/op     488 B/op      16 allocs/op\nBenchmarkZerolog-14               25791891        45.66 ns/op      0 B/op       0 allocs/op\nBenchmarkLoggerDefault-14          1480904       813.6 ns/op     199 B/op       5 allocs/op\nBenchmarkLoggerNoCaller-14         9050629       134.5 ns/op     200 B/op       5 allocs/op\nBenchmarkFmtSprintf-14            27270996        44.97 ns/op      39 B/op       2 allocs/op\n```\n\n### Takeaways\n\n- With caller lookup disabled (the default) `simple-logger` remains faster than\n  the sugared `zap` logger while keeping a simpler API.\n- Disabling `SyncWrites` still has little effect when writing to `io.Discard`; the\n  critical section is short. The toggle is useful only when the destination writer\n  itself is the bottleneck.\n- `zerolog` remains the zero-allocation champion for structured logging; use it\n  when absolute minimum overhead matters and you are comfortable with its API.\n- `logrus` trades speed for flexibility. Compared to it, `simple-logger` offers\n  a ~5× throughput advantage while preserving a familiar API.\n- Caller lookup has been optimized but still costs ~5× more than the default path.\n  Enable it only when file/line metadata is required.\n\nEven without caller information, the logger performs more work than\n`fmt.Sprintf` because it writes to an `io.Writer` and formats timestamps. For\nabsolute maximum throughput, make sure `EnableCaller` remains `false` and only\nset `SyncWrites` to `false` when the underlying writer safely handles concurrent\naccess.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! If you have ideas, suggestions, or bug fixes, please open an issue or submit a pull request.\n\n## Contact\n\nFor any questions or issues, please reach out via GitHub.\n\n---\n\nHappy logging!\n\n## Known Limitations\n\n- **Resource management:** `Logger.ApplyConfig` and `SetOutputWithCloser` can own file handles; remember to call `logger.Close()` when you are done to release resources promptly.\n- **Caller information overhead:** Enabling caller reporting requires walking the call stack, which adds latency to every log call. The default configuration leaves caller reporting disabled to avoid this cost.\n- **JSON caller resolution:** `JSONFormatter` now looks up caller information in line with the text formatter, but costs remain higher when caller tracking is enabled.\n\n### Environment Variables\n\n| Variable            | Description                                         |\n|---------------------|-----------------------------------------------------|\n| `LOG_LEVEL`         | Overrides the log level (`DEBUG`..`FATAL`).         |\n| `LOG_OUTPUT`        | `stdout`, `stderr`, or a file path.                 |\n| `LOG_FORMAT`        | `text`, `json`, or `custom`.                        |\n| `LOG_ENABLE_CALLER` | `true`/`false` to include caller information.       |\n| `LOG_SYNC_WRITES`   | `true`/`false` to control write serialization.      |\n| `LOG_COLORIZE`      | `true`/`false` to colorize text formatter output.   |\n| `LOG_TIME_FORMAT`   | Go time layout applied to timestamps (e.g. `2006-01-02T15:04:05Z07:00`). |\n| `LOG_INCLUDE_STACKTRACE` | `true`/`false` to append stacktraces on error/fatal logs. |\n| `LOG_ROTATE`       | `true`/`false` to enable built-in file rotation.         |\n| `LOG_ROTATE_MAX_SIZE` | Max file size in MB before rotation (default 100).    |\n| `LOG_ROTATE_MAX_AGE`  | Max age in days before old files are removed (default 30). |\n| `LOG_ROTATE_MAX_BACKUPS` | Number of old files to keep (default 7).          |\n| `LOG_ROTATE_COMPRESS` | `true`/`false` to gzip rotated logs (default true).   |\n\n### Runtime Reconfiguration\n\nUse `ConfigureLogger` to hot-swap formatter, level, and output without rebuilding loggers:\n\n```go\nlogger := log.NewLogger(os.Stdout, log.INFO, \u0026log.DefaultFormatter{})\n\ncfg := log.LoggerConfig{\n    Level:    log.DEBUG,\n    Format:   \"json\",\n    Output:   \"stdout\",\n    SyncWrites: true,\n}\n\nif _, err := log.ConfigureLogger(logger, cfg); err != nil {\n    log.NewLogger(os.Stderr, log.ERROR, \u0026log.DefaultFormatter{}).Error(\"reconfigure failed\", err)\n}\n```\n\nCalling `ConfigureLogger(nil, cfg)` is equivalent to `ApplyConfig(cfg)` and returns a brand new logger.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpod32g%2Fsimple-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpod32g%2Fsimple-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpod32g%2Fsimple-logger/lists"}