{"id":26508632,"url":"https://github.com/rodaine/protoslog","last_synced_at":"2025-03-21T00:37:11.981Z","repository":{"id":222948523,"uuid":"758817120","full_name":"rodaine/protoslog","owner":"rodaine","description":"log/slog support for protobufs","archived":false,"fork":false,"pushed_at":"2025-02-10T15:59:19.000Z","size":57,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T16:48:22.439Z","etag":null,"topics":["golang","logging","protobuf","protocol-buffers","slog","structured-logging"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/rodaine/protoslog","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodaine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-17T06:51:47.000Z","updated_at":"2025-02-10T15:59:21.000Z","dependencies_parsed_at":"2024-02-17T08:22:09.418Z","dependency_job_id":"d6b48ae9-a560-4827-89cb-35fee591a2fa","html_url":"https://github.com/rodaine/protoslog","commit_stats":null,"previous_names":["rodaine/protoslog"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodaine%2Fprotoslog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodaine%2Fprotoslog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodaine%2Fprotoslog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodaine%2Fprotoslog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodaine","download_url":"https://codeload.github.com/rodaine/protoslog/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244717385,"owners_count":20498284,"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":["golang","logging","protobuf","protocol-buffers","slog","structured-logging"],"created_at":"2025-03-21T00:37:11.545Z","updated_at":"2025-03-21T00:37:11.967Z","avatar_url":"https://github.com/rodaine.png","language":"Go","readme":"# protoslog [![Go Reference](https://pkg.go.dev/badge/github.com/rodaine/protoslog.svg)](https://pkg.go.dev/github.com/rodaine/protoslog) [![CI](https://github.com/rodaine/protoslog/actions/workflows/ci.yaml/badge.svg)](https://github.com/rodaine/protoslog/actions/workflows/ci.yaml)\n\n`protoslog` provides utilities for using protocol buffer messages with the `log/slog` package introduced in Go 1.21.\n\n## Example\n\n`protoslog` operates against Protocol Buffer messages. Below, one might have such a `User` message:\n\n```protobuf\nsyntax=\"proto3\";\n\nimport \"google/protobuf/timestamp.proto\";\n\nmessage User {\n  fixed64 id = 1;\n  string email = 2 [debug_redact=true];\n  Status status = 3;\n  google.protobuf.Timestamp updated = 4;\n}\n\nenum Status {\n  UNSPECIFIED = 0;\n  ACTIVE = 1;\n  INACTIVE = 2;\n}\n```\n\n`protoslog` does **NOT** require any code generation (beyond the output of `protoc-gen-go`) to properly log a message:\n\n```go\npackage main\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/rodaine/protoslog\"\n\t\"github.com/rodaine/protoslog/internal/gen\"\n)\n\nfunc main() {\n\tmsg := \u0026gen.User{\n\t\tId:      123,\n\t\tEmail:   \"rodaine@github.com\",\n\t\tStatus:  gen.ACTIVE,\n\t\tUpdated: time.Now(),\n\t}\n\n\tslog.Info(\"hello\", protoslog.Message(\"user\", msg))\n}\n```\n\nOutputs:\n\n```text\n2022/11/08 15:28:26 INFO hello user.id=123 user.email=REDACTED user.status=ACTIVE user.updated=2022-11-08T15:28:26.000Z\n```\n\n## Field Value Types\n\nMessages are lazily converted into a `slog.GroupValue` with each of its populated field converted into a `slog.Attr` with the field name as the key and value produced based on its type (similar to the canonical JSON encoding rules)\n\n### Scalar Types\n\n- **bool**: `slog.BoolValue`\n- **floats**: `slog.Float64Value`\n- **bytes**: base64 encoded in a `slog.StringValue`\n- **string**: `slog.StringValue`\n- **enum**: `slog.StringValue` of the value name if it's defined, or `slog.Int64Value` otherwise\n- **signed integer**: `slog.Int64Value`\n- **unsigned integer**: `slog.Uint64Value`\n\n### Composite Types\n\nPopulated composite fields are encoded as a `slog.GroupValue`:\n\n- **message**: each field converted into a `slog.Attr` with its name as the key and the value recursively applying these rules\n- **repeated**: each item converted into a `slog.Attr` with its index string-ified as the key and the value recursively applying these rules\n- **map**: each entry converted into a `slog.Attr` with its key string-ified and the value recursively applying these rules\n\n### Well-Known Types (WKTs)\n\nSimilar to the canonical JSON encoding, some of the WKTs produce special-cased `slog.Value`:\n\n- **google.protobuf.NullValue**: empty `slog.Value{}` (equivalent of `nil`)\\\n- **google.protobuf.Timestamp**: `slog.TimeValue`\n- **google.protobuf.Duration**: `slog.DurationValue`\n- **wrappers**: it's `value` field, applying these rules\n- **google.protobuf.ListValue**: its `values` field, applying the repeated rule above\n- **google.protobuf.Struct**: its `fields` field, applying the map rule above\n- **google.protobuf.Value**: the field set in its `kind` oneof, applying these rules\n- **google.protobuf.Any**: see [Any WKT Resolution] below\n\n## Redaction\n\nMessages may contain personal identifiable information (PII), secrets, or\nsimilar data that should not be written into a log. Message fields can be\nannotated with the [debug_redact] option to identify such values. By default,\nprotoslog will redact these fields, with the behavior customizable via options.\n\nPopulated redacted fields are replaced with a `slog.StringValue(\"REDACTED\")`:\n\n```go\nmsg := \u0026gen.User{Email: \"personal@identifiable.info\"}\nslog.Info(\"default\", protoslog.Message(\"user\", msg))\n// Stderr: 2022/11/08 15:28:26 INFO default user.email=REDACTED\n```\n\nTo elide redacted fields instead of including them, `WithElideRedactions` can \nbe used:\n\n```go\nslog.Info(\"elide\", protoslog.Message(\"user\", msg, protoslog.WithElideRedactions()))\n// Stderr: 2022/11/08 15:28:26 INFO elide\n```\n\nRedaction may also be disabled via `WithDisableRedactions`:\n\n```go\nslog.Info(\"disable\", protoslog.Message(\"user\", msg, protoslog.WithDisableRedactions()))\n// Stderr: 2022/11/08 15:28:26 INFO disable email=personal@identifiable.info\n```\n\n## All Fields\n\nBy default, `protoslog` only emits fields that are populated on the message (via\nthe behavior of `protoreflect.Message#Has`):\n\n```go\nmsg := \u0026gen.Location{Latitude: 1.23}\nslog.Info(\"default\", protoslog.Message(\"loc\", msg))\n// Stderr: 2022/11/08 15:28:26 INFO default loc.latitude=1.23\n```\n\nTo emit all fields regardless of presence, use `WithAllFields`:\n\n```go\nslog.Info(\"all\", protoslog.Message(\"loc\", msg, protoslog.WithAllFields()))\n// Stderr: 2022/11/08 15:28:26 INFO all loc.latitude=1.23 loc.longitude=0\n```\n\nFor unpopulated \"nullable,\" repeated, and map fields, the zero `slog.Value`\nis emitted (which is equivalent to `nil`). All other fields emit their default \nvalues.\n\n## Any WKT Resolution\n\n`protoslog` emits the `Any` field's `type_url` with the key `@type`. By default,\n`protoslog` attempts to resolve the field's value and on success emits it:\n\n```go\nmsg := \u0026gen.User{Id: 123}\nanyPB, _ := anypb.New(msg)\nslog.Info(\"success\", protoslog.Message(\"any\", anyPB))\n// Stderr: 2022/11/08 15:28:26 INFO success any.@type=type.googleapis.com/User any.id=123\n```\n\nIf the inner value does not resolve to a `slog.GroupValue` (e.g., it's a WKT), the result is added as `@value`:\n\n```go\nmsg := durationpb.New(5*time.Second)\nanyPB, _ := anypb.New(msg)\nslog.Info(\"wkt\", protoslog.Message(\"any\", anyPB))\n// Stderr: 2022/11/08 15:28:26 INFO wkt any.@type=type.googleapis.com/google.protobuf.Duration any.@value=5s\n```\n\nIf the value cannot be resolved (either unknown or an error occurs), only the `@type` attribute will be present:\n\n```go\nanyPB := \u0026anypb.Any{TypeUrl: \"foobar\"}\nslog.Info(\"unknown\", protoslog.Message(\"any\", anyPB))\n// Stderr: 2022/11/08 15:28:26 INFO unknown any.@type=foobar\n```\n\nBy default, `protoslog` uses `protoregistry.GlobalTypes` to resolve Any WKTs. A custom resolver can be provided via `WithAnyResolver`:\n\n```go\nslog.Info(\"custom\", protoslog.Message(\"any\", anyPB, protoslog.WithAnyResolver(myResolver)))\n```\n\nTo skip resolving Any WKTs, use `WithSkipAnys`. Only the `@type` attribute will be emitted:\n\n```go\nslog.Info(\"skip\", protoslog.Message(\"any\", anyPB, protoslog.WithSkipAnys()))\n```\n\n## `slog` Handler\n\nIf a message is not wrapped via `protoslog`, it will be presented in the logs \nwith the behavior of `slog.AnyValue`. To ensure all messages are resolved \ncorrectly regardless, a `protoslog.Handler` can wrap a `slog.Handler`:\n\n```go\nhandler := protoslog.NewHandler(slog.Default().Handler())\nlogger := slog.New(handler)\n\nmsg := \u0026gen.User{Id: 123}\nlogger.Info(\"handler\", \"user\", msg)\n// Stderr: 2022/11/08 15:28:26 INFO handler user.id=123\n```\n\nThe options on `protoslog.Handler` supersede those on messages wrapped via other \n`protoslog` functions.\n\n## `protoc-gen-slog`\n\nTo make the generated message types produced by `protoc-gen-go` implement \n`slog.LogValuer`, `protoc-gen-slog` can be used to generate `LogValue` methods.\n\n```shell\ngo install github.com/rodaine/protoslog/protoc-gen-slog\n```\n\n### Buf CLI\n\nWhen using `buf`, ensure the `out` path and `opt` values are equivalent for both\n`protoc-gen-go` and `protoc-gen-slog` plugins:\n\n```yaml\n# buf.gen.yaml\nversion: v1\nplugins:\n  - plugin: buf.build/protocolbuffers/go:v1.32.0\n    out: gen\n    opt:\n      - paths=source_relative\n  - plugin: slog\n    out: gen\n    opt:\n      - paths=source_relative\n```\n\n### protoc\n\nWhen using `protoc`, ensure both plugin options and output path are equivalent:\n\n```shell\nprotoc \\\n  --go_out=\"$OUT\" \\\n  --slog_out=\"$OUT\" \\\n  $PROTOS\n```\n\n[debug_redact]: https://github.com/protocolbuffers/protobuf/blob/v22.0/src/google/protobuf/descriptor.proto#L630-L632","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodaine%2Fprotoslog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodaine%2Fprotoslog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodaine%2Fprotoslog/lists"}