{"id":19561377,"url":"https://github.com/m-mizutani/zlog","last_synced_at":"2025-04-27T00:31:12.040Z","repository":{"id":43067495,"uuid":"412703617","full_name":"m-mizutani/zlog","owner":"m-mizutani","description":"Secure logger in Go to avoid output sensitive data in log","archived":true,"fork":false,"pushed_at":"2023-09-02T22:45:07.000Z","size":62,"stargazers_count":36,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-26T07:08:33.665Z","etag":null,"topics":["golang","logger","security"],"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/m-mizutani.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":"2021-10-02T05:50:40.000Z","updated_at":"2024-08-15T02:11:52.000Z","dependencies_parsed_at":"2024-06-18T20:13:33.889Z","dependency_job_id":"0e0e0c4e-4ea1-4aea-afba-a5775e3f3144","html_url":"https://github.com/m-mizutani/zlog","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m-mizutani%2Fzlog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m-mizutani%2Fzlog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m-mizutani%2Fzlog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m-mizutani%2Fzlog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/m-mizutani","download_url":"https://codeload.github.com/m-mizutani/zlog/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251072279,"owners_count":21532004,"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","logger","security"],"created_at":"2024-11-11T05:11:15.381Z","updated_at":"2025-04-27T00:31:11.806Z","avatar_url":"https://github.com/m-mizutani.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zlog [![Go Reference](https://pkg.go.dev/badge/github.com/m-mizutani/zlog.svg)](https://pkg.go.dev/github.com/m-mizutani/zlog) [![Vulnerability scan](https://github.com/m-mizutani/zlog/actions/workflows/trivy.yml/badge.svg)](https://github.com/m-mizutani/zlog/actions/workflows/trivy.yml) [![Unit test](https://github.com/m-mizutani/zlog/actions/workflows/test.yml/badge.svg)](https://github.com/m-mizutani/zlog/actions/workflows/test.yml) [![Security Scan](https://github.com/m-mizutani/zlog/actions/workflows/gosec.yml/badge.svg)](https://github.com/m-mizutani/zlog/actions/workflows/gosec.yml)\n\n\u003e [!IMPORTANT]\n\u003e zlog has been serving two key roles in Go language: structured logging and the removal of confidential values. However, with Go 1.21, [slog](https://pkg.go.dev/log/slog), the official structured logging mechanism, has been formally incorporated. In addition, slog is equipped with a feature called ReplaceAttr, which enables the removal of confidential values as well. Moving forward, we recommend using slog for structured logging and a newly created package called [masq](https://github.com/m-mizutani/masq) for the removal of confidential values. We have decided to gradually stop maintaining zlog, believing that it has fulfilled its role, and will soon archive the repository. Thank you for your patronage.\n\nA main distinct feature of `zlog` is secure logging that avoid to output secret/sensitive values to log. The feature reduce risk to store secret values (API token, password and such things) and sensitive data like PII (Personal Identifiable Information) such as address, phone number, email address and etc into logging storage.\n\n`zlog` also has major logger features: contextual logging, leveled logging, structured message, show stacktrace of error. See following usage for mote detail.\n\nFor example, adding `zlog:\"secret\"` tag to `Email` field in structure.\n\n```go\ntype myRecord struct {\n\tID    string\n\tEMail string `zlog:\"secret\"`\n}\nrecord := myRecord{\n\tID:    \"m-mizutani\",\n\tEMail: \"mizutani@hey.com\",\n}\n\nlogger := newExampleLogger(zlog.WithFilters(filter.Tag()))\nlogger.With(\"record\", record).Info(\"Got record\")\n```\n\nThen, output following log with filtered `Email` field.\n\n```bash\n [info] Got record\n \"record\" =\u003e zlog_test.myRecord{\n   ID:    \"m-mizutani\",\n   EMail: \"[filtered]\",\n }\n```\n\n## Usage\n\n### Basic example\n\n```go\nimport \"github.com/m-mizutani/zlog\"\n\ntype myRecord struct {\n\tName  string\n\tEMail string\n}\n\nfunc main() {\n\trecord := myRecord{\n\t\tName:  \"mizutani\",\n\t\tEMail: \"mizutani@hey.com\",\n\t}\n\n\tlogger := zlog.New()\n\tlogger.With(\"record\", record).Info(\"hello my logger\")\n}\n```\n\n`zlog.New()` creates a new logger with default settings (console formatter).\n\n![example](https://user-images.githubusercontent.com/605953/139518107-e1b1deb0-f9c8-4439-b527-7e3ae4e575a0.png)\n\n#### Contextual logging\n\n`Logger.With(key string, value interface{})` method allows contextual logging that output not only message but also related variables. The method saves a pair of key and value and output it by pretty printing (powered by [k0kubun/pp](https://github.com/k0kubun/pp)).\n\n\n### Filter sensitive data\n\n#### By specified value\n\nThis function is designed to hide limited and predetermined secret values, such as API tokens that the application itself uses to call external services.\n\n```go\nconst issuedToken = \"abcd1234\"\nauthHeader := \"Authorization: Bearer \" + issuedToken\n\nlogger := newExampleLogger(zlog.WithFilters(\n\tfilter.Value(issuedToken),\n))\n\nlogger.With(\"auth\", authHeader).Info(\"send header\")\n// Output:  [info] send header\n// \"auth\" =\u003e \"Authorization: Bearer [filtered]\"\n```\n\n#### By field name\n\nThis filter hides the secret value if it matches the field name of the specified structure.\n\n```go\ntype myRecord struct {\n\tID    string\n\tPhone string\n}\nrecord := myRecord{\n\tID:    \"m-mizutani\",\n\tPhone: \"090-0000-0000\",\n}\n\nlogger := newExampleLogger(\n\tzlog.WithFilters(filter.Field(\"Phone\")),\n)\nlogger.With(\"record\", record).Info(\"Got record\")\n// Output:  [info] Got record\n// \"record\" =\u003e zlog_test.myRecord{\n//   ID:    \"m-mizutani\",\n//   Phone: \"[filtered]\",\n// }\n```\n\n#### By custom type\n\nYou can define a type that you want to keep secret, and then specify it in a Filter to prevent it from being displayed. The advantage of this method is that copying a value from a custom type to the original type requires a cast, making it easier for the developer to notice unintentional copying. (Of course, this is not a perfect solution because you can still copy by casting.)\n\nThis method may be useful for use cases where you need to use secret values between multiple structures.\n\n```go\ntype password string\ntype myRecord struct {\n\tID    string\n\tEMail password\n}\nrecord := myRecord{\n\tID:    \"m-mizutani\",\n\tEMail: \"abcd1234\",\n}\n\nlogger := newExampleLogger(\n\tzlog.WithFilters(filter.Type(password(\"\"))),\n)\nlogger.With(\"record\", record).Info(\"Got record\")\n// Output:  [info] Got record\n// \"record\" =\u003e zlog_test.myRecord{\n//   ID:    \"m-mizutani\",\n//   EMail: \"[filtered]\",\n// }\n```\n\n#### By struct tag\n\n```go\ntype myRecord struct {\n\tID    string\n\tEMail string `zlog:\"secret\"`\n}\nrecord := myRecord{\n\tID:    \"m-mizutani\",\n\tEMail: \"mizutani@hey.com\",\n}\n\nlogger := newExampleLogger(zlog.WithFilters(filter.Tag()))\nlogger.With(\"record\", record).Info(\"Got record\")\n// Output:  [info] Got record\n// \"record\" =\u003e zlog_test.myRecord{\n//   ID:    \"m-mizutani\",\n//   EMail: \"[filtered]\",\n// }\n```\n\n#### By data pattern (e.g. personal information)\n\nThis is an experimental effort and not a very reliable method, but it may have some value. It is a way to detect and hide personal information that should not be output based on a predefined pattern, like many DLP (Data Leakage Protection) solutions.\n\nIn the following example, we use a filter that we wrote to detect Japanese phone numbers. The content is just a regular expression. Currently zlog does not have as many patterns as the existing DLP solutions, and the patterns are not accurate enough. However we hope to expand it in the future if necessary.\n\n```go\ntype myRecord struct {\n\tID    string\n\tPhone string\n}\nrecord := myRecord{\n\tID:    \"m-mizutani\",\n\tPhone: \"090-0000-0000\",\n}\n\nlogger := newExampleLogger(zlog.WithFilters(filter.PhoneNumber()))\nlogger.With(\"record\", record).Info(\"Got record\")\n// Output:  [info] Got record\n// \"record\" =\u003e zlog_test.myRecord{\n//   ID:    \"m-mizutani\",\n//   Phone: \"[filtered]\",\n// }\n```\n\n### Customize Log output format\n\nzlog has `Emitter` that is interface to output log event. A default emitter is `Writer` that has `Formatter` to format log message, values and error information and `io.Writer` to output formatted log data.\n\n#### Change io.Writer\n\nFor example, change output to standard error.\n\n```go\nlogger := logger := zlog.New(\n\tzlog.WithEmitter(\n\t\tzlog.NewWriterWith(zlog.NewConsoleFormatter(), os.Stderr),\n\t),\n)\nlogger.Info(\"output to stderr\")\n```\n\n#### Change formatter\n\nFor example, use JsonFormatter to output structured json.\n\n```go\nlogger := zlog.New(\n\tzlog.WithEmitter(\n\t\tzlog.NewWriterWith(zlog.NewJsonFormatter(), os.Stdout),\n\t),\n)\n\nlogger.Info(\"output as json format\")\n// Output: {\"timestamp\":\"2021-10-02T14:58:11.791258\",\"level\":\"info\",\"msg\":\"output as json format\",\"values\":null}\n```\n\n#### Use original emitter\n\nYou can use your original Emitter that has `Emit(*zlog.Log) error` method.\n\n```go\ntype myEmitter struct {\n\tseq int\n}\n\nfunc (x *myEmitter) Emit(ev *zlog.Log) error {\n\tx.seq++\n\tprefix := []string{\"＼(^o^)／\", \"(´・ω・｀)\", \"(・∀・)\"}\n\tfmt.Println(prefix[x.seq%3], ev.Msg)\n\treturn nil\n}\n\nfunc ExampleEmitter() {\n\tlogger := zlog.New(\n\t\tzlog.WithEmitter(\u0026myEmitter{}),\n\t)\n\n\tlogger.Info(\"waiwai\")\n\tlogger.Info(\"heyhey\")\n\t// Output:\n\t// ＼(^o^)／ waiwai\n\t// (´・ω・｀) heyhey\n}\n```\n\n### Leveled Logging\n\nzlog allows for logging at the following levels.\n\n- `trace` (`zlog.LevelTrace`)\n- `debug` (`zlog.LevelDebug`)\n- `info` (`zlog.LevelInfo`)\n- `warn` (`zlog.LevelWarn`)\n- `error` (`zlog.LevelError`)\n\nLog level can be changed by modifying `Logger.Level` or calling `Logger.SetLogLevel()` method.\n\nModifying `Logger.Level` directly:\n```go\n\tlogger = zlog.New(zlog.WithLogLevel(\"info\"))\n\tlogger.Debug(\"debugging\")\n\tlogger.Info(\"information\")\n\t// Output: [info] information\n```\n\nUsing `SetLogLevel()` method. Log level is case insensitive.\n```go\n\tlogger.SetLogLevel(\"InFo\")\n\n\tlogger.Debug(\"debugging\")\n\tlogger.Info(\"information\")\n\t// Output: [info] information\n```\n\n### Error handling\n\n`Logger.Err(err error)` outputs not only error message but also stack trace and error related values.\n\n#### Output stack trace\n\nSupport below error packages.\n\n- [github.com/pkg/errors](https://github.com/pkg/errors)\n- [github.com/m-mizutani/goerr](https://github.com/m-mizutani/goerr)\n\n```go\nfunc crash() error {\n\treturn errors.New(\"oops\")\n}\n\nfunc main() {\n\tlogger := zlog.New()\n\tif err := crash(); err != nil {\n\t\tlogger.Err(err).Error(\"failed\")\n\t}\n}\n\n// Output:\n// [error] failed\n//\n// ------------------\n// *errors.fundamental: oops\n//\n// [StackTrace]\n// github.com/m-mizutani/zlog_test.crash\n// \t/Users/mizutani/.ghq/github.com/m-mizutani/zlog_test/main.go:xx\n// github.com/m-mizutani/zlog_test.main\n// \t/Users/mizutani/.ghq/github.com/m-mizutani/zlog_test/main.go:xx\n// runtime.main\n//\t/usr/local/Cellar/go/1.17/libexec/src/runtime/proc.go:255\n// runtime.goexit\n//\t/usr/local/Cellar/go/1.17/libexec/src/runtime/asm_amd64.s:1581\n// ------------------\n```\n\n#### Output error related values\n\n```go\nfunc crash(args string) error {\n\treturn goerr.New(\"oops\").With(\"args\", args)\n}\n\nfunc main() {\n\tlogger := zlog.New()\n\tif err := crash(\"hello\"); err != nil {\n\t\tlogger.Err(err).Error(\"failed\")\n\t}\n}\n\n// Output:\n// [error] failed\n//\n// ------------------\n// *goerr.Error: oops\n//\n// (snip)\n//\n// [Values]\n// args =\u003e \"hello\"\n// ------------------\n```\n\n\n## License\n\n- MIT License\n- Author: Masayoshi Mizutani \u003cmizutani@hey.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm-mizutani%2Fzlog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fm-mizutani%2Fzlog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm-mizutani%2Fzlog/lists"}