{"id":24314923,"url":"https://github.com/karrick/gologs","last_synced_at":"2025-10-28T08:04:37.209Z","repository":{"id":137108302,"uuid":"165129140","full_name":"karrick/gologs","owner":"karrick","description":"Go library for logs","archived":false,"fork":false,"pushed_at":"2023-04-21T20:35:57.000Z","size":190,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-05T17:40:26.608Z","etag":null,"topics":["golang-library","logger","logging","logging-library"],"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/karrick.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":"2019-01-10T20:50:39.000Z","updated_at":"2022-02-02T04:29:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"33c0e2ec-8ec8-46e1-a244-de417f97cafe","html_url":"https://github.com/karrick/gologs","commit_stats":null,"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karrick%2Fgologs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karrick%2Fgologs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karrick%2Fgologs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karrick%2Fgologs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karrick","download_url":"https://codeload.github.com/karrick/gologs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242748896,"owners_count":20179097,"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-library","logger","logging","logging-library"],"created_at":"2025-01-17T10:16:12.839Z","updated_at":"2025-10-28T08:04:37.100Z","avatar_url":"https://github.com/karrick.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gologs\n\n## Why\n\nWhy yet another logging library?\n\n1. Create a log, split it into branches, give each branch different\n   log prefixes, and set each branch to independent log level.\n\n## Goals\n\n1. This should work within the Go ecosystem. Specifically, it should\n   emit logs to any io.Writer.\n\n1. This should be flexible enough to provide for use cases not\n   originally envisioned, yet be easy enough to use to facilitate\n   adoption. I should want to reach for this library for all my\n   logging needs, for both command line and long running services.\n\n1. This should be lightweight. This should not spin up any go\n   routines. This should only allocate when creating a new logger or a\n   new log branch, or when the user specifically requires it by\n   invoking the `Format` method. Events that do not get logged should\n   not be formatted. This should not ask the OS for the system time if\n   log format specification does not require it.\n\n1. This should be correct. It should never invoke Write more than once\n   per logged event.\n\n[![GoDoc](https://godoc.org/github.com/karrick/gologs?status.svg)](https://godoc.org/github.com/karrick/gologs)\n\n## Compliments\n\nA while ago I significantly altered the API of this library based on\nthe amazing [zerolog](https://github.com/rs/zerolog) library. I hope\nthe authors of that library have heard the expression that imitation\nis the most sincere form of flattery.\n\n## Usage Example\n\n```Go\npackage main\n\nimport (\n    \"flag\"\n    \"fmt\"\n    \"os\"\n\n    \"github.com/karrick/gologs\"\n)\n\nfunc main() {\n    optDebug := flag.Bool(\"debug\", false, \"Print debug output to stderr\")\n    optVerbose := flag.Bool(\"verbose\", false, \"Print verbose output to stderr\")\n    optQuiet := flag.Bool(\"quiet\", false, \"Print warning and error output to stderr\")\n    flag.Parse()\n\n    // Initialize the global log variable, which will be used very much like the\n    // log standard library would be used.\n    log := gologs.New(os.Stderr)\n\n    // Configure log level according to command line flags.\n    if *optDebug {\n        log.SetDebug()\n    } else if *optVerbose {\n        log.SetVerbose()\n    } else if *optQuiet {\n        log.SetError()\n    } else {\n        log.SetInfo()\n    }\n\n    // For sake of example, invoke printSize with a child logger that includes\n    // the function name in the JSON properties of the log message.\n    clog := log.With().String(\"function\", \"printSize\").Logger()\n\n    for _, arg := range flag.Args() {\n        // NOTE: Sends event to parent logger.\n        log.Verbose().String(\"arg\", arg).Msg(\"\")\n\n        // NOTE: Sends events to child logger.\n        if err := printSize(clog, arg); err != nil {\n            log.Warning().Err(err).Msg(\"\")\n        }\n    }\n}\n\nfunc printSize(log *gologs.Logger, pathname string) error {\n    log.Debug().String(\"pathname\", pathname).Msg(\"stat\")\n    stat, err := os.Stat(pathname)\n    if err != nil {\n        return err\n    }\n\n    size := stat.Size()\n    log.Debug().String(\"pathname\", pathname).Int64(\"size\", size).Msg(\"\")\n\n    if (stat.Mode() \u0026 os.ModeType) == 0 {\n        fmt.Printf(\"%s is %d bytes\\n\", pathname, size)\n    }\n\n    return nil\n}\n```\n\n## Description\n\n### Creating a Logger Instance\n\nEverything written by this logger is formatted as a JSON event, given\na trailing newline, and written to the underlying io.Writer. That\nio.Writer might be os.Stderr, or it might be a log rolling library,\nwhich in turn, is writting to a set of managed log files. The library\nprovides a few time formatting functions, but the time is only\nincluded when the Logger is updated to either one of the provided time\nformatting functions or a user specified time formatter.\n\n```Go\n    log1 := gologs.New(os.Stderr)\n    log1.Info().Msg(\"started program\")\n    // Output:\n    // {\"level\":\"info\",\"message\":\"starting program\"}\n\n    log2 := gologs.New(os.Stderr).SetTimeFormatter(gologs.TimeUnix)\n    log2.Info().Msg(\"started program\")\n    // Output:\n    // {\"time\":1643776764,\"level\":\"info\",\"message\":\"starting program\"}\n\n    log3 := gologs.New(os.Stderr).SetTimeFormatter(gologs.TimeUnixNano)\n    log3.Info().Msg(\"started program\")\n    // Output:\n    // {\"time\":1643776794592630092,\"level\":\"info\",\"message\":\"starting program\"}\n\n    log4 := gologs.New(os.Stderr).SetTimeFormatter(gologs.TimeFormat(time.RFC3339))\n    log4.Info().Msg(\"started program\")\n    // Output:\n    // {\"time\":\"2022-08-06T15:14:04-04:00\",\"level\":\"info\",\"message\":\"starting program\"}\n\n    log5 := gologs.New(os.Stderr).SetTimeFormatter(gologs.TimeFormat(time.Kitchen))\n    log5.Info().Msg(\"started program\")\n    // Output:\n    // {\"time\":\"3:14PM\",\"level\":\"info\",\"message\":\"starting program\"}\n```\n\n### Log Levels\n\nLike most logging libraries, the basic logger provides methods to\nchange its log level, controling which events get logged and which get\nignored.\n\n```Go\n    log.SetVerbose()\n    log.Info().Msg(\"this event gets logged\")\n    log.Verbose().Msg(\"and so does this event\")\n    log.Debug().Msg(\"but this event gets ignored\")\n\n    log.SetLevel(gologs.Debug)\n    log.Debug().Msg(\"this event does get logged\")\n```\n\nWhen a logger is in Error mode, only Error events are logged. When a\nlogger is in Warning mode, only Error and Warning events are\nlogged. When a logger is in Info mode, only Error, Warning, and Info\nevents are logged. When a logger is in Verbose mode, only Error,\nWarning, Info, and Verbose events are logged. When a logger is in\nDebug mode, all events are logged.\n\nNote the logger mode for a newly created Logger is Warning, which I\nfeel is in keeping with the UNIX philosophy to _Avoid unnecessary\noutput_. Simple command line programs will not need to set the log\nlevel to prevent spewing too many events. While service application\ndevelopers will need to spend a few minutes to build in the ability to\nconfigure the log level based on their service needs.\n\nPerhaps more idiomatic of a command line program log configuration:\n\n```Go\n    if *optDebug {\n        log.SetDebug()\n    } else if *optVerbose {\n        log.SetVerbose()\n    } else if *optQuiet {\n        log.SetError()\n    } else {\n        log.SetInfo()\n    }\n```\n\n### A Tree of Logs with Multiple Branches\n\nIn managing several real world services, I discovered the need for\nfiner granularity in managing which events are logged in different\nparts of the same running program. Sometimes all events in one\nparticular module of the service should be logged with great detail,\nwhile a different part of the program is deemed functional and the\nloggging of developer events would saturate the logs.\n\nThis library allows this workflow by allowing a developer to create a\ntree of logs with multiple branches, and each branch can have an\nindependently controlled log level. These log branches are\nlightweight, require no go routines to facilitate, and can even be\nephemeral, and demonstrated later in the Tracer Logging\nsection. Creating logger branches do allocate by copying the branch\nbyte slice from the parent to the child branch.\n\n#### Base of the Tree\n\nTo be able to independently control log levels of different parts of\nthe same program at runtime, this library provides for the creation of\nwhat I like to call a tree of logs. At the base of the tree, events\nare written to an underlying io.Writer. This allows a developer to\ncreate a log and have it write to standard error, standard output, a\nfile handle, a log rolling library which writes to a file, or any\nother structure that implements the io.Writer interface.\n\n#### Creating New Branches for the Log Tree\n\nDifferent logging configurations can be effected by creating a logging\ntree, and while the tree may be arbitrarily complex, a simple tree is\nlikely more developer friendly than a complex one. For instance, I\nhave adopted the pattern of creating a very small tree, with a base\nlogger for the entire application, and a logger branch for each major\nmodule of the program. Each of those branches can have a different log\nlevel, each of which can be controlled at runtime using various means,\nalways by invoking one of its log level control methods. Additionally\neach branch can have a particular string prefix provided that will\nprefix the logged events.\n\nThis allows each branch to have an independently controlled log level,\nand the program can set one logger to run at `Debug` mode, while the\nother branches run at different levels. These log levels are also safe\nto asynchronously modify while other threads are actively logging\nevents to them.\n\n```Go\n    // Foo is a module of the program with its own logger.\n    type Foo struct {\n        log *gologs.Logger\n        // ...\n    }\n\n    // Bar is a module of the program with its own logger.\n    type Bar struct {\n        log *gologs.Logger\n        // ...\n    }\n\n    func example1() {\n        // log defined as in previous examples...\n        foo := \u0026Foo{\n            log: log.With().String(\"module\",\"FOO\").Logger(),\n        }\n        go foo.run()\n\n        bar := \u0026Bar{\n            log: log.With().String(\"module\",\"BAR\").Logger(),\n        }\n        go bar.run()\n    }\n```\n\nIn the above example both `Foo` and `Bar` are provided their own\nindividual logger to use, and both `Foo` and `Bar` can independently\ncontrol its own log level. It is important that they use that logger\nto log all of their events during their lifetime, in order to be\neffective.\n\n### Tracer Logging\n\nI am sure I'm not the only person who wanted to figure out why a\nparticular request or action was not working properly on a running\nservice, decided to activate DEBUG log levels to watch the single\nrequest percolate through the service, to be reminded that the service\nis actually serving tens of thousands of requests per second, and now\nthe additional slowdown that accompanies logging each and every single\nlog event in the entire program not only slows it down, but makes it\nimpossible to see the action or request in the maelstrom of log\nmessages scrolling by the terminal.\n\nFor instance, let's say an administrator or developer wants to send a\nrequest through their running system, logging all events related to\nthat request, regardless of the log level, but not necessarily see\nevents for other requests.\n\nFor this example, remember that each module has a Logger it uses\nwhenever logging any event. Let's say the `Foo` module receives\nrequests to process. The `Foo` can create highly ephemeral Tracer\nLoggers to be assigned to the request instance itself, and provided\nthat the request methods log using the provided logger, then those\nevents will bypass any filters in place between where the log event\nwas created to the base of the logging tree, and get written to the\nunderlying io.Writer.\n\n```Go\n    type Request struct {\n        log   *gologs.Logger\n        query string\n        // ...\n    }\n\n    func (f *Foo) NewRequest(query string) (*Request, error) {\n        r := \u0026Request{\n            log:   f.log.With().String(\"request\", query).Logger(),\n            query: query,\n        }\n        if strings.HasSuffix(key, \"*\") {\n            r.log.SetTracing(true)\n        }\n        // ...\n    }\n\n    func (r *Request) Process() error {\n        r.log.Debug().Msg(\"beginning processing of request\")\n        // ...\n    }\n```\n\nIt is important to remember that events sent to a Logger configured\nfor tracing will bypass all log level filters. So `log`, `Foo`, and\n`Bar` all might be set for Warning level, but you want to follow a\nparticular request through the system, without changing the log\nlevels, also causing the system to log every other request. Tracer\nlogic is not meant to be added and removed while debugging a program,\nbut rather left in place, run in production, but not used, unless some\nspecial developer or administrator requested token marks a particular\nevent as one for which all events should be logged.\n\nHere's an example of what Tracer Loggers are trying to eliminate,\nassuming a hypothetical `Logging.Trace` method existed:\n\n```Go\n    // Counter example: desired behavior sans tracer logic. Each log line\n    // becomes a conditional, leading to code bloat.\n    func (r *Request) Handler() {\n        // It is inconvenient to branch log events each place you want to\n        // emit a log event.\n        if r.isSpecial {\n            r.Log.Trace().Msg(\"handling request\")\n        } else {\n            r.Log.Debug().Msg(\"handling request: %v\", r)\n        }\n\n        // Do some work, then need to log more:\n        if r.isSpecial {\n            r.Log.Trace().Int(\"request-cycles\", r.Cycles).Msg(\"\")\n        } else {\n            r.Log.Debug().Int(\"request-cycles\", r.Cycles).Msg(\"\")\n        }\n    }\n```\n\nI propose something better, where the developer does not need to\ninclude conditional statements to branch based on whether the log\nshould receive Tracer status or Verbose status for each log\nevent. Yet, when Tracer status, still get written to the log when\nsomething requires it.\n\n```Go\n    func NewRequest(log *gologs.Logger, key string) (*Request, error) {\n        log = log.With().\n            String(\"key\", key).\n            Tracing(strings.HasSuffix(key, \"*\")).\n            Logger()\n        r := \u0026R{\n            log: log,\n            Key: key,\n        }\n        return r, nil\n    }\n\n    func (r *Request) Handler() {\n        r.Log.Debug().Msg(\"handling request\")\n\n        // Do some work, then need to log more:\n\n        r.Log.Debug().Int(\"request-cycles\", r.Cycles).Msg(\"\")\n    }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarrick%2Fgologs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarrick%2Fgologs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarrick%2Fgologs/lists"}