{"id":13413327,"url":"https://github.com/samber/slog-multi","last_synced_at":"2026-02-02T20:12:14.634Z","repository":{"id":152434926,"uuid":"625972152","full_name":"samber/slog-multi","owner":"samber","description":"🚨 Design workflows of slog handlers: pipeline, middleware, fanout, routing, failover, load balancing...","archived":false,"fork":false,"pushed_at":"2024-09-09T18:30:51.000Z","size":171,"stargazers_count":340,"open_issues_count":1,"forks_count":15,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-09-09T22:56:11.690Z","etag":null,"topics":["attribute","error","errors","go","golang","handler","log","log-level","logger","middleware","slog","structured-logging"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/samber/slog-multi","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/samber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["samber"]}},"created_at":"2023-04-10T14:19:20.000Z","updated_at":"2024-09-09T18:30:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"0d0f41b5-0e14-43fd-858e-cc59e0fd5bda","html_url":"https://github.com/samber/slog-multi","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-multi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-multi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-multi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-multi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samber","download_url":"https://codeload.github.com/samber/slog-multi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247615377,"owners_count":20967184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["attribute","error","errors","go","golang","handler","log","log-level","logger","middleware","slog","structured-logging"],"created_at":"2024-07-30T20:01:37.816Z","updated_at":"2026-02-02T20:12:14.620Z","avatar_url":"https://github.com/samber.png","language":"Go","readme":"# slog-multi: Advanced Handler Composition for Go's Structured Logging (pipelining, fanout, routing, failover...)\n\n[![tag](https://img.shields.io/github/tag/samber/slog-multi.svg)](https://github.com/samber/slog-multi/releases)\n![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c)\n[![GoDoc](https://godoc.org/github.com/samber/slog-multi?status.svg)](https://pkg.go.dev/github.com/samber/slog-multi)\n![Build Status](https://github.com/samber/slog-multi/actions/workflows/test.yml/badge.svg)\n[![Go report](https://goreportcard.com/badge/github.com/samber/slog-multi)](https://goreportcard.com/report/github.com/samber/slog-multi)\n[![Coverage](https://img.shields.io/codecov/c/github/samber/slog-multi)](https://codecov.io/gh/samber/slog-multi)\n[![Contributors](https://img.shields.io/github/contributors/samber/slog-multi)](https://github.com/samber/slog-multi/graphs/contributors)\n[![License](https://img.shields.io/github/license/samber/slog-multi)](./LICENSE)\n\n**slog-multi** provides advanced composition patterns for Go's structured logging (`slog`). It enables you to build sophisticated logging workflows by combining multiple handlers with different strategies for distribution, routing, transformation, and error handling.\n\n## 🎯 Features\n\n- **🔄 Fanout**: Distribute logs to multiple handlers in parallel\n- **🛣️ Router**: Conditionally route logs based on custom criteria\n- **🎯 First Match**: Route logs to the first matching handler only\n- **🔄 Failover**: High-availability logging with automatic fallback\n- **⚖️ Load Balancing**: Distribute load across multiple handlers\n- **🔗 Pipeline**: Transform and filter logs with middleware chains\n- **🛡️ Error Recovery**: Graceful handling of logging failures\n\nMiddlewares:\n- **⚡ Inline Handlers**: Quick implementation of custom handlers\n- **🔧 Inline Middleware**: Rapid development of transformation logic\n\n\u003cdiv align=\"center\"\u003e\n  \u003chr\u003e\n  \u003csup\u003e\u003cb\u003eSponsored by:\u003c/b\u003e\u003c/sup\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://cast.ai/samuel\"\u003e\n    \u003cdiv\u003e\n      \u003cimg src=\"https://github.com/user-attachments/assets/502f8fa8-e7e8-4754-a51f-036d0443e694\" width=\"200\" alt=\"Cast AI\"\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      Cut Kubernetes \u0026 AI costs, boost application stability\n    \u003c/div\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://www.dash0.com?utm_campaign=148395251-samber%20github%20sponsorship\u0026utm_source=github\u0026utm_medium=sponsorship\u0026utm_content=samber\"\u003e\n    \u003cdiv\u003e\n      \u003cimg src=\"https://github.com/user-attachments/assets/b1f2e876-0954-4dc3-824d-935d29ba8f3f\" width=\"200\" alt=\"Dash0\"\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      100% OpenTelemetry-native observability platform\u003cbr\u003eSimple to use, built on open standards, and designed for full cost control\n    \u003c/div\u003e\n  \u003c/a\u003e\n  \u003chr\u003e\n\u003c/div\u003e\n\n**See also:**\n\n- [slog-multi](https://github.com/samber/slog-multi): `slog.Handler` chaining, fanout, routing, failover, load balancing...\n- [slog-formatter](https://github.com/samber/slog-formatter): `slog` attribute formatting\n- [slog-sampling](https://github.com/samber/slog-sampling): `slog` sampling policy\n- [slog-mock](https://github.com/samber/slog-mock): `slog.Handler` for test purposes\n\n**HTTP middlewares:**\n\n- [slog-gin](https://github.com/samber/slog-gin): Gin middleware for `slog` logger\n- [slog-echo](https://github.com/samber/slog-echo): Echo middleware for `slog` logger\n- [slog-fiber](https://github.com/samber/slog-fiber): Fiber middleware for `slog` logger\n- [slog-chi](https://github.com/samber/slog-chi): Chi middleware for `slog` logger\n- [slog-http](https://github.com/samber/slog-http): `net/http` middleware for `slog` logger\n\n**Loggers:**\n\n- [slog-zap](https://github.com/samber/slog-zap): A `slog` handler for `Zap`\n- [slog-zerolog](https://github.com/samber/slog-zerolog): A `slog` handler for `Zerolog`\n- [slog-logrus](https://github.com/samber/slog-logrus): A `slog` handler for `Logrus`\n\n**Log sinks:**\n\n- [slog-datadog](https://github.com/samber/slog-datadog): A `slog` handler for `Datadog`\n- [slog-betterstack](https://github.com/samber/slog-betterstack): A `slog` handler for `Betterstack`\n- [slog-rollbar](https://github.com/samber/slog-rollbar): A `slog` handler for `Rollbar`\n- [slog-loki](https://github.com/samber/slog-loki): A `slog` handler for `Loki`\n- [slog-sentry](https://github.com/samber/slog-sentry): A `slog` handler for `Sentry`\n- [slog-syslog](https://github.com/samber/slog-syslog): A `slog` handler for `Syslog`\n- [slog-logstash](https://github.com/samber/slog-logstash): A `slog` handler for `Logstash`\n- [slog-fluentd](https://github.com/samber/slog-fluentd): A `slog` handler for `Fluentd`\n- [slog-graylog](https://github.com/samber/slog-graylog): A `slog` handler for `Graylog`\n- [slog-quickwit](https://github.com/samber/slog-quickwit): A `slog` handler for `Quickwit`\n- [slog-slack](https://github.com/samber/slog-slack): A `slog` handler for `Slack`\n- [slog-telegram](https://github.com/samber/slog-telegram): A `slog` handler for `Telegram`\n- [slog-mattermost](https://github.com/samber/slog-mattermost): A `slog` handler for `Mattermost`\n- [slog-microsoft-teams](https://github.com/samber/slog-microsoft-teams): A `slog` handler for `Microsoft Teams`\n- [slog-webhook](https://github.com/samber/slog-webhook): A `slog` handler for `Webhook`\n- [slog-kafka](https://github.com/samber/slog-kafka): A `slog` handler for `Kafka`\n- [slog-nats](https://github.com/samber/slog-nats): A `slog` handler for `NATS`\n- [slog-parquet](https://github.com/samber/slog-parquet): A `slog` handler for `Parquet` + `Object Storage`\n- [slog-channel](https://github.com/samber/slog-channel): A `slog` handler for Go channels\n\n## 🚀 Installation\n\n```sh\ngo get github.com/samber/slog-multi\n```\n\n**Compatibility**: go \u003e= 1.21\n\nNo breaking changes will be made to exported APIs before v2.0.0.\n\n\u003e [!WARNING]\n\u003e Use this library carefully, log processing can be very costly (!)\n\u003e \n\u003e Excessive logging —with multiple processing steps and destinations— can introduce significant overhead, which is generally undesirable in performance-critical paths. Logging is always expensive, and sometimes, metrics or a sampling strategy are cheaper. The library itself does not generate extra load.\n\n## 💡 Usage\n\nGoDoc: [https://pkg.go.dev/github.com/samber/slog-multi](https://pkg.go.dev/github.com/samber/slog-multi)\n\n### Broadcast: `slogmulti.Fanout()`\n\nDistribute logs to multiple `slog.Handler` in parallel for maximum throughput and redundancy.\n\n```go\nimport (\n    \"net\"\n    slogmulti \"github.com/samber/slog-multi\"\n    \"log/slog\"\n    \"os\"\n    \"time\"\n)\n\nfunc main() {\n    logstash, _ := net.Dial(\"tcp\", \"logstash.acme:4242\")    // use github.com/netbrain/goautosocket for auto-reconnect\n    datadogHandler := slogdatadog.NewDatadogHandler(slogdatadog.Option{\n        APIKey: \"your-api-key\",\n        Service: \"my-service\",\n    })\n    stderr := os.Stderr\n\n    logger := slog.New(\n        slogmulti.Fanout(\n            slog.NewJSONHandler(logstash, \u0026slog.HandlerOptions{}),  // pass to first handler: logstash over tcp\n            slog.NewTextHandler(stderr, \u0026slog.HandlerOptions{}),    // then to second handler: stderr\n            datadogHandler,\n            // ...\n        ),\n    )\n\n    logger.\n        With(\n            slog.Group(\"user\",\n                slog.String(\"id\", \"user-123\"),\n                slog.Time(\"created_at\", time.Now()),\n            ),\n        ).\n        With(\"environment\", \"dev\").\n        With(\"error\", fmt.Errorf(\"an error\")).\n        Error(\"A message\")\n}\n```\n\nStderr output:\n\n```\ntime=2023-04-10T14:00:0.000000+00:00 level=ERROR msg=\"A message\" user.id=user-123 user.created_at=2023-04-10T14:00:0.000000+00:00 environment=dev error=\"an error\"\n```\n\nNetcat output:\n\n```json\n{\n\t\"time\":\"2023-04-10T14:00:0.000000+00:00\",\n\t\"level\":\"ERROR\",\n\t\"msg\":\"A message\",\n\t\"user\":{\n\t\t\"id\":\"user-123\",\n\t\t\"created_at\":\"2023-04-10T14:00:0.000000+00:00\"\n\t},\n\t\"environment\":\"dev\",\n\t\"error\":\"an error\"\n}\n```\n\n### Routing: `slogmulti.Router()`\n\nDistribute logs to all matching `slog.Handler` based on custom criteria like log level, attributes, or business logic.\n\n```go\nimport (\n    \"context\"\n    slogmulti \"github.com/samber/slog-multi\"\n    slogslack \"github.com/samber/slog-slack\"\n    \"log/slog\"\n    \"os\"\n)\n\nfunc main() {\n    slackChannelUS := slogslack.Option{Level: slog.LevelError, WebhookURL: \"xxx\", Channel: \"supervision-us\"}.NewSlackHandler()\n    slackChannelEU := slogslack.Option{Level: slog.LevelError, WebhookURL: \"xxx\", Channel: \"supervision-eu\"}.NewSlackHandler()\n    slackChannelAPAC := slogslack.Option{Level: slog.LevelError, WebhookURL: \"xxx\", Channel: \"supervision-apac\"}.NewSlackHandler()\n\n    consoleHandler := slog.NewTextHandler(os.Stderr, nil)\n\n    logger := slog.New(\n        slogmulti.Router().\n            Add(slackChannelUS, recordMatchRegion(\"us\")).\n            Add(slackChannelEU, recordMatchRegion(\"eu\")).\n            Add(slackChannelAPAC, recordMatchRegion(\"apac\")).\n            Add(consoleHandler, slogmulti.LevelIs(slog.LevelInfo, slog.LevelDebug)).\n            Handler(),\n    )\n\n    logger.\n        With(\"region\", \"us\").\n        With(\"pool\", \"us-east-1\").\n        Error(\"Server desynchronized\")\n}\n\nfunc recordMatchRegion(region string) func(ctx context.Context, r slog.Record) bool {\n    return func(ctx context.Context, r slog.Record) bool {\n        ok := false\n\n        r.Attrs(func(attr slog.Attr) bool {\n            if attr.Key == \"region\" \u0026\u0026 attr.Value.Kind() == slog.KindString \u0026\u0026 attr.Value.String() == region {\n                ok = true\n                return false\n            }\n\n            return true\n        })\n\n        return ok\n    }\n}\n```\n\n**Use Cases:**\n- Environment-specific logging (dev vs prod)\n- Level-based routing (errors to Slack, info to console)\n- Business logic routing (user actions vs system events)\n\n### First Match Routing: `Router().FirstMatch()`\n\nRoute logs to the **first matching handler only**, unlike regular routing which sends to all matching handlers. Perfect for priority-based routing where you want exactly one handler to receive each log.\n\n```go\nimport (\n    slogmulti \"github.com/samber/slog-multi\"\n    slogslack \"github.com/samber/slog-slack\"\n    \"log/slog\"\n)\n\nfunc main() {\n    queryChannel := slogslack.Option{Level: slog.LevelDebug, WebhookURL: \"xxx\", Channel: \"db-queries\"}.NewSlackHandler()\n    requestChannel := slogslack.Option{Level: slog.LevelError, WebhookURL: \"xxx\", Channel: \"service-requests\"}.NewSlackHandler()\n    influxdbChannel := slogslack.Option{Level: slog.LevelInfo, WebhookURL: \"xxx\", Channel: \"influxdb-metrics\"}.NewSlackHandler()\n    fallbackChannel := slogslack.Option{Level: slog.LevelError, WebhookURL: \"xxx\", Channel: \"logs\"}.NewSlackHandler()\n\n    logger := slog.New(\n        slogmulti.Router().\n            Add(queryChannel, slogmulti.AttrKindIs(\"query\", slog.KindString, \"args\", slog.KindAny)).\n            Add(requestChannel, slogmulti.AttrKindIs(\"method\", slog.KindString, \"body\", slog.KindAny)).\n            Add(influxdbChannel, slogmulti.AttrValueIs(\"scope\", \"influx\")).\n            Add(fallbackChannel).  // Catch-all for everything else\n            FirstMatch().           // ← Enable first-match routing\n            Handler(),\n    )\n\n    // Goes to queryChannel only (stops at first match)\n    logger.Debug(\"Executing SQL query\", \"query\", \"SELECT * FROM users WHERE id = ?\", \"args\", []int{1})\n\n    // Goes to requestChannel only (stops at first match)\n    logger.Error(\"Incoming request failed\", \"method\", \"POST\", \"body\", \"{'name':'test'}\")\n\n    // Goes to fallbackChannel (no other handlers matched)\n    logger.Error(\"An unexpected error occurred\")\n}\n```\n\n#### Built-in Predicates\n\n**Level predicates:**\n- `LevelIs(levels ...slog.Level)` - Match specific log levels\n- `LevelIsNot(levels ...slog.Level)` - Exclude specific log levels\n\n**Message predicates:**\n- `MessageIs(msg string)` - Exact message match\n- `MessageIsNot(msg string)` - Message doesn't match\n- `MessageContains(part string)` - Message contains substring\n- `MessageNotContains(part string)` - Message doesn't contain substring\n\n**Attribute predicates:**\n- `AttrValueIs(key, value, ...)` - Check attributes have exact values\n- `AttrKindIs(key, kind, ...)` - Check attributes have specific types\n\n### Failover: `slogmulti.Failover()`\n\nEnsure logging reliability by trying multiple handlers in order until one succeeds. Perfect for high-availability scenarios.\n\n```go\nimport (\n    \"net\"\n    slogmulti \"github.com/samber/slog-multi\"\n    \"log/slog\"\n    \"os\"\n    \"time\"\n)\n\n\nfunc main() {\n    // Create connections to multiple log servers\n    // ncat -l 1000 -k\n    // ncat -l 1001 -k\n    // ncat -l 1002 -k\n\n    // List AZs - use github.com/netbrain/goautosocket for auto-reconnect\n    logstash1, _ := net.Dial(\"tcp\", \"logstash.eu-west-3a.internal:1000\")\n    logstash2, _ := net.Dial(\"tcp\", \"logstash.eu-west-3b.internal:1000\")\n    logstash3, _ := net.Dial(\"tcp\", \"logstash.eu-west-3c.internal:1000\")\n\n    logger := slog.New(\n        slogmulti.Failover()(\n            slog.HandlerOptions{}.NewJSONHandler(logstash1, nil),    // Primary\n            slog.HandlerOptions{}.NewJSONHandler(logstash2, nil),    // Secondary\n            slog.HandlerOptions{}.NewJSONHandler(logstash3, nil),    // Tertiary\n        ),\n    )\n\n    logger.\n        With(\n            slog.Group(\"user\",\n                slog.String(\"id\", \"user-123\"),\n                slog.Time(\"created_at\", time.Now()),\n            ),\n        ).\n        With(\"environment\", \"dev\").\n        With(\"error\", fmt.Errorf(\"an error\")).\n        Error(\"A message\")\n}\n```\n\n**Use Cases:**\n- High-availability logging infrastructure\n- Disaster recovery scenarios\n- Multi-region deployments\n\n### Load balancing: `slogmulti.Pool()`\n\nDistribute logging load across multiple handlers using round-robin with randomization to increase throughput and provide redundancy.\n\n```go\nimport (\n    \"net\"\n    slogmulti \"github.com/samber/slog-multi\"\n    \"log/slog\"\n    \"os\"\n    \"time\"\n)\n\nfunc main() {\n    // Create multiple log servers\n    // ncat -l 1000 -k\n    // ncat -l 1001 -k\n    // ncat -l 1002 -k\n\n    // List AZs - use github.com/netbrain/goautosocket for auto-reconnect\n    logstash1, _ := net.Dial(\"tcp\", \"logstash.eu-west-3a.internal:1000\")\n    logstash2, _ := net.Dial(\"tcp\", \"logstash.eu-west-3b.internal:1000\")\n    logstash3, _ := net.Dial(\"tcp\", \"logstash.eu-west-3c.internal:1000\")\n\n    logger := slog.New(\n        slogmulti.Pool()(\n            // A random handler will be picked for each log\n            slog.HandlerOptions{}.NewJSONHandler(logstash1, nil),\n            slog.HandlerOptions{}.NewJSONHandler(logstash2, nil),\n            slog.HandlerOptions{}.NewJSONHandler(logstash3, nil),\n        ),\n    )\n\n    // High-volume logging\n    for i := 0; i \u003c 1000; i++ {\n        logger.\n            With(\n                slog.Group(\"user\",\n                    slog.String(\"id\", \"user-123\"),\n                    slog.Time(\"created_at\", time.Now()),\n                ),\n            ).\n            With(\"environment\", \"dev\").\n            With(\"error\", fmt.Errorf(\"an error\")).\n            Error(\"A message\")\n    }\n}\n```\n\n**Use Cases:**\n- High-throughput logging scenarios\n- Distributed logging infrastructure\n- Performance optimization\n\n### Recover errors: `slogmulti.RecoverHandlerError()`\n\nGracefully handle logging failures without crashing the application. Catches both panics and errors from handlers.\n\n```go\nimport (\n    \"context\"\n    slogformatter \"github.com/samber/slog-formatter\"\n    slogmulti \"github.com/samber/slog-multi\"\n    \"log/slog\"\n    \"os\"\n)\n\nrecovery := slogmulti.RecoverHandlerError(\n    func(ctx context.Context, record slog.Record, err error) {\n        // will be called only if subsequent handlers fail or return an error\n        log.Println(err.Error())\n    },\n)\nsink := NewSinkHandler(...)\n\nlogger := slog.New(\n    slogmulti.\n        Pipe(recovery).\n        Handler(sink),\n)\n\nerr := fmt.Errorf(\"an error\")\nlogger.Error(\"a message\",\n    slog.Any(\"very_private_data\", \"abcd\"),\n    slog.Any(\"user\", user),\n    slog.Any(\"err\", err))\n\n// outputs:\n// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg=\"a message\" error.message=\"an error\" error.type=\"*errors.errorString\" user=\"John doe\" very_private_data=\"********\"\n```\n\n### Pipelining: `slogmulti.Pipe()`\n\nTransform and filter logs using middleware chains. Perfect for data privacy, formatting, and cross-cutting concerns.\n\n```go\nimport (\n    \"context\"\n    slogmulti \"github.com/samber/slog-multi\"\n    \"log/slog\"\n    \"os\"\n    \"time\"\n)\n\nfunc main() {\n    // First middleware: format Go `error` type into an structured object {error: \"*myCustomErrorType\", message: \"could not reach https://a.b/c\"}\n    errorFormattingMiddleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {\n        record.Attrs(func(attr slog.Attr) bool {\n            if attr.Key == \"error\" \u0026\u0026 attr.Value.Kind() == slog.KindAny {\n                if err, ok := attr.Value.Any().(error); ok {\n                    record.AddAttrs(\n                        slog.String(\"error_type\", \"error\"),\n                        slog.String(\"error_message\", err.Error()),\n                    )\n                }\n            }\n            return true\n        })\n        return next(ctx, record)\n    })\n\n    // Second middleware: remove PII\n    gdprMiddleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {\n        record.Attrs(func(attr slog.Attr) bool {\n            if attr.Key == \"email\" || attr.Key == \"phone\" || attr.Key == \"created_at\" {\n                record.AddAttrs(slog.String(attr.Key, \"*********\"))\n            }\n            return true\n        })\n        return next(ctx, record)\n    })\n\n    // Final handler\n    sink := slog.NewJSONHandler(os.Stderr, \u0026slog.HandlerOptions{})\n\n    logger := slog.New(\n        slogmulti.\n            Pipe(errorFormattingMiddleware).\n            Pipe(gdprMiddleware).\n            // ...\n            Handler(sink),\n    )\n\n    logger.\n        With(\n            slog.Group(\"user\",\n                slog.String(\"id\", \"user-123\"),\n                slog.String(\"email\", \"user-123\"),\n                slog.Time(\"created_at\", time.Now()),\n            ),\n        ).\n        With(\"environment\", \"dev\").\n        Error(\"A message\",\n            slog.String(\"foo\", \"bar\"),\n            slog.Any(\"error\", fmt.Errorf(\"an error\")),\n        )\n}\n```\n\nStderr output:\n\n```json\n{\n    \"time\":\"2023-04-10T14:00:0.000000+00:00\",\n    \"level\":\"ERROR\",\n    \"msg\":\"A message\",\n    \"user\":{\n        \"email\":\"*******\",\n        \"phone\":\"*******\",\n        \"created_at\":\"*******\"\n    },\n    \"environment\":\"dev\",\n    \"foo\":\"bar\",\n    \"error\":{\n        \"type\":\"*myCustomErrorType\",\n        \"message\":\"an error\"\n    }\n}\n```\n\n**Use Cases:**\n- Data privacy and GDPR compliance\n- Error formatting and standardization\n- Log enrichment and transformation\n- Performance monitoring and metrics\n\n## 🔧 Advanced Patterns\n\n### Custom middleware\n\nMiddleware must match the following prototype:\n\n```go\ntype Middleware func(slog.Handler) slog.Handler\n```\n\nThe example above uses:\n- a custom middleware, [see here](./examples/pipe/gdpr.go)\n- an inline middleware, [see here](./examples/pipe/errors.go)\n\n\u003e **Note**: `WithAttrs` and `WithGroup` methods of custom middleware must return a new instance, not `this`.\n\n#### Inline handler\n\nInline handlers provide shortcuts to implement `slog.Handler` without creating full struct implementations.\n\n```go\nmdw := slogmulti.NewHandleInlineHandler(\n    // simulate \"Handle()\" method\n    func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error {\n        // Custom logic here\n        // [...]\n        return nil\n    },\n)\n```\n\n```go\nmdw := slogmulti.NewInlineHandler(\n    // simulate \"Enabled()\" method\n    func(ctx context.Context, groups []string, attrs []slog.Attr, level slog.Level) bool {\n        // Custom logic here\n        // [...]\n        return true\n    },\n    // simulate \"Handle()\" method\n    func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error {\n        // Custom logic here\n        // [...]\n        return nil\n    },\n)\n```\n\n#### Inline middleware\n\nInline middleware provides shortcuts to implement middleware functions that hook specific methods.\n\n#### Hook `Enabled()` Method\n\n```go\nmiddleware := slogmulti.NewEnabledInlineMiddleware(func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{\n    // Custom logic before calling next\n    if level == slog.LevelDebug {\n        return false // Skip debug logs\n    }\n    return next(ctx, level)\n})\n```\n\n#### Hook `Handle()` Method\n\n```go\nmiddleware := slogmulti.NewHandleInlineMiddleware(func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {\n    // Add timestamp to all logs\n    record.AddAttrs(slog.Time(\"logged_at\", time.Now()))\n    return next(ctx, record)\n})\n```\n\n#### Hook `WithAttrs()` Method\n\n```go\nmdw := slogmulti.NewWithAttrsInlineMiddleware(func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{\n    // Filter out sensitive attributes\n    filtered := make([]slog.Attr, 0, len(attrs))\n    for _, attr := range attrs {\n        if attr.Key != \"password\" \u0026\u0026 attr.Key != \"token\" {\n            filtered = append(filtered, attr)\n        }\n    }\n    return next(attrs)\n})\n```\n\n#### Hook `WithGroup()` Method\n\n```go\nmdw := slogmulti.NewWithGroupInlineMiddleware(func(name string, next func(string) slog.Handler) slog.Handler{\n    // Add prefix to group names\n    prefixedName := \"app.\" + name\n    return next(name)\n})\n```\n\n#### Complete Inline Middleware\n\n\u003e **Warning**: You should implement your own middleware for complex scenarios.\n\n```go\nmdw := slogmulti.NewInlineMiddleware(\n    func(ctx context.Context, level slog.Level, next func(context.Context, slog.Level) bool) bool{\n        // Custom logic here\n        // [...]\n        return next(ctx, level)\n    },\n    func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error{\n        // Custom logic here\n        // [...]\n        return next(ctx, record)\n    },\n    func(attrs []slog.Attr, next func([]slog.Attr) slog.Handler) slog.Handler{\n        // Custom logic here\n        // [...]\n        return next(attrs)\n    },\n    func(name string, next func(string) slog.Handler) slog.Handler{\n        // Custom logic here\n        // [...]\n        return next(name)\n    },\n)\n```\n\n## 💡 Best Practices\n\n### Performance Considerations\n\n- **Use Fanout sparingly**: Broadcasting to many handlers can impact performance\n- **Implement sampling**: For high-volume logs, consider sampling strategies\n- **Monitor handler performance**: Some handlers (like network-based ones) can be slow\n- **Use buffering**: Consider buffering for network-based handlers\n\n### Error Handling\n\n- **Always use error recovery**: Wrap handlers with `RecoverHandlerError`\n- **Implement fallbacks**: Use failover patterns for critical logging\n- **Monitor logging failures**: Track when logging fails to identify issues\n\n### Security and Privacy\n\n- **Redact sensitive data**: Use middleware to remove PII and secrets\n- **Validate log content**: Ensure logs don't contain sensitive information\n- **Use secure connections**: For network-based handlers, use TLS\n\n### Monitoring and Observability\n\n- **Add correlation IDs**: Include request IDs in logs for tracing\n- **Structured logging**: Use slog's structured logging features consistently\n- **Log levels**: Use appropriate log levels for different types of information\n\n## 🤝 Contributing\n\n- Ping me on twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :))\n- Fork the [project](https://github.com/samber/slog-multi)\n- Fix [open issues](https://github.com/samber/slog-multi/issues) or request new features\n\nDon't hesitate ;)\n\n```bash\n# Install some dev dependencies\nmake tools\n\n# Run tests\nmake test\n# or\nmake watch-test\n```\n\n## 👤 Contributors\n\n![Contributors](https://contrib.rocks/image?repo=samber/slog-multi)\n\n## 💫 Show your support\n\nIf this project helped you, please give it a ⭐️ on GitHub!\n\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber)\n\n## 📝 License\n\nCopyright © 2023 [Samuel Berthe](https://github.com/samber).\n\nThis project is [MIT](./LICENSE) licensed.\n","funding_links":["https://github.com/sponsors/samber"],"categories":["Go","General","Logging","日志记录"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Fslog-multi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamber%2Fslog-multi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Fslog-multi/lists"}