{"id":13413326,"url":"https://github.com/samber/slog-formatter","last_synced_at":"2025-05-07T16:06:25.356Z","repository":{"id":153271067,"uuid":"628659641","full_name":"samber/slog-formatter","owner":"samber","description":"🚨 slog: Attribute formatting","archived":false,"fork":false,"pushed_at":"2024-09-09T18:30:23.000Z","size":104,"stargazers_count":106,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-09-10T22:33:30.740Z","etag":null,"topics":["anonymization","error","formatter","formatting","go","golang","handler","log-level","logger","logging","middleware","pii","slog","structured-logging"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/samber/slog-formatter","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-16T16:27:42.000Z","updated_at":"2024-09-09T18:30:27.000Z","dependencies_parsed_at":"2023-09-23T09:49:13.263Z","dependency_job_id":"415ac312-b2d9-41b0-af5b-017857eb477a","html_url":"https://github.com/samber/slog-formatter","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-formatter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-formatter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-formatter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Fslog-formatter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samber","download_url":"https://codeload.github.com/samber/slog-formatter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243639551,"owners_count":20323509,"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":["anonymization","error","formatter","formatting","go","golang","handler","log-level","logger","logging","middleware","pii","slog","structured-logging"],"created_at":"2024-07-30T20:01:37.795Z","updated_at":"2025-03-14T20:12:32.567Z","avatar_url":"https://github.com/samber.png","language":"Go","readme":"\n# slog: Attribute formatting\n\n[![tag](https://img.shields.io/github/tag/samber/slog-formatter.svg)](https://github.com/samber/slog-formatter/releases)\n![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c)\n[![GoDoc](https://godoc.org/github.com/samber/slog-formatter?status.svg)](https://pkg.go.dev/github.com/samber/slog-formatter)\n![Build Status](https://github.com/samber/slog-formatter/actions/workflows/test.yml/badge.svg)\n[![Go report](https://goreportcard.com/badge/github.com/samber/slog-formatter)](https://goreportcard.com/report/github.com/samber/slog-formatter)\n[![Coverage](https://img.shields.io/codecov/c/github/samber/slog-formatter)](https://codecov.io/gh/samber/slog-formatter)\n[![Contributors](https://img.shields.io/github/contributors/samber/slog-formatter)](https://github.com/samber/slog-formatter/graphs/contributors)\n[![License](https://img.shields.io/github/license/samber/slog-formatter)](./LICENSE)\n\nCommon formatters for [slog](https://pkg.go.dev/log/slog) library + helpers for building your own.\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://quickwit.io?utm_campaign=github_sponsorship\u0026utm_medium=referral\u0026utm_content=samber-slog-formatter\u0026utm_source=github\"\u003e\n    \u003cdiv\u003e\n      \u003cimg src=\"https://github.com/samber/oops/assets/2951285/49aaaa2b-b8c6-4f21-909f-c12577bb6a2e\" width=\"240\" alt=\"Quickwit\"\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      Cloud-native search engine for observability - An OSS alternative to Splunk, Elasticsearch, Loki, and Tempo.\n    \u003c/div\u003e\n  \u003c/a\u003e\n  \u003chr\u003e\n\u003c/div\u003e\n\n**Handlers:**\n- [NewFormatterHandler](#NewFormatterHandler): main handler\n- [NewFormatterMiddleware](#NewFormatterMiddleware): compatible with `slog-multi` middlewares\n- [RecoverHandlerError](#RecoverHandlerError): catch panics and error from handlers\n\n**Common formatters:**\n- [TimeFormatter](#TimeFormatter): transforms a `time.Time` into a readable string\n- [UnixTimestampFormatter](#UnixTimestampFormatter): transforms a `time.Time` into a unix timestamp.\n- [TimezoneConverter](#TimezoneConverter): set a `time.Time` to a different timezone\n- [ErrorFormatter](#ErrorFormatter): transforms a go error into a readable error\n- [HTTPRequestFormatter](#HTTPRequestFormatter-and-HTTPResponseFormatter): transforms a *http.Request into a readable object\n- [HTTPResponseFormatter](#HTTPRequestFormatter-and-HTTPResponseFormatter): transforms a *http.Response into a readable object\n- [PIIFormatter](#PIIFormatter): hide private Personal Identifiable Information (PII)\n- [IPAddressFormatter](#IPAddressFormatter): hide ip address from logs\n- [FlattenFormatterMiddleware](#FlattenFormatterMiddleware): returns a formatter middleware that flatten attributes recursively\n\n**Custom formatter:**\n- [Format](#Format): pass any attribute into a formatter\n- [FormatByKind](#FormatByKind): pass attributes matching `slog.Kind` into a formatter\n- [FormatByType](#FormatByType): pass attributes matching generic type into a formatter\n- [FormatByKey](#FormatByKey): pass attributes matching key into a formatter\n- [FormatByFieldType](#FormatByFieldType): pass attributes matching both key and generic type into a formatter\n- [FormatByGroup](#FormatByGroup): pass attributes under a group into a formatter\n- [FormatByGroupKey](#FormatByGroupKey): pass attributes under a group and matching key, into a formatter\n- [FormatByGroupKeyType](#FormatByGroupKeyType): pass attributes under a group, matching key and matching a generic type, into a formatter\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## 🚀 Install\n\n```sh\ngo get github.com/samber/slog-formatter\n```\n\n**Compatibility**: go \u003e= 1.21\n\nNo breaking changes will be made to exported APIs before v2.0.0.\n\n⚠️ Warnings:\n- in some case, you should consider implementing `slog.LogValuer` instead of using this library.\n- use this library carefully, log processing can be very costly (!)\n\n## 🚀 Getting started\n\nThe following example has 3 formatters that anonymize data, format errors and format user. 👇\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\nformatter1 := slogformatter.FormatByKey(\"very_private_data\", func(v slog.Value) slog.Value {\n    return slog.StringValue(\"***********\")\n})\nformatter2 := slogformatter.ErrorFormatter(\"error\")\nformatter3 := slogformatter.FormatByType(func(u User) slog.Value {\n\treturn slog.StringValue(fmt.Sprintf(\"%s %s\", u.firstname, u.lastname))\n})\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(\n        slog.NewTextHandler(os.Stdout, nil),\n    ),\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## 💡 Spec\n\nGoDoc: [https://pkg.go.dev/github.com/samber/slog-formatter](https://pkg.go.dev/github.com/samber/slog-formatter)\n\n### NewFormatterHandler\n\nReturns a slog.Handler that applies formatters to.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\ntype User struct {\n\temail     string\n\tfirstname string\n\tlastname  string\n}\n\nformatter1 := slogformatter.FormatByKey(\"very_private_data\", func(v slog.Value) slog.Value {\n    return slog.StringValue(\"***********\")\n})\nformatter2 := slogformatter.ErrorFormatter(\"error\")\nformatter3 := slogformatter.FormatByType(func(u User) slog.Value {\n\treturn slog.StringValue(fmt.Sprintf(\"%s %s\", u.firstname, u.lastname))\n})\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(\n        slog.NewTextHandler(os.StdErr, nil),\n    ),\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### NewFormatterMiddleware\n\nReturns a `slog-multi` middleware that applies formatters to.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\tslogmulti \"github.com/samber/slog-multi\"\n\t\"log/slog\"\n)\n\nformatter1 := slogformatter.FormatByKey(\"very_private_data\", func(v slog.Value) slog.Value {\n    return slog.StringValue(\"***********\")\n})\nformatter2 := slogformatter.ErrorFormatter(\"error\")\nformatter3 := slogformatter.FormatByType(func(u User) slog.Value {\n\treturn slog.StringValue(fmt.Sprintf(\"%s %s\", u.firstname, u.lastname))\n})\n\nformattingMiddleware := slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)\nsink := slog.NewJSONHandler(os.Stderr, slog.HandlerOptions{})\n\nlogger := slog.New(\n    slogmulti.\n        Pipe(formattingMiddleware).\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### RecoverHandlerError\n\nReturns a `slog.Handler` that recovers from panics or error of the chain of handlers.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\tslogmulti \"github.com/samber/slog-multi\"\n\t\"log/slog\"\n)\n\nrecovery := slogformatter.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### TimeFormatter\n\nTransforms a `time.Time` into a readable string.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.TimeFormatter(time.DateTime, time.UTC),\n)\n```\n\n### UnixTimestampFormatter\n\nTransforms a `time.Time` into a unix timestamp.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.UnixTimestampFormatter(time.Millisecond),\n)\n```\n\n### TimezoneConverter\n\nSet a `time.Time` to a different timezone.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.TimezoneConverter(time.UTC),\n)\n```\n\n### ErrorFormatter\n\nTransforms a Go error into a readable error.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(\n        slogformatter.ErrorFormatter(\"error\"),\n    )(\n        slog.NewTextHandler(os.Stdout, nil),\n    ),\n)\n\nerr := fmt.Errorf(\"an error\")\nlogger.Error(\"a message\", slog.Any(\"error\", err))\n\n// outputs:\n// {\n//   \"time\":\"2023-04-10T14:00:0.000000+00:00\",\n//   \"level\": \"ERROR\",\n//   \"msg\": \"a message\",\n//   \"error\": {\n//     \"message\": \"an error\",\n//     \"type\": \"*errors.errorString\"\n//     \"stacktrace\": \"main.main()\\n\\t/Users/samber/src/github.com/samber/slog-formatter/example/example.go:108 +0x1c\\n\"\n//   }\n// }\n```\n\n### HTTPRequestFormatter and HTTPResponseFormatter\n\nTransforms *http.Request and *http.Response into readable objects.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(\n        slogformatter.HTTPRequestFormatter(false),\n        slogformatter.HTTPResponseFormatter(false),\n    )(\n        slog.NewJSONHandler(os.Stdout, nil),\n    ),\n)\n\nreq, _ := http.NewRequest(http.MethodGet, \"https://api.screeb.app\", nil)\nreq.Header.Set(\"Content-Type\", \"application/json\")\nreq.Header.Set(\"X-TOKEN\", \"1234567890\")\n\nres, _ := http.DefaultClient.Do(req)\n\nlogger.Error(\"a message\",\n    slog.Any(\"request\", req),\n    slog.Any(\"response\", res))\n```\n\n### PIIFormatter\n\nHides private Personal Identifiable Information (PII).\n\nIDs are kept as is. Values longer than 5 characters have a plain text prefix.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(\n        slogformatter.PIIFormatter(\"user\"),\n    )(\n        slog.NewTextHandler(os.Stdout, nil),\n    ),\n)\n\nlogger.\n    With(\n        slog.Group(\n            \"user\",\n            slog.String(\"id\", \"bd57ffbd-8858-4cc4-a93b-426cef16de61\"),\n            slog.String(\"email\", \"foobar@example.com\"),\n            slog.Group(\n                \"address\",\n                slog.String(\"street\", \"1st street\"),\n                slog.String(\"city\", \"New York\"),\n                slog.String(\"country\", \"USA\"),\n                slog.Int(\"zip\", 12345),\n            ),\n        ),\n    ).\n    Error(\"an error\")\n\n// outputs:\n// {\n//   \"time\":\"2023-04-10T14:00:0.000000+00:00\",\n//   \"level\": \"ERROR\",\n//   \"msg\": \"an error\",\n//   \"user\": {\n//     \"id\": \"bd57ffbd-8858-4cc4-a93b-426cef16de61\",\n//     \"email\": \"foob*******\",\n//     \"address\": {\n//       \"street\": \"1st *******\",\n//       \"city\": \"New *******\",\n//       \"country\": \"*******\",\n//       \"zip\": \"*******\"\n//     }\n//   }\n// }\n```\n\n### IPAddressFormatter\n\nTransforms an IP address into \"********\".\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\t\"log/slog\"\n)\n\nlogger := slog.New(\n    slogformatter.NewFormatterHandler(\n        slogformatter.IPAddressFormatter(\"ip_address\"),\n    )(\n        slog.NewTextHandler(os.Stdout, nil),\n    ),\n)\n\nlogger.\n    With(\"ip_address\", \"1.2.3.4\").\n    Error(\"an error\")\n\n// outputs:\n// {\n//   \"time\":\"2023-04-10T14:00:0.000000+00:00\",\n//   \"level\": \"ERROR\",\n//   \"msg\": \"an error\",\n//   \"ip_address\": \"*******\",\n// }\n```\n\n### FlattenFormatterMiddleware\n\nA formatter middleware that flatten attributes recursively.\n\n```go\nimport (\n\tslogformatter \"github.com/samber/slog-formatter\"\n\tslogmulti \"github.com/samber/slog-multi\"\n\t\"log/slog\"\n)\n\nlogger := slog.New(\n    slogmulti.\n        Pipe(slogformatter.FlattenFormatterMiddlewareOptions{Separator: \".\", Prefix: \"attrs\", IgnorePath: false}.NewFlattenFormatterMiddlewareOptions()).\n        Handler(slog.NewJSONHandler(os.Stdout, nil)),\n)\n\nlogger.\n    With(\"email\", \"samuel@acme.org\").\n    With(\"environment\", \"dev\").\n    WithGroup(\"group1\").\n    With(\"hello\", \"world\").\n    WithGroup(\"group2\").\n    With(\"hello\", \"world\").\n    Error(\"A message\", \"foo\", \"bar\")\n\n// outputs:\n// {\n//   \"time\": \"2023-05-20T22:14:55.857065+02:00\",\n//   \"level\": \"ERROR\",\n//   \"msg\": \"A message\",\n//   \"attrs.email\": \"samuel@acme.org\",\n//   \"attrs.environment\": \"dev\",\n//   \"attrs.group1.hello\": \"world\",\n//   \"attrs.group1.group2.hello\": \"world\",\n//   \"foo\": \"bar\"\n// }\n```\n\n### Format\n\nPass every attributes into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.Format(func(groups []string, key string, value slog.Value) slog.Value {\n        // hide everything under \"user\" group\n        if lo.Contains(groups, \"user\") {\n            return slog.StringValue(\"****\")\n        }\n\n        return value\n    }),\n)\n```\n\n### FormatByKind\n\nPass attributes matching `slog.Kind` into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByKind(slog.KindDuration, func(value slog.Value) slog.Value {\n        return ...\n    }),\n)\n```\n\n### FormatByType\n\nPass attributes matching generic type into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    // format a custom error type\n    slogformatter.FormatByType[*customError](func(err *customError) slog.Value {\n        return slog.GroupValue(\n            slog.Int(\"code\", err.code),\n            slog.String(\"message\", err.msg),\n        )\n    }),\n    // format other errors\n    slogformatter.FormatByType[error](func(err error) slog.Value {\n        return slog.GroupValue(\n            slog.Int(\"code\", err.Error()),\n            slog.String(\"type\", reflect.TypeOf(err).String()),\n        )\n    }),\n)\n```\n\n⚠️ Consider implementing `slog.LogValuer` when possible:\n\n```go\ntype customError struct {\n    ...\n}\n\nfunc (customError) Error() string {\n    ...\n}\n\n// implements slog.LogValuer\nfunc (customError) LogValue() slog.Value {\n\treturn slog.StringValue(...)\n}\n```\n\n### FormatByKey\n\nPass attributes matching key into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByKey(\"abcd\", func(value slog.Value) slog.Value {\n        return ...\n    }),\n)\n```\n\n### FormatByFieldType\n\nPass attributes matching both key and generic type into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByFieldType[User](\"user\", func(u User) slog.Value {\n        return ...\n    }),\n)\n```\n\n### FormatByGroup\n\nPass attributes under a group into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByGroup([]{\"user\", \"address\"}, func(attr []slog.Attr) slog.Value {\n        return ...\n    }),\n)\n```\n\n### FormatByGroupKey\n\nPass attributes under a group and matching key, into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByGroupKey([]{\"user\", \"address\"}, \"country\", func(value slog.Value) slog.Value {\n        return ...\n    }),\n)\n```\n\n### FormatByGroupKeyType\n\nPass attributes under a group, matching key and matching a generic type, into a formatter.\n\n```go\nslogformatter.NewFormatterHandler(\n    slogformatter.FormatByGroupKeyType[string]([]{\"user\", \"address\"}, \"country\", func(value string) slog.Value {\n        return ...\n    }),\n)\n```\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-formatter)\n- Fix [open issues](https://github.com/samber/slog-formatter/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-formatter)\n\n## 💫 Show your support\n\nGive a ⭐️ if this project helped you!\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":["Formatting","Logging","日志记录"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Fslog-formatter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamber%2Fslog-formatter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Fslog-formatter/lists"}