{"id":21865220,"url":"https://github.com/gildas/go-logger","last_synced_at":"2026-06-05T01:01:15.708Z","repository":{"id":43703370,"uuid":"175622626","full_name":"gildas/go-logger","owner":"gildas","description":"Bunyan logger for Go","archived":false,"fork":false,"pushed_at":"2026-05-21T04:10:21.000Z","size":673,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-21T10:11:43.897Z","etag":null,"topics":["bunyan","golang","logging","node-bunyan"],"latest_commit_sha":null,"homepage":null,"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/gildas.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-03-14T12:59:23.000Z","updated_at":"2026-05-21T04:09:52.000Z","dependencies_parsed_at":"2023-12-02T11:29:51.341Z","dependency_job_id":"0b2f70f0-ff7b-440e-8d5b-9252d5d3a6df","html_url":"https://github.com/gildas/go-logger","commit_stats":null,"previous_names":[],"tags_count":61,"template":false,"template_full_name":null,"purl":"pkg:github/gildas/go-logger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gildas","download_url":"https://codeload.github.com/gildas/go-logger/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-logger/sbom","scorecard":{"id":426781,"data":{"date":"2025-08-11","repo":{"name":"github.com/gildas/go-logger","commit":"bdbe8b80af26d7b570998b00c4a9898a7c51211f"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/5 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/snyk.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/snyk.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/snyk.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/snyk.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/snyk.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/snyk.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/snyk.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:35: update your workflow using https://app.stepsecurity.io/secureworkflow/gildas/go-logger/test.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":10,"reason":"SAST tool detected","details":["Info: SAST configuration detected: Snyk","Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T02:23:57.326Z","repository_id":43703370,"created_at":"2025-08-19T02:23:57.326Z","updated_at":"2025-08-19T02:23:57.326Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33926275,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["bunyan","golang","logging","node-bunyan"],"created_at":"2024-11-28T04:15:12.623Z","updated_at":"2026-06-05T01:01:15.685Z","avatar_url":"https://github.com/gildas.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-logger\n\n![GoVersion](https://img.shields.io/github/go-mod/go-version/gildas/go-logger)\n[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat-square)](https://pkg.go.dev/github.com/gildas/go-logger)\n[![License](https://img.shields.io/github/license/gildas/go-logger)](https://github.com/gildas/go-logger/blob/master/LICENSE)\n[![Report](https://goreportcard.com/badge/github.com/gildas/go-logger)](https://goreportcard.com/report/github.com/gildas/go-logger)  \n\n![master](https://img.shields.io/badge/branch-master-informational)\n[![Test](https://github.com/gildas/go-logger/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/gildas/go-logger/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/gildas/go-logger/branch/master/graph/badge.svg?token=gFCzS9b7Mu)](https://codecov.io/gh/gildas/go-logger/branch/master)\n\n![dev](https://img.shields.io/badge/branch-dev-informational)\n[![Test](https://github.com/gildas/go-logger/actions/workflows/test.yml/badge.svg?branch=dev)](https://github.com/gildas/go-logger/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/gildas/go-logger/branch/dev/graph/badge.svg?token=gFCzS9b7Mu)](https://codecov.io/gh/gildas/go-logger/branch/dev)\n\ngo-logger is a logging library based on [node-bunyan](https://github.com/trentm/node-bunyan).\n\nThe output is compatible with the `bunyan` log reader application from that `node` package.\n\n## Usage\n\nYou first start by creating a `Logger` object that will operate on a `Stream`.\n\n```go\npackage main\n\nimport \"github.com/gildas/go-logger\"\n\nvar Log = logger.Create(\"myapp\")\n```\n\nThen, you can log messages to the same levels from `node-bunyan`:\n\n```go\nLog.Tracef(\"This is a message at the trace level for %s\", myObject)\nLog.Debugf(\"This is a message at the debug level for %s\", myObject)\nLog.Infof(\"This is a message at the trace level for %s\", myObject)\nLog.Warnf(\"This is a message at the warn level for %s\", myObject)\nLog.Errorf(\"This is a message at the error level for %s\", myObject, err)\nLog.Fatalf(\"This is a message at the fatal level for %s\", myObject, err)\n```\n\nNote the `err` variable (must implement the [error](https://blog.golang.org/error-handling-and-go) interface) used with the last two log calls. By just adding it to the list of arguments at Error or Fatal level while not mentioning it in the format string will tell the `Logger` to spit that error in a bunyan [Record field](https://github.com/trentm/node-bunyan#log-record-fields).\n\nMore generally, [Record fields](https://github.com/trentm/node-bunyan#log-record-fields) can be logged like this:\n\n```go\nLog.Record(\"myObject\", myObject).Infof(\"Another message about my object\")\nLog.Record(\"?myObject\", myObject).Infof(\"Another message about my object, show myObject even if it is nil\")\nLog.Recordf(\"myObject\", \"format %s %+v\". myObject.ID(), myObject).Infof(\"This record uses a formatted value\")\n\nlog := Log.Record(\"dynamic\", func() interface{} { return myObject.Callme() })\n\nlog.Infof(\"This is here\")\nlog.Infof(\"That is there\")\n```\n\nIn the second example, the prefix `?` tells the `Logger` to log the record if the value is `nil`, empty string, nil UUID, or implements `IsNil() bool` returning true. By default, the `Logger` will not log these values.\n\nIn the last example, the code `myObject.Callme()` will be executed each time *log* is used to write a message.\nThis is used, as an example, to add a timestamp to the log's `Record`.\n\nIn addition to the [Bunyan core fields](https://github.com/trentm/node-bunyan#core-fields), this library adds a couple of Record Fields:\n\n- `topic` can be used for stuff like types or general topics (e.g.: \"http\")\n- `scope` can be used to scope logging within a topic, like a `func` or a portion of code.\n\nWhen the `Logger` is created its *topic* and *scope* are set to \"main\".\n\nHere is a simple example how [Record fields](https://github.com/trentm/node-bunyan#log-record-fields) can be used with a type:\n\n```go\ntype Stuff struct {\n    Field1 string\n    Field2 int\n    Logger *logger.Logger // So Stuff carries its own logger\n}\n\nfunc (s *Stuff) SetLogger(l *logger.Logger) {\n    s.Logger = l.Topic(\"stuff\").Scope(\"stuff\")\n}\n\nfunc (s Stuff) DoSomething(other *OtherStuff) error {\n    log := s.Logger.Scope(\"dosomething\")\n\n    log.Record(\"other\", other).Infof(\"Need to do something\")\n    if err := someFunc(s, other); err != nil {\n        log.Errorf(\"Something went wrong with other\", err)\n        return err\n    }\n    return nil\n}\n```\n\nThe call to `Record(key, value)` creates a new `Logger` object. So, they are like Russian dolls when it comes down to actually writing the log message to the output stream. In other words, `Record` objects are collected from their parent's `Logger` back to the original `Logger`.  \n\nFor example:  \n\n```go\nvar Log   = logger.Create(\"test\")\nvar child = Log.Record(\"key1\", \"value1\").Record(\"key2\", \"value2\")\n```\n\n*child* will actually be something like `Logger(Logger(Logger(Stream to stdout)))`. Though we added only 2 records.  \n\nTherefore, to optimize the number of `Logger` objects that are created, there are some convenience methods that can be used:\n\n```go\nfunc (s stuff) DoSomethingElse(other *OtherStuff) {\n    log := s.Logger.Child(\"new_topic\", \"new_scope\", \"id\", other.ID(), \"key1\", \"value1\")\n\n    log.Infof(\"I am logging this stuff\")\n\n    log.Records(\"key2\", \"value2\", \"key3\", 12345).Warnf(\"Aouch that hurts!\")\n}\n```\n\nThe `Child` method will create one `Logger` that has a `Record` containing a topic, a scope, 2 keys (*id* and *key1*) with their values.\n\nThe `Records` method will create one `Logger` that has 2 keys (*key2* and *key3*) with their values.\n\nFor example, with these methods:  \n\n```go\nvar Log    = logger.Create(\"test\")\nvar child1 = Log.Child(\"topic\", \"scope\", \"key2\", \"value2\", \"key3\", \"value3\")\nvar child2 = child1.Records(\"key2\", \"value21\", \"key4\", \"value4\")\n```\n\n*child1* will be something like `Logger(Logger(Stream to stdout))`. Though we added 2 records.  \n*child2* will be something like `Logger(Logger(Logger(Stream to stdout)))`. Though we added 1 record to the 2 records added previously.  \n\n## Stream objects\n\nA `Stream` is where the `Logger` actually writes its `Record` data.\n\nWhen creating a `Logger`, you can specify the destination it will write to:\n\n```go\nvar Log = logger.Create(\"myapp\", \"file://path/to/myapp.log\")\nvar Log = logger.Create(\"myapp\", \"/path/to/myapp.log\")\nvar Log = logger.Create(\"myapp\", \"./localpath/to/myapp.log\")\nvar Log = logger.Create(\"myapp\", \"stackdriver\")\nvar Log = logger.Create(\"myapp\", \"gcp\")\nvar Log = logger.Create(\"myapp\", \"/path/to/myapp.log\", \"stderr\")\nvar Log = logger.Create(\"myapp\", \"nil\")\n```\n\nThe first three `Logger` objects will write to a file, the fourth to Google Stackdriver, the fifth to Google Cloud Platform (GCP), the sixth to a file and stderr, and the seventh to nowhere (i.e. logs do not get written at all).\n\nBy default, when creating the `Logger` with:\n\n```go\nvar Log = logger.Create(\"myapp\")\n```\n\nThe `Logger` will write to the standard output or the destination specified in the environment variable `LOG_DESTINATION`.\n\nYou can also create a `Logger` by passing it a `Stream` object (these are equivalent to the previous code):\n\n```go\nvar Log = logger.Create(\"myapp\", \u0026logger.FileStream{Path: \"/path/to/myapp.log\"})\nvar Log = logger.Create(\"myapp\", \u0026logger.StackDriverStream{})\nvar Log = logger.Create(\"myapp\", \u0026logger.NilStream{})\nvar Log = logger.Create(\"myapp\", \u0026logger.FileStream{Path: \"/path/to/myapp.log\"}, \u0026logger.StderrStream{})\n```\n\nA few notes:\n\n- the `StackDriverStream` needs a `LogID` parameter or the value of the environment variable `GOOGLE_PROJECT_ID`. (see [Google's StackDriver documentation](https://godoc.org/cloud.google.com/go/logging#NewClient) for the description of that parameter).\n- `NilStream` is a `Stream` that does not write anything, all messages are lost.\n- `MultiStream` is a `Stream` than can write to several streams.\n- `StdoutStream` and `FileStream` are buffered by default. Data is written from every `LOG_FLUSHFREQUENCY` (default 5 minutes) or when the `Record`'s `Level` is at least *ERROR*.\n- Streams convert the `Record` to write via a `Converter`. The converter is set to a default value per Stream.\n\nYou can also create a `Logger` with a combination of destinations and streams, AND you can even add some records right away:\n\n```go\nvar Log = logger.Create(\"myapp\",\n    \u0026logger.FileStream{Path: \"/path/to/myapp.log\"},\n    \"stackdriver\",\n    NewRecord().Set(\"key\", \"value\"),\n)\n```\n\n### Setting the LevelSet\n\nAll `Stream` types, except `NilStream` and `MultiStream` can use a `LevelSet`. When set, `Record` objects that have a `Level` below the `LevelSet` are not written to the `Stream`. This allows to log only stuff above *WARN* for instance.\n\nA `LevelSet` is a set of `Level` objects organized by `topic` and `scope`.\n\nThe `LevelSet` can be set via a string or the environment variable `LOG_LEVEL`:\n\n- `LOG_LEVEL=INFO`  \n  will configure the `LevelSet` to *INFO*, which is the default if nothing is set;\n- `LOG_LEVEL=TRACE:{topic1};DEBUG`  \n  will configure the `LevelSet` to *DEBUG* for everything and *TRACE* for the topic *topic1* to *TRACE* (and all the scopes under that topic);\n- `LOG_LEVEL=INFO;DEBUG:{topic1:scope1,scope2}`  \n  will configure the `LevelSet` to *INFO* for everything and to *DEBUG* for the topic *topic1* and scopes *scope1*, *scope2* to *DEBUG* (all the other scopes under that topic will be set to *INFO*);\n- `LOG_LEVEL=INFO;DEBUG:{topic1};TRACE:{topic2}`  \n  will configure the `LevelSet` to *INFO* for everything, to *DEBUG* for the topic *topic1*, and to *TRACE* for the topic *topic2* (and all the scopes under these topics);\n- `LOG_LEVEL=INFO;DEBUG:{:scope1}`  \n  will configure the `LevelSet` to *INFO* for everything, to *DEBUG* for the scope *topic1* (and all the topics containing that scope);\n- The last setting of a topic supersedes the ones set before;\n- If the environment variable `DEBUG` is set to *1*, the default `Level` in the `LevelSet` is superseded with *DEBUG*.\n\nAt the moment, the `LevelSet` can be configured only for all `Streamer` of a `Logger`.\n\nIt is also possible to change the default `Level` by calling `FilterMore()`and `FilterLess()` methods on the `Logger` or any of its `Streamer` members. The former will log less data and the latter will log more data. We provide an example of how to use these in the [examples](examples/set-level-with-signal/) folder using Unix signals.\n\n```go\nlog := logger.Create(\"myapp\", \u0026logger.StdoutStream{})\n// We are filtering at INFO\nlog.FilterLess()\n// We are now filtering at DEBUG\n```\n\n### StackDriver Stream\n\nIf you plan to log to Google's StackDriver from a Google Cloud Kubernetes or a Google Cloud Instance, you do not need the StackDriver Stream and should use the Stdout Stream with the StackDriver Converter, since the standard output of your application will be captured automatically by Google to feed StackDriver:  \n\n```go\nvar Log = logger.Create(\"myapp\", \"gcp\") // \"google\" or \"googlecloud\" are valid aliases\nvar Log = logger.Create(\"myapp\", \u0026logger.StdoutStream{Converter: \u0026logger.StackDriverConverter{}})\n```\n\nTo be able to use the StackDriver Stream from outside Google Cloud, you have some configuration to do first.\n\nOn your workstation, you need to get the key filename:  \n\n1. Authenticate with Google Cloud  \n  ```console\n  gcloud auth login\n  ```\n2. Create a Service Account (`logger-account` is just an example of a service account name)  \n  ```console\n  gcloud iam service-acccount create logger-account\n  ```\n3. Associate the Service Account to the Project you want to use  \n  ```console\n  gcloud projects add-iam-policy-binding my-logging-project \\\n    --member \"serviceAccount:logger-account@my-logging-project.iam.gserviceaccount.com\" \\\n    --role \"roles/logging.logWriter\"\n  ```\n4. Retrieve the key filename  \n  ```console\n  gcloud iam service-accounts keys create /path/to/key.json \\\n    --iam-account logger-account@my-logging-project.iam.gserviceaccount.com\n  ```\n\nYou can either set the `GOOGLE_APPLICATION_CREDENTIAL` and `GOOGLE_PROJECT_ID` environment variables with the path of the obtained key and Google Project ID or provide them to the StackDriver stream:  \n\n```go\nvar log = logger.Create(\"myapp\", \u0026logger.StackDriverStream{})\n```\n\n```go\nvar log = logger.Create(\"myapp\", \u0026logger.StackDriverStream{\n    Parent:      \"my-logging-project\",\n    KeyFilename: \"/path/to/key.json\",\n})\n```\n\n### Writing your own Stream\n\nYou can also write your own `Stream` by implementing the `logger.Streamer` interface and create the Logger like this:\n\n```go\nvar log = logger.Create(\"myapp\", \u0026MyStream{})\n```\n\n### Logging Source Information\n\nIt is possible to log source information such as the source filename and code line, go package, and the caller func.\n\n```go\nvar Log = logger.Create(\"myapp\", \u0026logger.FileStream{Path: \"/path/to/myapp.log\", SourceInfo: true})\n\nfunc MyFunc() {\n  Log.Infof(\"I am Here\")\n}\n```\n\n**Note**: Since this feature can be expensive to compute, it is turned of by default.  \nTo turn it on, you need to either specify the option in the Stream object, set the environment variable `LOG_SOURCEINFO` to *true*. It is also turned on if the environment variable `DEBUG` is *true*.\n\n### Timing your funcs\n\nYou can automatically log the duration of your func by calling them via the logger:\n\n```go\nlog.TimeFunc(\"message shown with the duration\", func() {\n  log.Info(\"I am here\")\n  // ... some stuff that takes time\n  time.Sleep(12*time.Second)\n})\n```\n\nThe duration will logged in the `msg` record after the given message. It will also be added as a float value in the `duration` record.\n\nThere are 3 more variations for funcs that return an error, a value, an error and a value:\n\n```go\nresult := log.TimeFuncV(\"message shown with the duration\", func() interface{} {\n  log.Info(\"I am here\")\n  // ... some stuff that takes time\n  time.Sleep(12*time.Second)\n  return 12\n})\n\nerr := log.TimeFuncE(\"message shown with the duration\", func() err {\n  log.Info(\"I am here\")\n  // ... some stuff that takes time\n  time.Sleep(12*time.Second)\n  return errors.ArgumentMissing.With(\"path\")\n})\n\nresult, err := log.TimeFuncVE(\"message shown with the duration\", func() (interface{}, error) {\n  log.Info(\"I am here\")\n  // ... some stuff that takes time\n  time.Sleep(12*time.Second)\n  return 12, errors.ArgumentInvalid.With(\"value\", 12)\n})\n```\n\n### Miscellaneous\n\nThe following convenience methods can be used when creating a `Logger` from another one (received from arguments, for example):\n\n```go\nvar log = logger.CreateIfNil(OtherLogger, \"myapp\")\n```\n\n```go\nvar log = logger.Create(\"myapp\", OtherLogger)\n```\n\nIf `OtherLogger` is `nil`, the new `Logger` will write to the `NilStream()`.\n\n```go\nvar log = logger.Must(logger.FromContext(context))\n```\n\n`Must` can be used to create a `Logger` from a method that returns `*Logger, error`, if there is an error, `Must` will panic.\n\n`FromContext` can be used to retrieve a `Logger` from a GO context. (This is used in the paragraph about HTTP Usage)  \n\n`log.ToContext` will store the `Logger` to the given GO context.\n\n## Redacting\n\nThe `Logger` can redact records as needed by simply implementing the `logger.Redactable` interface in the data that is logged.\n\nFor example:\n\n```go\ntype Customer {\n  ID   uuid.UUID `json:\"id\"`\n  Name string    `json:\"name\"`\n}\n\n// implements logger.Redactable\nfunc (customer Customer) Redact() interface{} {\n  return Customer{customer.Name, logger.Redact(customer.ID)}\n}\n\nmain() {\n  // ...\n  customer := Customer{uuid, \"John Doe\"}\n\n  log.Record(\"customer\", customer).Infof(\"Got a customer\")\n}\n```\n\nWhen redacting a field, you can also call `logger.RedactWithHash` which will redact the value with a string like: \"REDACTED-\u0026lt;hash\u0026gt;\" where `\u003chash\u003e` is a SHA256 hash of the original value. This is useful when you want to redact a value but still want to be able to identify it in the logs. You can also change the prefix with `logger.RedactWithPrefixedHash`.\n\nFor Complex objects, you can also implement the `logger.RedactableWithKeys` interface:\n\n```go\ntype Customer {\n  ID   uuid.UUID `json:\"id\"`\n  Name string    `json:\"name\"`\n}\n\n// implements logger.RedactableWithKeys\nfunc (customer Customer) Redact(keyToRedact ...string) interface{} {\n  redacted := customer\n  for _, key := range keyToRedact {\n    switch key {\n    case \"name\":\n      redacted.Name = logger.RedactWithHash(customer.Name)\n    }\n  }\n}\n\nmain() {\n  log.RecordWithKeysToRedact(\"customer\", customer, \"name\").Infof(\"Got a customer\")\n}\n```\n\nYou can also redact slices and maps of objects:\n\n```go\nlog.Record(\"users\", logger.RedactSlice(users)).Infof(\"Got a list of users\")\nlog.Record(\"users\", logger.RedactMap(users)).Infof(\"Got a map of users\")\n```\n\nYou can also redact the log messages by providing regular expressions, called redactors. Whenever a redactor matches, its matched content is replaced with \"REDACTED\".\n\nYou can assign several redactors to a single logger:\n\n```go\nr1, err := logger.NewRedactor(\"[0-9]{10}\")\nr2 := (logger.Redactor)(myregexp)\nlog := logger.Create(\"test\", r1, r2)\n```\n\nYou can also add redactors to a child logger (without modifying the parent logger):\n\n```go\nr3 := logger.NewRedactor(\"[a-z]{8}\")\nlog := parent.Child(\"topic\", \"scope\", \"record1\", \"value1\", r3)\n```\n\n**Note:** Adding redactors to a logger **WILL** have a performance impact on your application as each regular expression will be matched against every single message produced by the logger. We advise you to use as few redactors as possible and contain them in child logger, so they have a minimal impact.\n\n## Converters\n\nThe `Converter` object is responsible for converting the `Record`, given to the `Stream` to write, to match other log viewers.\n\nThe default `Converter` is `BunyanConverter` so the `bunyan` log viewer can read the logs.\n\nHere is a list of all the converters:\n\n- `BunyanConverter`, the default converter (does nothing, actually),\n- `CloudWatchConverter` produces logs that are nicer with AWS CloudWatch log viewer.\n- `PinoConverter` produces logs that can be used by [pino](http://getpino.io),\n- `StackDriverConverter` produces logs that are nicer with Google StackDriver log viewer,\n\n**Note**: When you use converters, their output will most probably not work anymore with `bunyan`. That means you cannot have both worlds in the same Streamer. In some situation, you can survive this by using several streamers, one converted, one not.\n\n### Writing your own Converter\n\nYou can also write your own `Converter` by implementing the `logger.Converter` interface:  \n\n```go\ntype MyConverter struct {\n  // ...\n}\n\nfunc (converter *MyConverter) Convert(record Record) Record {\n    record[\"newvalue\"] = true\n    return record\n}\n\nvar Log = logger.Create(\"myapp\", \u0026logger.StdoutStream{Converter: \u0026MyConverter{}})\n```\n\n## Standard Log Compatibility\n\nTo use a `Logger` with the standard go `log` library, you can simply call the `AsStandardLog()` method. You can optionally give a `Level`:  \n\n```go\npackage main\n\nimport (\n  \"net/http\"\n  \"github.com/gildas/go-logger\"\n)\n\nfunc main() {\n    log := logger.Create(\"myapp\")\n\n    server1 := http.Server{\n      // extra http stuff\n      ErrorLog: log.AsStandardLog()\n    }\n\n    server2 := http.Server{\n      // extra http stuff\n      ErrorLog: log.AsStandardLog(logger.WARN)\n    }\n}\n```\n\nYou can also give an `io.Writer` to the standard `log` constructor:  \n\n```go\npackage main\n\nimport (\n  \"log\"\n  \"net/http\"\n  \"github.com/gildas/go-logger\"\n)\n\nfunc main() {\n    mylog := logger.Create(\"myapp\")\n\n    server1 := http.Server{\n      // extra http stuff\n      ErrorLog: log.New(mylog.Writer(), \"\", 0),\n    }\n\n    server2 := http.Server{\n      // extra http stuff\n      ErrorLog: log.New(mylog.Writer(logger.WARN), \"\", 0),\n    }\n}\n```\n\nSince `Writer()` returns `io.Writer`, anything that uses that interface could, in theory, write to a `Logger`.\n\n## HTTP Usage\n\nIt is possible to pass `Logger` objects to [http.Handler](https://golang.org/pkg/net/http/#Handler). When doing so, the Logger will automatically write the request identifier (\"X-Request-Id\" HTTP Header), remote host, user agent, when the request starts and when the request finishes along with its duration.\n\nThe request identifier is attached every time the log writes in a `Record`.\n\nHere is an example:\n\n```go\npackage main\n\nimport (\n  \"net/http\"\n  \"github.com/gildas/go-logger\"\n  \"github.com/gorilla/mux\"\n)\n\nfunc MyHandler() http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n      // Extracts the Logger from the request's context\n      //  Note: Use logger.Must only when you know there is a Logger as it will panic otherwise\n      log := logger.Must(logger.FromContext(r.Context()))\n\n      log.Infof(\"Now we are logging inside this http Handler\")\n    })\n}\n\nfunc main() {\n  log := logger.Create(\"myapp\")\n  router := mux.NewRouter()\n  router.Methods(\"GET\").Path(\"/\").Handler(log.HttpHandler()(MyHandler()))\n}\n```\n\nWhen the http request handler (*MyHandler*) starts, the following records are logged:  \n\n- `reqid`, contains the request Header X-Request-Id if present, or a random UUID\n- `path`, contains the URL Path of the request\n- `remote`, contains the remote address of the request\n- The `topic` is set to \"route\" and the `scope` to the path of the request URL\n\nWhen the http request handler (*MyHandler*) ends, the following additional records are logged:  \n\n- `duration`, contains the duration in seconds (**float64**) of the handler execution\n\n## Environment Variables\n\nThe `Logger` can be configured completely by environment variables if needed. These are:  \n\n- `LOG_DESTINATION`, default: `StdoutStream`  \n  The `Stream`s to write logs to. It can be a comma-separated list (for a `MultiStream`)\n- `LOG_LEVEL`, default: *INFO*  \n  The level to filter by default. If the environment `DEBUG` is set the default level is *DEBUG*\n- `LOG_CONVERTER`, default: \"bunyan\"  \n  The default `Converter` to use\n- `LOG_FLUSHFREQUENCY`, default: 5 minutes  \n  The default Flush Frequency for the streams that will be buffered\n- `LOG_OBFUSCATION_KEY`, default: none  \n  The SSL public key to use when obfuscating if you want a reversible obfuscation\n- `GOOGLE_APPLICATION_CREDENTIALS`  \n  The path to the credential file for the `StackDriverStream`\n- `GOOGLE_PROJECT_ID`  \n  The Google Cloud Project ID for the `StackDriverStream`\n- `DEBUG`, default: none  \n  If set to \"1\", this will set the default level to filter to *DEBUG*\n\n## Thanks\n\nSpecial thanks to [@chakrit](https://github.com/chakrit) for his [chakrit/go-bunyan](https://github.com/chakrit/go-bunyan) that inspired me. In fact earlier versions were wrappers around his library.  \n\nWell, we would not be anywhere without the original work of [@trentm](https://github.com/trentm) and the original [trentm/node-bunyan](https://github.com/trentm/node-bunyan). Many, many thanks!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgildas%2Fgo-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgildas%2Fgo-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgildas%2Fgo-logger/lists"}