{"id":46095967,"url":"https://github.com/zalgonoise/zlog","last_synced_at":"2026-03-01T18:36:55.001Z","repository":{"id":37029697,"uuid":"454376332","full_name":"zalgonoise/zlog","owner":"zalgonoise","description":"A lightweight Golang library to handle logging ","archived":false,"fork":false,"pushed_at":"2023-03-07T02:19:09.000Z","size":3077,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-06-21T09:39:13.354Z","etag":null,"topics":[],"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/zalgonoise.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":"2022-02-01T12:20:01.000Z","updated_at":"2022-02-01T12:30:09.000Z","dependencies_parsed_at":"2024-06-21T08:36:04.083Z","dependency_job_id":"7e2daf26-6ebd-4deb-b4f3-1c0ae9fa6bf0","html_url":"https://github.com/zalgonoise/zlog","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zalgonoise/zlog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fzlog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fzlog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fzlog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fzlog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zalgonoise","download_url":"https://codeload.github.com/zalgonoise/zlog/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fzlog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29979121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T16:35:47.903Z","status":"ssl_error","status_checked_at":"2026-03-01T16:35:44.899Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2026-03-01T18:36:54.903Z","updated_at":"2026-03-01T18:36:54.985Z","avatar_url":"https://github.com/zalgonoise.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zlog\nA lightweight Golang library to handle logging \n\n_________________________\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/zalgonoise/zlog/raw/media/img/LoggerGopher-small.png\" /\u003e\n\u003c/p\u003e\n\n\n## Index\n\n1. [Overview](#overview)\n1. [Installation](#installation)\n1. [Usage](#usage)\n\t1. [Loggers usage](#loggers-usage)\n\t\t1. [Simple Logger](#simple-logger---example)\n\t\t1. [Custom Logger](#custom-logger---example)\n\t\t1. [Multilogger](#multilogger---example)\n\t\t1. [Output formats](#output-formats---example)\n\t\t1. [Modular events](#modular-events---example)\n\t\t1. [Channeled Logger](#channeled-logger---example)\n\t\t1. [Callstack in Metadata](#callstack-in-metadata---example)\n\t1. [Storing log events](#storing-log-events)\n\t\t1. [Logger as a Writer](#logger-as-a-writer---example)\n\t\t1. [Writing events to a file](#writing-events-to-a-file---example)\n\t\t1. [Writing events to a database](#writing-events-to-a-database)\n\t1. [Logging events remotely](#logging-events-remotely)\n\t\t1. [gRPC server / client](#unary-grpc-server--client---example)\n\t1. [Building the library](#building-the-library)\n\t\t1. [Targets](#targets)\n\t\t1. [Target types](#target-types)\n1. [Features](#features)\n\t1. [Simple API](#simple-api)\n\t1. [Highly configurable](#highly-configurable)\n\t1. [Feature-rich events](#feature-rich-events)\n\t\t1. [Data structure](#data-structure)\n\t\t1. [Event builder](#event-builder)\n\t\t1. [Log levels](#log-levels)\n\t\t1. [Structured metadata](#structured-metadata)\n\t\t1. [Callstack in metadata](#callstack-in-metadata)\n\t1. [Multi-everything](#multi-everything)\n\t1. [Different formatters](#different-formatters)\n\t\t1. [Text](#text)\n\t\t\t1. [Log Timestamps](#log-timestamps)\n\t\t1. [JSON](#json)\n\t\t1. [BSON](#bson)\n\t\t1. [CSV](#csv)\n\t\t1. [XML](#xml)\n\t\t1. [Protobuf](#protobuf)\n\t\t1. [Gob](#gob)\n\t1. [Data Stores](#data-stores)\n\t\t1. [Writer interface](#writer-interface)\n\t\t1. [Logfile](#logfile)\n\t\t1. [Databases](#databases)\n\t\t\t1. [SQLite](#sqlite)\n\t\t\t1. [MySQL](#mysql)\n\t\t\t1. [PostgreSQL](#postgresql)\n\t\t\t1. [MongoDB](#mongodb)\n\t1. [gRPC](#grpc)\n\t\t1. [gRPC Log Service](#grpc-log-service)\n\t\t1. [gRPC Log Server](#grpc-log-server)\n\t\t\t1. [Log Server Configs](#log-server-configs)\n\t\t1. [gRPC Log Client](#grpc-log-client)\n\t\t\t1. [Log Client Configs](#log-client-configs)\n\t\t\t1. [Log Client Backoff](#log-client-backoff)\n\t\t1. [Connection Addresses](#connection-addresses)\n1. [Integration](#integration)\n\t1. [Protobuf code generation](#protobuf-code-generation)\n\t1. [Adding your own configuration settings](#adding-your-own-configuration-settings)\n\t1. [Adding methods to a Builder pattern](#adding-methods-to-a-builder-pattern)\n\t1. [Adding interceptors to gRPC server / client](#adding-interceptors-to-grpc-server--client)\n1. [Benchmarks](#benchmarks)\n1. [Contributing](#contributing)\n\n_________________\n\n\n### Overview \n\n\nThis project started (like many others) as a means for me to learn and understand how logging works (in Go and in general), among other interesting Go design patterns. \n\nBasing myself off of the standard library `log` package, the goal was to create a new, _minimalist_ logger while introducing great features found in open-source projects like [logrus](https://github.com/sirupsen/logrus).\n\nVery quickly it became apparent that this project had little or no minimalism as it grew, as I intended to add new features as I learned new technologies and techniques.\n\nThat being the case, the goal morphed from simplicity to feature-rich **and** developer-friendly at the same time -- using abstractions and wrappers to allow more complex configuration or behavior if the dev wants, while (trying to) keep it idiomatic when using simple or default configurations. \n\n________________\n\n\n### Installation\n\nTo use the library in a project you're working on, ensure that you've initialized your `go.mod` file by running:\n\n```shell\ngo mod init ${package_name} # like github.com/user/repo\ngo mod tidy\n```\n\nAfter doing so, you can `go get` this library:\n\n```shell\ngo get github.com/zalgonoise/zlog\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/go_get_zlog.png\" /\u003e\n\u003c/p\u003e\n\n\nFrom this point onward, you can import the library in your code and use it as needed.\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/go_mod_zlog.png\" /\u003e\n\u003c/p\u003e\n\n\n\n\u003e There are plans to add a CLI version too, to serve as a gRPC Log Server binary or a one-shot logger binary. The corresponding `go install` instructions will be added by then.\n\n_________________\n\n### Usage\n\nThis section covers basic usage and typical use-cases of different modules in this library. There are several [individual examples](./examples/) to provide direct context, as well as the [Features](#features) section, which goes in-depth on each module and its functionality. In here you will find reference to certain actions, a snippet from the respective example and a brief explanation of what's happening.\n\n#### Loggers usage\n\n##### Simple Logger - [_example_](./examples/logger/simple_logger/simple_logger.go)\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n)\n\nfunc main() {\n\tlog.Print(\"this is the simplest approach to entering a log message\")\n\tlog.Tracef(\"and can include formatting: %v %v %s\", 3.5, true, \"string\")\n\tlog.Errorln(\"which is similar to fmt.Print() method calls\")\n\n\tlog.Panicf(\"example of a logger panic event: %v\", true)\n}\n```\n\n_Output_\n\n```\n[info]  [2022-07-26T17:05:46.208657519Z]        [log]   this is the simplest approach to entering a log message\n[trace] [2022-07-26T17:05:46.208750114Z]        [log]   and can include formatting: 3.5 true string\n[error] [2022-07-26T17:05:46.208759031Z]        [log]   which is similar to fmt.Print() method calls\n\n[panic] [2022-07-26T17:05:46.208766425Z]        [log]   example of a logger panic event: true\npanic: example of a logger panic event: true\n\ngoroutine 1 [running]:\ngithub.com/zalgonoise/zlog/log.(*logger).Panicf(0xc0001dafc0, {0x952348, 0x23}, {0xc0001af410, 0x1, 0x1})\n        /go/src/github.com/zalgonoise/zlog/log/print.go:226 +0x357\ngithub.com/zalgonoise/zlog/log.Panicf(...)\n        /go/src/github.com/zalgonoise/zlog/log/print.go:784\nmain.main()\n        /go/src/github.com/zalgonoise/zlog/examples/logger/simple_logger/simple_logger.go:19 +0x183\nexit status 2\n\n```\n\n\u003c/details\u003e\n\nThe simplest approach to using the logger library is to call its built-in methods, as if they were `fmt.Print()`-like calls. The logger exposes methods for registering messages in [different log levels](#log-levels), defined in its [`Printer` interface](./log/print.go#L18).\n\nNote that there are calls which are configured to halt the application's runtime, like `Fatal()` and `Panic()`. These exit calls can be skipped in the [logger's configuration](#highly-configurable).\n\nMore information on the [`Printer` interface](./log/print.go#L18) and the Logger's methods in the [_Simple API_ section](#simple-api).\n\n\n##### Custom Logger - [_example_](./examples/logger/custom_logger/custom_logger.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n)\n\nfunc main() {\n\tlogger := log.New(\n\t\tlog.WithPrefix(\"svc\"),\n\t\tlog.WithSub(\"mod\"),\n\t)\n\n\tbuf := new(bytes.Buffer)\n\n\tlogger.SetOuts(buf)\n\tlogger.Prefix(\"service\")\n\tlogger.Sub(\"module\")\n\n\tlogger.Info(\"message written to a new buffer\")\n\n\tfmt.Println(buf.String())\n}\n```\n\n_Output_\n\n```\n[info]  [2022-07-26T17:04:25.371617213Z]        [service]       [module]        message written to a new buffer\n```\n\n\u003c/details\u003e\n\n\nThe logger is customized on creation, and any number of configuration can be passed to it. This makes it flexible for simple configurations (where only defaults are applied), and makes it granular enough for the complex ones.\n\nFurthermore, it will also expose [certain methods](#simple-api) to allow changes to the logger's configuration during runtime (with `Prefix()`, `Sub()` and `Metadata()`, as well as `AddOuts()` and `SetOuts()` methods). Besides these and for more information on the available configuration functions for loggers, check out the [_Highly configurable_ section](#highly-configurable).\n\n\n\n\n_________________\n\n\n\n##### MultiLogger - [_example_](./examples/logger/multilogger/multilogger.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n)\n\nfunc main() {\n\tbuf := new(bytes.Buffer)\n\n\tstdLogger := log.New() // default logger printing to stdErr\n\tjsonLogger := log.New( // custom JSON logger, writing to buffer\n\t\tlog.WithOut(buf),\n\t\tlog.CfgFormatJSONIndent,\n\t)\n\n\t// join both loggers\n\tlogger := log.MultiLogger(\n\t\tstdLogger,\n\t\tjsonLogger,\n\t)\n\n\t// print messages to stderr\n\tlogger.Info(\"some event occurring\")\n\tlogger.Warn(\"a warning pops-up\")\n\tlogger.Log(\n\t\tevent.New().Level(event.Level_error).\n\t\t\tMessage(\"and finally an error\").\n\t\t\tMetadata(event.Field{\n\t\t\t\t\"code\":      5,\n\t\t\t\t\"some-data\": true,\n\t\t\t}).\n\t\t\tBuild())\n\n\t// print buffer content\n\tfmt.Print(\"\\n---\\n- JSON data:\\n---\\n\", buf.String())\n}\n```\n\n_Output_\n\n```\n[info]  [2022-07-28T17:12:44.966084127Z]        [log]   some event occurring\n[warn]  [2022-07-28T17:12:44.966220938Z]        [log]   a warning pops-up\n[error] [2022-07-28T17:12:44.966246265Z]        [log]   and finally an error    [ code = 5 ; some-data = true ] \n\n---\n- JSON data:\n---\n{\n  \"timestamp\": \"2022-07-28T17:12:44.966187177Z\",\n  \"service\": \"log\",\n  \"level\": \"info\",\n  \"message\": \"some event occurring\"\n}\n{\n  \"timestamp\": \"2022-07-28T17:12:44.966234771Z\",\n  \"service\": \"log\",\n  \"level\": \"warn\",\n  \"message\": \"a warning pops-up\"\n}\n{\n  \"timestamp\": \"2022-07-28T17:12:44.966246265Z\",\n  \"service\": \"log\",\n  \"level\": \"error\",\n  \"message\": \"and finally an error\",\n  \"metadata\": {\n    \"code\": 5,\n    \"some-data\": true\n  }\n}\n```\n\n\u003c/details\u003e\n\nThe logger on [line 21](./examples/logger/multilogger/multilogger.go#L21) is merging any loggers provided as input. In this example, the caller can leverage this functionality to write the same events to different outputs (with different formats), or with certain log level filters (`writerA` will register all events, while `writerB` will register events that are `error` and above).\n\nThis approach can be taken with all kinds of loggers, provided that they they implement the same methods as [`Logger` interface](./log/logger.go#L95). By all kinds of loggers, I mean those within this library, such as having a standard-error logger, as well as a gRPC Log Client configured as one, with [`MultiLogger()`](./log/multilog.go#L11). More information on [_Multi-everything_, in its own section](#multi-everything)\n\n\n\n_________________\n\n\n\n##### Output formats - [_example_](./examples/logger/formatted_logger/formatted_logger.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/log/format/text\"\n)\n\nfunc main() {\n\n\t// setup a simple text logger, with custom formatting\n\tcustTextLogger := log.New(\n\t\tlog.WithFormat(\n\t\t\ttext.New().\n\t\t\t\tColor().\n\t\t\t\tDoubleSpace().\n\t\t\t\tLevelFirst().\n\t\t\t\tUpper().\n\t\t\t\tTime(text.LTRubyDate).\n\t\t\t\tBuild(),\n\t\t),\n\t)\n\n\t// setup a simple JSON logger\n\tjsonLogger := log.New(log.CfgFormatJSON)\n\n\t// setup a simple XML logger\n\txmlLogger := log.New(log.CfgFormatXML)\n\n\t// (...)\n\n\t// join all loggers\n\tmultiLogger := log.MultiLogger(\n\t\tcustTextLogger,\n\t\tjsonLogger,\n\t\txmlLogger,\n\t\t// (...)\n\t)\n\n\t// example message to print\n\tvar msg = event.New().Message(\"message from a formatted logger\").Build()\n\n\t// print the message to standard out, with different formats\n\tmultiLogger.Log(msg)\n}\n\n```\n\n_Output_\n\n```\n[INFO]          [Sat Jul 30 13:17:31 +0000 2022]                [LOG]           message from a formatted logger\n{\"timestamp\":\"2022-07-30T13:17:31.744955941Z\",\"service\":\"log\",\"level\":\"info\",\"message\":\"message from a formatted logger\"}\n\u003centry\u003e\u003ctimestamp\u003e2022-07-30T13:17:31.744955941Z\u003c/timestamp\u003e\u003cservice\u003elog\u003c/service\u003e\u003clevel\u003einfo\u003c/level\u003e\u003cmessage\u003emessage from a formatted logger\u003c/message\u003e\u003c/entry\u003e\n```\n\n\u003c/details\u003e\n\nWhen setting up the [`Logger` interface](./log/logger.go#L95), different formatters can be passed as well. There are common formats already implemented (like JSON, XML, CSV, BSON), as well as a modular text formatter. \n\nNew formatters can also be added seamlessly by complying with their corresponding interfaces. More information on all formatters in the [_Different Formatters_ section](#different-formatters).\n\n\n\n_________________\n\n\n\n##### Modular events - [_example_](./examples/logger/modular_events/modular_events.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n)\n\nfunc main() {\n\n\t// Events can be created and customized with a builder pattern,\n\t// where each element is defined with a chained method until the\n\t// Build() method is called.\n\t//\n\t// This last method will apply the timestamp to the event and any\n\t// defaults for missing (required) fields.\n\tlog.Log(\n\t\tevent.New().\n\t\t\tPrefix(\"module\").\n\t\t\tSub(\"service\").\n\t\t\tLevel(event.Level_warn).\n\t\t\tMetadata(event.Field{\n\t\t\t\t\"data\": true,\n\t\t\t}).\n\t\t\tBuild(),\n\t\tevent.New().\n\t\t\tPrefix(\"mod\").\n\t\t\tSub(\"svc\").\n\t\t\tLevel(event.Level_debug).\n\t\t\tMetadata(event.Field{\n\t\t\t\t\"debug\": \"something something\",\n\t\t\t}).\n\t\t\tBuild(),\n\t)\n}\n```\n\n_Output_\n\n```\n[warn]  [2022-07-30T13:21:14.023201168Z]        [module]        [service]               [ data = true ] \n[debug] [2022-07-30T13:21:14.023467597Z]        [mod]   [svc]           [ debug = \"something something\" ]\n```\n\n\u003c/details\u003e\n\nThe events are created under-the-hood when using methods from the [Printer interface](./log/print.go#L18), but they can also be created using the exposed events builder. This allows using a clean approach when using the logger (using its [`Log()` method](#simple-api)), while keeping the events as detailed as you need. More information on events in the [_Feature-rich Events_ section](#feature-rich-events).\n\n\n_________________\n\n\n\n##### Channeled Logger - [_example_](./examples/logger/channeled_logger/channeled_logger.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"time\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/log/logch\"\n)\n\nfunc main() {\n\n\t// create a new, basic logger directly as a channeled logger\n\tchLogger := logch.New(log.New())\n\n\t// send messages using its Log() method directly; like the simple one:\n\tchLogger.Log(\n\t\tevent.New().Message(\"one\").Build(),\n\t\tevent.New().Message(\"two\").Build(),\n\t\tevent.New().Message(\"three\").Build(),\n\t)\n\n\t// or, call its Channels() method to work with the channels directly:\n\tmsgCh, done := chLogger.Channels()\n\n\t// send the messages in a separate goroutine, then close the logger\n\tgo func() {\n\t\tmsgCh \u003c- event.New().Message(\"four\").Build()\n\t\tmsgCh \u003c- event.New().Message(\"five\").Build()\n\t\tmsgCh \u003c- event.New().Message(\"six\").Build()\n\n\t\t// give it a millisecond to allow the last message to be printed\n\t\ttime.Sleep(time.Millisecond)\n\n\t\t// send done signal to stop the process\n\t\tdone \u003c- struct{}{}\n\t}()\n\n\t// keep-alive until the done signal is received\n\tfor {\n\t\tselect {\n\t\tcase \u003c-done:\n\t\t\treturn\n\t\t}\n\t}\n\n}\n```\n\n_Output_\n\n```\n[info]  [2022-07-31T12:15:40.702944256Z]        [log]   one\n[info]  [2022-07-31T12:15:40.703050024Z]        [log]   two\n[info]  [2022-07-31T12:15:40.703054422Z]        [log]   three\n[info]  [2022-07-31T12:15:40.703102802Z]        [log]   four\n[info]  [2022-07-31T12:15:40.703156352Z]        [log]   five\n[info]  [2022-07-31T12:15:40.703169196Z]        [log]   six\n```\n\n\u003c/details\u003e\n\nSince loggers are usually kept running in the background (as your app handles events and writes to its logger), you are perfectly able to launch this logger as a goroutine. To simplify the process, an interface is added: ([`ChanneledLogger`](./log/logch/logch.go#L12)). The gist of it is being able to directly launch a logger in a goroutine, with useful methods to interact with it (`Log()`, `Close()` and `Channels()`). More information on this logic in the [_Highly Configurable_ section](#highly-configurable).\n\n\n_________________\n\n\n\n##### Callstack in Metadata - [_example_](./examples/logger/callstack_in_metadata/callstack_md.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n)\n\n// set up an indented JSON logger; package-level as an example\n//\n// you'd set this up within your logic however it's convenient\nvar logger = log.New(log.CfgFormatJSONIndent)\n\n// placeholder operation for visibility in the callstack\nfunc operation(value int) bool {\n\treturn subOperation(value)\n}\n\n// placeholder sub-operation for visibility in the callstack\n//\n// error is printed whenever input is zero\nfunc subOperation(value int) bool {\n\tif value == 0 {\n\t\tlogger.Log(\n\t\t\tevent.New().\n\t\t\t\tLevel(event.Level_error).\n\t\t\t\tMessage(\"operation failed\").\n\t\t\t\tMetadata(event.Field{\n\t\t\t\t\t\"error\": \"input cannot be zero\", // custom metadata\n\t\t\t\t\t\"input\": value,                  // custom metadata\n\t\t\t\t}).\n\t\t\t\tCallStack(true). // add (complete) callstack to metadata\n\t\t\t\tBuild(),\n\t\t)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc main() {\n\t// all goes well until something happens within your application\n\tfor a := 5; a \u003e= 0; a-- {\n\t\tif operation(a) {\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n}\n```\n\n_Output_\n\n```json\n{\n  \"timestamp\": \"2022-08-01T16:06:46.162355505Z\",\n  \"service\": \"log\",\n  \"level\": \"error\",\n  \"message\": \"operation failed\",\n  \"metadata\": {\n    \"callstack\": {\n      \"goroutine-1\": {\n        \"id\": \"1\",\n        \"stack\": [\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/trace.(*stacktrace).getCallStack(...)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/trace/trace.go:54\"\n          },\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/trace.New(0x30?)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/trace/trace.go:41 +0x7f\"\n          },\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/event.(*EventBuilder).CallStack(0xc000077fc0, 0x30?)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/event/builder.go:99 +0x6b\"\n          },\n          {\n            \"method\": \"main.subOperation(0x0)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:31 +0x34b\"\n          },\n          {\n            \"method\": \"main.operation(...)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:15\"\n          },\n          {\n            \"method\": \"main.main()\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:42 +0x33\"\n          }\n        ],\n        \"status\": \"running\"\n      }\n    },\n    \"error\": \"input cannot be zero\",\n    \"input\": 0\n  }\n}\n```\n\n\u003c/details\u003e\n\nWhen creating an event, you're able to chain the `Callstack(all bool)` method to it, before building the event. The `bool` value it takes represents whether you want a full or trimmed callstack. \n\nMore information on event building in [the _Feature-rich Events_ section](#feature-rich-events), and the [_Callstack in metadata_ section in particular](#callstack-in-metadata).\n\n\n_________________\n\n\n#### Storing log events\n\n##### Logger as a Writer - [_example_](./examples/logger/log_as_writer/log_as_writer.go)\n\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n)\n\nfunc main() {\n\n\tlogger := log.New()\n\n\tn, err := logger.Write([]byte(\"Hello, world!\"))\n\tif err != nil {\n\t\tfmt.Println(\"errored: \", err)\n\t\tos.Exit(1)\n\t}\n\n\tfmt.Printf(\"\\n---\\nn: %v, err: %v\\n---\\n\", n, err)\n\n\tn, err = logger.Write(event.New().Message(\"Hi, world!\").Build().Encode())\n\tif err != nil {\n\t\tfmt.Println(\"errored: \", err)\n\t\tos.Exit(1)\n\t}\n\n\tfmt.Printf(\"\\n---\\nn: %v, err: %v\\n---\\n\", n, err)\n}\n```\n\n_Output_\n\n```\n[info]  [2022-07-30T12:07:44.451547181Z]        [log]   Hello, world!\n\n---\nn: 69, err: \u003cnil\u003e\n---\n[info]  [2022-07-30T12:07:44.45166375Z] [log]   Hi, world!\n\n---\nn: 65, err: \u003cnil\u003e\n---\n```\n\n\u003c/details\u003e\n\nSince the [`Logger` interface](./log/logger.go#L95) also implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer), it can be used in a broader form. The example above shows how simply passing a (string) message as a slice of bytes replicates a `log.Info()` call, and passing an encoded event will actually read its parameters (level, prefix, etc) and register an event accordingly. More information in the [_Writer Interface_ section](#writer-interface).\n\n\n_________________\n\n\n\n##### Writing events to a file - [_example_](./examples/datastore/file/logfile.go)\n\n\u003cdetails\u003e\n\n_Snippet_\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/store/fs\"\n)\n\nfunc main() {\n\t// create a new temporary file in /tmp\n\ttempF, err := ioutil.TempFile(\"/tmp\", \"zlog_test_fs-\")\n\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error creating temp file: %v\", err)\n\t}\n\tdefer os.Remove(tempF.Name())\n\n\t// use the temp file as a LogFile\n\tlogF, err := fs.New(\n\t\ttempF.Name(),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error creating logfile: %v\", err)\n\t}\n\n\t// set up a max size in MB, to auto-rotate\n\tlogF.MaxSize(50)\n\n\t// create a simple logger using the logfile as output, log messages to it\n\tvar logger = log.New(\n\t\tlog.WithOut(logF),\n\t)\n\tlogger.Log(\n\t\tevent.New().Message(\"log entry written to file\").Build(),\n\t)\n\n\t// print out file's's content\n\tb, err := os.ReadFile(tempF.Name())\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error reading logfile's data: %v\", err)\n\t}\n\n\tfmt.Println(string(b))\n}\n```\n\n_Output_\n\n```\n[info]  [2022-08-02T16:28:07.523156566Z]        [log]   log entry written to file\n```\n\n\u003c/details\u003e\n\nThis logger also exposes a package to simplify writing log events to a file. [This package](./store/fs/logfile.go) exposes methods that may be helpful, like max-size limits and auto-rotate features. \n\nMore information on this topic in [the _Logfile_ section](#logfile).\n\n\n_________________\n\n\n\n##### Writing events to a database\n\n\u003cdetails\u003e\n\n\n_SQLite Snippet_ - [_example_](./examples/datastore/db/sqlite/sqlite.go)\n\n\n\u003cdetails\u003e\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/store/db/sqlite\"\n\n\t\"os\"\n)\n\nconst (\n\tdbPathEnv string = \"SQLITE_PATH\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc setupMethodOne(dbPath string) log.Logger {\n\t// create a new DB writer\n\tdb, err := sqlite.New(dbPath)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// create a new logger with the general DB config\n\tlogger := log.New(\n\t\tlog.WithDatabase(db),\n\t)\n\n\treturn logger\n}\n\nfunc setupMethodTwo(dbPath string) log.Logger {\n\t// create one with the package function\n\treturn log.New(\n\t\tsqlite.WithSQLite(dbPath),\n\t)\n}\n\nfunc main() {\n\t// load sqlite db path from environment variable\n\tsqlitePath, ok := getEnv(dbPathEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"SQLite database path not provided, from env variable %s\", dbPathEnv)\n\t}\n\n\t// setup a logger by preparing the DB writer\n\tloggerOne := setupMethodOne(sqlitePath)\n\n\t// write a message to the DB\n\tloggerOne.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #1\").Build(),\n\t)\n\n\t// setup a logger directly as a DB writer\n\tloggerTwo := setupMethodTwo(sqlitePath)\n\n\t// write a message to the DB\n\tloggerTwo.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #2\").Build(),\n\t)\n}\n\n```\n\n\u003c/details\u003e\n\n\n\n_MySQL Snippet_ - [_example_](./examples/datastore/db/mysql/mysql.go)\n\n\n\n\u003cdetails\u003e\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/store/db/mysql\"\n\n\t\"os\"\n)\n\nconst (\n\t_         string = \"MYSQL_USER\"     // account for these env variables\n\t_         string = \"MYSQL_PASSWORD\" // account for these env variables\n\tdbAddrEnv string = \"MYSQL_HOST\"\n\tdbPortEnv string = \"MYSQL_PORT\"\n\tdbNameEnv string = \"MYSQL_DATABASE\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc setupMethodOne(dbAddr, dbPort, dbName string) log.Logger {\n\t// create a new DB writer\n\tdb, err := mysql.New(dbAddr+\":\"+dbPort, dbName)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// create a new logger with the general DB config\n\tlogger := log.New(\n\t\tlog.WithDatabase(db),\n\t)\n\n\treturn logger\n}\n\nfunc setupMethodTwo(dbAddr, dbPort, dbName string) log.Logger {\n\t// create one with the package function\n\treturn log.New(\n\t\tmysql.WithMySQL(dbAddr+\":\"+dbPort, dbName),\n\t)\n}\n\nfunc main() {\n\t// load mysql db details from environment variables\n\tmysqlAddr, ok := getEnv(dbAddrEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"MySQL database address not provided, from env variable %s\", dbAddrEnv)\n\t}\n\n\tmysqlPort, ok := getEnv(dbPortEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"MySQL database port not provided, from env variable %s\", dbPortEnv)\n\t}\n\n\tmysqlDB, ok := getEnv(dbNameEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"MySQL database name not provided, from env variable %s\", dbNameEnv)\n\t}\n\n\t// setup a logger by preparing the DB writer\n\tloggerOne := setupMethodOne(mysqlAddr, mysqlPort, mysqlDB)\n\n\t// write a message to the DB\n\tloggerOne.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #1\").Build(),\n\t)\n\n\t// setup a logger directly as a DB writer\n\tloggerTwo := setupMethodTwo(mysqlAddr, mysqlPort, mysqlDB)\n\n\t// write a message to the DB\n\tloggerTwo.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #2\").Build(),\n\t)\n}\n```\n\n\u003c/details\u003e\n\n\n\n_PostgreSQL Snippet_ - [_example_](./examples/datastore/db/postgres/postgres.go)\n\n\n\u003cdetails\u003e\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/store/db/postgres\"\n\n\t\"os\"\n)\n\nconst (\n\t_         string = \"POSTGRES_USER\"     // account for these env variables\n\t_         string = \"POSTGRES_PASSWORD\" // account for these env variables\n\tdbAddrEnv string = \"POSTGRES_HOST\"\n\tdbPortEnv string = \"POSTGRES_PORT\"\n\tdbNameEnv string = \"POSTGRES_DATABASE\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc setupMethodOne(dbAddr, dbPort, dbName string) log.Logger {\n\t// create a new DB writer\n\tdb, err := postgres.New(dbAddr, dbPort, dbName)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// create a new logger with the general DB config\n\tlogger := log.New(\n\t\tlog.WithDatabase(db),\n\t)\n\n\treturn logger\n}\n\nfunc setupMethodTwo(dbAddr, dbPort, dbName string) log.Logger {\n\t// create one with the package function\n\treturn log.New(\n\t\tpostgres.WithPostgres(dbAddr, dbPort, dbName),\n\t)\n}\n\nfunc main() {\n\t// load postgres db details from environment variables\n\tpostgresAddr, ok := getEnv(dbAddrEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Postgres database address not provided, from env variable %s\", dbAddrEnv)\n\t}\n\n\tpostgresPort, ok := getEnv(dbPortEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Postgres database port not provided, from env variable %s\", dbPortEnv)\n\t}\n\n\tpostgresDB, ok := getEnv(dbNameEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Postgres database name not provided, from env variable %s\", dbNameEnv)\n\t}\n\n\t// setup a logger by preparing the DB writer\n\tloggerOne := setupMethodOne(postgresAddr, postgresPort, postgresDB)\n\n\t// write a message to the DB\n\tloggerOne.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #1\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #1\").Build(),\n\t)\n\n\t// setup a logger directly as a DB writer\n\tloggerTwo := setupMethodTwo(postgresAddr, postgresPort, postgresDB)\n\n\t// write a message to the DB\n\tloggerTwo.Log(\n\t\tevent.New().Message(\"log entry #1 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #2 written to sqlite db writer #2\").Build(),\n\t\tevent.New().Message(\"log entry #3 written to sqlite db writer #2\").Build(),\n\t)\n}\n```\n\n\u003c/details\u003e\n\n\n\n\n_MongoDB Snippet_  - [_example_](./examples/datastore/db/mongo/mongo.go)\n\n\n\u003cdetails\u003e\n\n```go\npackage main\n\nimport (\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n\t\"github.com/zalgonoise/zlog/store/db/mongo\"\n\n\t\"os\"\n)\n\nconst (\n\t_         string = \"MONGO_USER\"     // account for these env variables\n\t_         string = \"MONGO_PASSWORD\" // account for these env variables\n\tdbAddrEnv string = \"MONGO_HOST\"\n\tdbPortEnv string = \"MONGO_PORT\"\n\tdbNameEnv string = \"MONGO_DATABASE\"\n\tdbCollEnv string = \"MONGO_COLLECTION\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc setupMethodOne(dbAddr, dbPort, dbName, dbColl string) (log.Logger, func()) {\n\t// create a new DB writer\n\tdb, err := mongo.New(dbAddr+\":\"+dbPort, dbName, dbColl)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// create a new logger with the general DB config\n\tlogger := log.New(\n\t\tlog.WithDatabase(db),\n\t)\n\n\treturn logger, func() {\n\t\tdb.Close()\n\t}\n}\n\nfunc setupMethodTwo(dbAddr, dbPort, dbName, dbColl string) log.Logger {\n\t// create one with the package function\n\treturn log.New(\n\t\tmongo.WithMongo(dbAddr+\":\"+dbPort, dbName, dbColl),\n\t)\n}\n\nfunc main() {\n\t// load mongo db details from environment variables\n\tmongoAddr, ok := getEnv(dbAddrEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Mongo database address not provided, from env variable %s\", dbAddrEnv)\n\t}\n\n\tmongoPort, ok := getEnv(dbPortEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Mongo database port not provided, from env variable %s\", dbPortEnv)\n\t}\n\n\tmongoDB, ok := getEnv(dbNameEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Mongo database name not provided, from env variable %s\", dbNameEnv)\n\t}\n\n\tmongoColl, ok := getEnv(dbCollEnv)\n\tif !ok {\n\t\tlog.Fatalf(\"Mongo collection name not provided, from env variable %s\", dbCollEnv)\n\t}\n\n\t// setup a logger by preparing the DB writer\n\tloggerOne, close := setupMethodOne(mongoAddr, mongoPort, mongoDB, mongoColl)\n\tdefer close()\n\n\t// write a message to the DB\n\tloggerOne.Log(\n\t\tevent.New().Message(\"log entry written to sqlite db writer #1\").Build(),\n\t)\n\n\t// setup a logger directly as a DB writer\n\tloggerTwo := setupMethodTwo(mongoAddr, mongoPort, mongoDB, mongoColl)\n\n\t// write a message to the DB\n\tn, err := loggerTwo.Output(\n\t\tevent.New().Message(\"log entry written to sqlite db writer #2\").Build(),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n == 0 {\n\t\tlog.Fatalf(\"zero bytes written\")\n\t}\n\n\tlog.Info(\"event written to DB writer #2 successfully\")\n}\n```\n\n\u003c/details\u003e\n\n\n\u003c/details\u003e\n\nThere are packages available in [`store/db`](./store/db) which will allow for a simple setup when preferring to write log events to a database (instead of a file or a buffer).\n\nFor this, most DBs leverage GORM to make it seamless to use different databases. Here is the list of supported database types:\n- [SQLite](#sqlite)\n- [MySQL](#mysql)\n- [PostgreSQL](#postgresql)\n- [MongoDB](#mongodb)\n\nMore information on this topic in [the _Databases_ section](#databases).\n\n\n_________________\n\n\n#### Logging events remotely\n\n##### gRPC server / client - [_example_](./examples/grpc/simple_unary_client_server/)\n\n\n\u003cdetails\u003e\n\n_Server Snippet_\n\n```go\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/zalgonoise/zlog/log\"\n\n\t\"github.com/zalgonoise/zlog/grpc/server\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc getTLSConf() server.LogServerConfig {\n\tvar tlsConf server.LogServerConfig\n\n\tvar withCert bool\n\tvar withKey bool\n\tvar withCA bool\n\n\tcertPath, ok := getEnv(\"TLS_SERVER_CERT\")\n\tif ok {\n\t\twithCert = true\n\t}\n\n\tkeyPath, ok := getEnv(\"TLS_SERVER_KEY\")\n\tif ok {\n\t\twithKey = true\n\t}\n\n\tcaPath, ok := getEnv(\"TLS_CA_CERT\")\n\tif ok {\n\t\twithCA = true\n\t}\n\n\tif withCert \u0026\u0026 withKey {\n\t\tif withCA {\n\t\t\ttlsConf = server.WithTLS(certPath, keyPath, caPath)\n\t\t} else {\n\t\t\ttlsConf = server.WithTLS(certPath, keyPath)\n\t\t}\n\t}\n\n\treturn tlsConf\n}\n\nfunc main() {\n\n\tgrpcLogger := server.New(\n\t\tserver.WithLogger(\n\t\t\tlog.New(\n\t\t\t\tlog.WithFormat(log.TextColorLevelFirstSpaced),\n\t\t\t),\n\t\t),\n\t\tserver.WithServiceLogger(\n\t\t\tlog.New(\n\t\t\t\tlog.WithFormat(log.TextColorLevelFirstSpaced),\n\t\t\t),\n\t\t),\n\t\tserver.WithAddr(\"127.0.0.1:9099\"),\n\t\tserver.WithGRPCOpts(),\n\t\tgetTLSConf(),\n\t)\n\tgrpcLogger.Serve()\n}\n```\n\n_Output_\n\n```\n[info]          [2022-08-04T11:16:29.624287477Z]                [gRPC]          [listen]                gRPC server is listening to connections         [ addr = \"127.0.0.1:9099\" ] \n[debug]         [2022-08-04T11:16:29.624489336Z]                [gRPC]          [serve]         gRPC server is running          [ addr = \"127.0.0.1:9099\" ] \n[debug]         [2022-08-04T11:16:29.624530116Z]                [gRPC]          [handler]               message handler is running\n[trace]         [2022-08-04T11:16:29.624588293Z]                [gRPC]          [Done]          listening to done signal\n```\n\n_Client snippet_\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/zalgonoise/zlog/grpc/client\"\n\t\"github.com/zalgonoise/zlog/log\"\n\t\"github.com/zalgonoise/zlog/log/event\"\n)\n\nfunc getEnv(env string) (val string, ok bool) {\n\tv := os.Getenv(env)\n\n\tif v == \"\" {\n\t\treturn v, false\n\t}\n\n\treturn v, true\n}\n\nfunc getTLSConf() client.LogClientConfig {\n\tvar tlsConf client.LogClientConfig\n\n\tvar withCert bool\n\tvar withKey bool\n\tvar withCA bool\n\n\tcertPath, ok := getEnv(\"TLS_CLIENT_CERT\")\n\tif ok {\n\t\twithCert = true\n\t}\n\n\tkeyPath, ok := getEnv(\"TLS_CLIENT_KEY\")\n\tif ok {\n\t\twithKey = true\n\t}\n\n\tcaPath, ok := getEnv(\"TLS_CA_CERT\")\n\tif ok {\n\t\twithCA = true\n\t}\n\n\tif withCA {\n\t\tif withCert \u0026\u0026 withKey {\n\t\t\ttlsConf = client.WithTLS(caPath, certPath, keyPath)\n\t\t} else {\n\t\t\ttlsConf = client.WithTLS(caPath)\n\t\t}\n\t}\n\n\treturn tlsConf\n}\n\nfunc main() {\n\tlogger := log.New(\n\t\tlog.WithFormat(log.TextColorLevelFirst),\n\t)\n\n\tgrpcLogger, errCh := client.New(\n\t\tclient.WithAddr(\"127.0.0.1:9099\"),\n\t\tclient.UnaryRPC(),\n\t\tclient.WithLogger(\n\t\t\tlogger,\n\t\t),\n\t\tclient.WithGRPCOpts(),\n\t\tgetTLSConf(),\n\t)\n\t_, done := grpcLogger.Channels()\n\n\tgrpcLogger.Log(event.New().Message(\"hello from client\").Build())\n\n\tfor i := 0; i \u003c 3; i++ {\n\t\tgrpcLogger.Log(event.New().Level(event.Level_warn).Message(fmt.Sprintf(\"warning #%v\", i)).Build())\n\t\ttime.Sleep(time.Millisecond * 50)\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase err := \u003c-errCh:\n\t\t\tpanic(err)\n\t\tcase \u003c-done:\n\t\t\treturn\n\t\t}\n\t}\n}\n```\n\n_Client output_\n\n```\n[debug] [2022-08-04T11:21:11.36101206Z] [gRPC]  [init]  setting up Unary gRPC client\n[debug] [2022-08-04T11:21:11.361184863Z]        [gRPC]  [conn]  connecting to remote    [ addr = \"127.0.0.1:9099\" ; index = 0 ] \n[debug] [2022-08-04T11:21:11.361299493Z]        [gRPC]  [conn]  connecting to remote    [ index = 0 ; addr = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.361876301Z]        [gRPC]  [conn]  dialed the address successfully [ addr = \"127.0.0.1:9099\" ; index = 0 ] \n[debug] [2022-08-04T11:21:11.361995075Z]        [gRPC]  [log]   setting up log service with connection  [ remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.362029197Z]        [gRPC]  [log]   received a new log message to register  [ timeout = 30 ; remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.362151576Z]        [gRPC]  [conn]  dialed the address successfully [ addr = \"127.0.0.1:9099\" ; index = 0 ] \n[debug] [2022-08-04T11:21:11.36221311Z] [gRPC]  [log]   setting up log service with connection  [ remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.362242582Z]        [gRPC]  [log]   received a new log message to register  [ remote = \"127.0.0.1:9099\" ; timeout = 30 ] \n[debug] [2022-08-04T11:21:11.414202683Z]        [gRPC]  [conn]  connecting to remote    [ addr = \"127.0.0.1:9099\" ; index = 0 ] \n[debug] [2022-08-04T11:21:11.415095627Z]        [gRPC]  [conn]  dialed the address successfully [ index = 0 ; addr = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.415162552Z]        [gRPC]  [log]   setting up log service with connection  [ remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.415192529Z]        [gRPC]  [log]   received a new log message to register  [ timeout = 30 ; remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.464857675Z]        [gRPC]  [conn]  connecting to remote    [ index = 0 ; addr = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.465455812Z]        [gRPC]  [conn]  dialed the address successfully [ addr = \"127.0.0.1:9099\" ; index = 0 ] \n[debug] [2022-08-04T11:21:11.465509873Z]        [gRPC]  [log]   setting up log service with connection  [ remote = \"127.0.0.1:9099\" ] \n[debug] [2022-08-04T11:21:11.465537439Z]        [gRPC]  [log]   received a new log message to register  [ remote = \"127.0.0.1:9099\" ; timeout = 30 ]\n```\n\n_Server output_\n\n```\n(...)\n[trace]         [2022-08-04T11:16:29.624588293Z]                [gRPC]          [Done]          listening to done signal\n[warn]          [2022-08-04T11:21:11.361248552Z]                [log]           warning #0\n[info]          [2022-08-04T11:21:11.361160675Z]                [log]           hello from client\n[debug]         [2022-08-04T11:21:11.362648438Z]                [gRPC]          [handler]               input log message parsed and registered\n[debug]         [2022-08-04T11:21:11.362701641Z]                [gRPC]          [handler]               input log message parsed and registered\n[warn]          [2022-08-04T11:21:11.414114247Z]                [log]           warning #1\n[debug]         [2022-08-04T11:21:11.415435299Z]                [gRPC]          [handler]               input log message parsed and registered\n[warn]          [2022-08-04T11:21:11.464787592Z]                [log]           warning #2\n[debug]         [2022-08-04T11:21:11.465792823Z]                [gRPC]          [handler]               input log message parsed and registered\n```\n\n\n\u003c/details\u003e\n\nSetting up a Log Server to register events over a network is made possible with the [gRPC](https://grpc.io/) implementation of both client and server logic. This is to make it both fast and simple to configure (remote) logging when it should not be a center-piece of your application; merely a debugging / development feature.\n\nThe [gRPC](https://grpc.io/) framework allows a quick and secure means of exchanging messages as remote procedure calls, leveraging protocol buffers to generate source code on-the-fly. As such, protocol buffers have a huge weight on this library, having the event data structure defined as one.\n\nNot only is it simple to setup and kick off a gRPC Log server, with either unary and stream RPCs and many other neat features -- it's also extensible by using the same service and message `.proto` files and integrate the Logger in your own application, by generating the Log Client code for your environment.\n\nMore information on gRPC service / server / client implementations, in [the _gRPC_ section](#grpc).\n\n\n_________________\n\n\n#### Building the library\n\nThis library uses [Bazel](https://bazel.build/) as a build system. Bazel is an amazing tool that promises a hermetic build -- one that will *always* output the same results. While this is the greatest feature of Bazel (which may not be achievable in other build tools that are dependent on your local environment), it is also very extensible in terms of integration, support for multiple languages, and clear _recipes_ of the dependencies of a particular library or binary.\n\nBazel also has a dozen features like remote execution, [like with BuildBuddy](https://www.buildbuddy.io/) and [remote caching](https://bazel.build/docs/remote-caching) (with a Docker container in another machine, for instance).\n\nTo install [Bazel](https://bazel.build/) on a machine, I prefer to use the [`bazelisk` tool](https://github.com/bazelbuild/bazelisk) which ensures that Bazel is both up-to-date and will not raise any sort of version issue later on. To install it, you can use your system's package manager, or the following Go command:\n\n```shell\ngo install github.com/bazelbuild/bazelisk@latest\n```\n\nBazel is the build tool of choice for this library as (for me, personally) it becomes very easy to configure a new build target when required; you simply need to setup the `WORKSPACE` file from presets provided by the tools used ([Go rules](https://github.com/bazelbuild/rules_go/releases), [`gazelle`](https://github.com/bazelbuild/bazel-gazelle/releases) and [`buildifier`](https://github.com/bazelbuild/buildtools/releases)); by adding the corresponding `WORKSPACE` code to your repo's file. \n\nOnce the `WORKSPACE` file is configured, you're able to use `gazelle` to auto-generate the build files for your Go code. This is done by running the `//:gazelle` targets listed in the section below:\n\n##### Targets\n\nCommand | Description\n:--:|:--:\n`bazel run //:gazelle --` | Generate the `BUILD.bazel` files for this and all subdirectories. It must be called at the root of the repository, where the `WORKSPACE` file is located\n`bazel run //:gazelle -- update-repos -from_file=go.mod` | Update the `WORKSPACE` file with the new dependencies, when they are added to Go code. It must be called at the root of the repository, where the `WORKSPACE` file is located\n`bazel build //log/...` | Builds the `log` package and all subpackages\n`bazel test //...` | Tests the entire library\n`bazel test //log/event:event_test` | Tests the `event` package, in the form of `//path/to:target`\n`bazel run //:zlog` | Executes the `:zlog` (`/main.go`) target\n`bazel run //examples/logger/simple_logger:simple_logger` | Executes the `simple_logger.go` example, in the form of `//path/to:target`\n`bazel run //:buildifier-check` | Executes the `buildifier` target in lint-checking mode for build files\n`bazel run //:buildifier-fix` | Executes the `buildifier` target and corrects any build file lint issues\n`bazel run //:lint` | Executes [`golangci-lint run ./...`](https://github.com/golangci/golangci-lint) with the `.go` files in the repository.\n\n##### Target types\n\n__Go Binary__\n\nIf you have a binary (`main.go` file), it will be listed as a binary target in a `BUILD.bazel` file, such as:\n\n```bzl\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_binary(\n    name = \"zlog\",\n    embed = [\":zlog_lib\"],\n    visibility = [\"//visibility:public\"],\n)\n```\n\nThese targets can be called with Bazel with a command such as:\n\n```shell\nbazel run //:zlog --\n```\n\n...Or if they are in some deeper directory, call the command from the repo's root:\n\n```shell\nbazel run //examples/logger/simple_logger:simple_logger\n```\n\n__Go Library__\n\nLibraries are non-executable packages of (Go) code which will provide functionality to a binary, as referenced in its imports:\n\n```bzl\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"simple_logger_lib\",\n    srcs = [\"simple_logger.go\"],\n    importpath = \"github.com/zalgonoise/zlog/examples/logger/simple_logger\",\n    visibility = [\"//visibility:private\"],\n    deps = [\"//log\"],\n)\n\ngo_binary(\n    name = \"simple_logger\",\n    embed = [\":simple_logger_lib\"], # uses the lib above\n    visibility = [\"//visibility:public\"],\n)\n```\n\n__Gazelle__\n\nUsed to generate Go build files for Bazel, it's declared in the `WORSKAPCE` file and referenced in the main `BUILD.bazel` (at the root of the repository):\n\n```bzl\nload(\"@bazel_gazelle//:def.bzl\", \"gazelle\")\n\n# gazelle:prefix github.com/zalgonoise/zlog\ngazelle(name = \"gazelle\")\n```\n\nUsage of `gazelle` just above this section, with both commands used to update Go build files and their dependencies for Bazel.\n\n__Buildifier__\n\nUsed to verify (and fix) build files that you may have created or modified. Similar to `go fmt` but for Bazel. it's declared in the `WORSKAPCE` file and referenced in the main `BUILD.bazel` (at the root of the repository), with two targets available:\n\n```bzl\nload(\"@com_github_bazelbuild_buildtools//buildifier:def.bzl\", \"buildifier\")\n\nbuildifier(\n    name = \"buildifier-check\",\n    lint_mode = \"warn\",\n    mode = \"check\",\n    multi_diff = True,\n)\n\nbuildifier(\n    name = \"buildifier-fix\",\n    lint_mode = \"fix\",\n    mode = \"fix\",\n    multi_diff = True,\n)\n```\n\n__Golangci-lint__\n\nUsed to perform a lint check in Go files, this tool will not alter any data besides outputting suggested changes to the terminal. Its AMD64 executable is being fetched from the official repository and executed with all Go files, with the command `golangci-lint run ./...`. The binary is imported in [`WORKSPACE`](./WORKSPACE):\n\n```bzl\n## golangci-lint\nhttp_archive(\n    name = \"golangci_golangci-lint\",\n    strip_prefix = \"golangci-lint-1.49.0-linux-amd64\",\n    sha256 = \"5badc6e9fee2003621efa07e385910d9a88c89b38f6c35aded153193c5125178\",\n    build_file = \"//:BUILD.golanglint-ci\",\n    urls = [\n        \"https://github.com/golangci/golangci-lint/releases/download/v1.49.0/golangci-lint-1.49.0-linux-amd64.tar.gz\",\n    ],\n)\n```\n\nIt is bundled locally in the [`BUILD.golanci-lint` file](./BUILD.golanglint-ci):\n\n```bzl\nfilegroup(\n    name = \"golangci\",\n    srcs = glob([\n        \"golangci-lint\",\n    ]),\n    visibility = [\"//visibility:public\"],\n)\n```\n\nAnd executed alongside all Go files of the repository in [`BUILD.bazel`](./BUILD.bazel#L24):\n\n```bzl\nsh_binary(\n  name = \"lint\",\n  srcs = [ \"@golangci_golangci-lint//:golangci\" ],\n  data = glob([ \"**/*.go\" ]),\n  args = [ \"run\", \"./...\" ],\n)\n```\n\n\n\n_________________\n\n\n\n### Features\n\nThis library provides a feature-rich structured logger, ready to write to many types of outputs (standard out / error, to buffers, to files and databases) and over-the-wire (via gRPC).\n\n#### Simple API\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/simple_logger_cli.png\" /\u003e\n\u003c/p\u003e\n\n\n\u003e See the [_Simple Logger_ example](#simple-logger---example)\n\n\nThe [`Logger` interface](./log/logger.go#L95) in this library provides a set complete set of idiomatic methods which allow to either control the logger:\n\n\n```go\ntype Logger interface {\n\tio.Writer\n\tPrinter\n\n\tSetOuts(outs ...io.Writer) Logger\n\tAddOuts(outs ...io.Writer) Logger\n\tPrefix(prefix string) Logger\n\tSub(sub string) Logger\n\tFields(fields map[string]interface{}) Logger\n\tIsSkipExit() bool\n}\n```\n\n...or to use its [Printer interface](./log/print.go#L18) and print messages in the `fmt.Print()` / `fmt.Println()` / `fmt.Printf()` way:\n\n\n```go\ntype Printer interface {\n\tOutput(m *event.Event) (n int, err error)\n\tLog(m ...*event.Event)\n\n\tPrint(v ...interface{})\n\tPrintln(v ...interface{})\n\tPrintf(format string, v ...interface{})\n\n\tPanic(v ...interface{})\n\tPanicln(v ...interface{})\n\tPanicf(format string, v ...interface{})\n\n\tFatal(v ...interface{})\n\tFatalln(v ...interface{})\n\tFatalf(format string, v ...interface{})\n\n\tError(v ...interface{})\n\tErrorln(v ...interface{})\n\tErrorf(format string, v ...interface{})\n\n\tWarn(v ...interface{})\n\tWarnln(v ...interface{})\n\tWarnf(format string, v ...interface{})\n\n\tInfo(v ...interface{})\n\tInfoln(v ...interface{})\n\tInfof(format string, v ...interface{})\n\n\tDebug(v ...interface{})\n\tDebugln(v ...interface{})\n\tDebugf(format string, v ...interface{})\n\n\tTrace(v ...interface{})\n\tTraceln(v ...interface{})\n\tTracef(format string, v ...interface{})\n}\n```\n\nThe logger configuration methods (listed below) can be used during runtime, to adapt the logger to the application's needs. However special care needs to be taken when calling the log writer-altering methods (`AddOuts()` and `SetOuts()`) considering that they can raise undersirable effects. It's recommended for these methods to be called when the logger is initialized in your app, if called at all.\n\n\nMethod | Description\n:--:|:--:\n[`SetOuts(...io.Writer) Logger`](./log/logger.go#L180) | sets (replaces) the defined [`io.Writer`](https://pkg.go.dev/io#Writer) in the Logger with the input list of [`io.Writer`](https://pkg.go.dev/io#Writer).\n[`AddOuts(...io.Writer) Logger`](./log/logger.go#L207) | adds (appends) the defined [`io.Writer`](https://pkg.go.dev/io#Writer) in the Logger with the input list of [`io.Writer`](https://pkg.go.dev/io#Writer).\n[`Prefix(string) Logger`](./log/logger.go#L237) | sets a logger-scoped (as opposed to message-scoped) prefix string to the logger\n[`Sub(string) Logger`](./log/logger.go#L259) | sets a logger-scoped (as opposed to message-scoped) sub-prefix string to the logger\n[`Fields(map[string]interface{}) Logger`](./log/logger.go#L275) | sets logger-scoped (as opposed to message-scoped) metadata fields to the logger\n[`IsSkipExit() bool`](./log/logger.go#L294) | returns a boolean on whether this logger is set to skip os.Exit(1) or panic() calls.\n\n\u003e Note: `SetOuts()` and `AddOuts()` methods will apply the [multi-writer pattern](#multi-everything) to the input list of [`io.Writer`](https://pkg.go.dev/io#Writer). The writers are merged as one.\n\n\u003e Note: Logger-scoped parameters (prefix, sub-prefix and metadata) allow calling either the [`Printer` interface](./log/print.go#18) methods (including event-based methods like `Log()` and `Output()`) without having to define these values. This can be especially useful when registering multiple log events in a certain module of your code -- _however_, the drawback is that these values are persisted in the logger, so you may need to unset them (calling `nil` or empty values on them).\n\n\u003e Note: `IsSkipExit()` is a useful method, used for example to determine wether a [MultiLogger](#multi-everything) should should be presented as a skip-exit-calls logger or not -- if _at least one_ configured logger in a multilogger is __not__ skipping exit calls, its output would be `false`.\n\n#### Highly configurable \n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/logger_new.png\" /\u003e\n\u003c/p\u003e\n\n\n\u003e See the [_Custom Logger_ example](#custom-logger---example)\n\n\nCreating a new logger with, for example, [`log.New()`](./log/logger.go#L134) takes any number of configurations (including none, for the default configuration). This allows added modularity to the way your logger should behave.\n\nMethod / Variable | Description\n:--:|:--:\n[`NilLogger()`](./log/conf.go#L154) | create a __nil-logger__ (that doesn't write anything to anywhere)\n[`WithPrefix(string)`](./log/conf.go#L165) | set a __default prefix__\n[`WithSub(string)`](./log/conf.#L172) | set a __default sub-prefix__\n[`WithOut(...io.Writer)`](./log/format.go#L179) | set (a) __default writer(s)__\n[`WithFormat(LogFormatter)`](./log/format.go#L33) | set the [formatter](#different-formatters) for the log event output content\n[`SkipExit` config](./log/conf.go#L78) | set the __skip-exit option__ (to skip `os.Exit(1)` and `panic()` calls)\n[`WithFilter(event.Level)`](./log/conf.go#L203) | set a __log-level filter__\n[`WithDatabase(...io.WriteCloser)`](./log/conf.go#L211) | set a __database writer__ (if [using a database](#databases))\n\nBeyond the functions and preset configurations above, the package also exposes the following preset for the [default config](./log/conf.go#L56):\n\n```go\nvar DefaultConfig LoggerConfig = \u0026multiconf{\n  confs: []LoggerConfig{\n    WithFormat(TextColorLevelFirst),\n    WithOut(),\n    WithPrefix(event.Default_Event_Prefix),\n  },\n}\n```\n\n...and the following [(initialized) presets](./log/conf.go#L77) for several useful \"defaults\":\n\n```go\nvar (\n\tDefaultCfg    = LoggerConfigs[0]  // default LoggerConfig\n\tSkipExit      = LoggerConfigs[1]  // skip-exits LoggerConfig\n\tStdOut        = LoggerConfigs[7]  // os.Stderr LoggerConfig\n\tPrefixDefault = LoggerConfigs[8]  // default-prefix LoggerConfig\n\tFilterInfo    = LoggerConfigs[9]  // Info-filtered LoggerConfig\n\tFilterWarn    = LoggerConfigs[10] // Warn-filtered LoggerConfig\n\tFilterError   = LoggerConfigs[11] // Error-filtered LoggerConfig\n\tNilConfig     = LoggerConfigs[12] // empty / nil LoggerConfig\n\tEmptyConfig   = LoggerConfigs[12] // empty / nil LoggerConfig\n)\n```\n\nIt's important to underline that the [`Logger` interface](./log/logger.go#L95) can also be launched in a goroutine without any hassle by using the [`log/logch` package](./log/logch/logch.go), for its [`ChanneledLogger` interface](./log/logch/logch.go#L12). \n\n\u003e See the [_Channeled Logger_ example](#channeled-logger---example)\n\nThis interface provides a narrower set of methods, but instead focuses on setting up controls to interact with the logger and goroutine. Note the list of methods available:\n\n\nMethod | Description\n:--:|:--:\n[`Log(msg ...*event.Event)`](./log/logch/logch.go#L91) | takes in any number of pointers to event.Event, and iterating through each of them, pushing them to the LogMessage channel.\n[`Close()`](./log/logch/logch.go#L116) | sends a signal (an empty `struct{}`) to the done channel, triggering the spawned goroutine to return\n[`Channels() (logCh chan *event.Event, done chan struct{})`](./log/logch/logch.go#L132) | returns the LogMessage channel and the done channel, so that they can be used directly with the same channel messaging patterns\n\nThe [`ChanneledLogger` interface](./log/logch/logch.go#L12) can be initialized with the [`New(log.Logger)`](./log/logch/logch.go#L48) function, which creates both message and done channels, and then kicks off the goroutine with the input logger listening to messages in it. Note that if you require multiple loggers to be converted to a [`ChanneledLogger`](./log/logch/logch.go#L12), then you should merge them with [`log.Multilogger(...log.Logger)`](#multi-everything), first.\n\n\n#### Feature-rich events\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/event_builder.png\" /\u003e\n\u003c/p\u003e\n\n\n\u003e See the [_Modular events_ example](#modular-events---example)\n\n\n##### Data structure\n\nThe events are defined in a protocol buffer format, in [`proto/event.proto`](./proto/event.proto#L20); to give it a seamless integration as a gRPC logger's request message:\n\n```protobuf\nmessage Event {\n    optional google.protobuf.Timestamp time = 1;\n    optional string prefix = 2 [ default = \"log\" ];\n    optional string sub = 3;\n    optional Level level = 4 [ default = info ];\n    required string msg = 5;\n    optional google.protobuf.Struct meta = 6;\n}\n```\n\n\n##### Event builder\n\n\nAn event is created with a builder pattern, by defining a set of elements before _spitting out_ the resulting object. \n\nThe event builder will allow chaining methods after [`event.New()`](./log/event/builder.go#L29) until the [`Build()`](./log/event/builder.go#L107) method is called. Below is a list of all available methods to the [`event.EventBuilder`](./log/event/builder.go#L14):\n\nMethod signature | Description\n:--:|:--:\n[`Prefix(p string) *EventBuilder`](./log/event/builder.go#L47) | set the prefix element\n[`Sub(s string) *EventBuilder`](./log/event/builder.go#L54) | set the sub-prefix element\n[`Message(m string) *EventBuilder`](./log/event/builder.go#L61) | set the message body element\n[`Level(l Level) *EventBuilder`](./log/event/builder.go#L68) | set the level element\n[`Metadata(m map[string]interface{}) *EventBuilder`](./log/event/builder.go#L75) | set (or add to) the metadata element\n[`CallStack(all bool) *EventBuilder`](./log/event/builder.go#L94) | grab the current call stack, and add it as a \"callstack\" object in the event's metadata\n[`Build() *Event`](./log/event/builder.go#L107) | build an event with configured elements, defaults applied where needed, and by adding a timestamp\n\n##### Log levels\n\nLog levels are defined as a protobuf enum, as [`Level` enum](./proto/event.proto#L9):\n\n```protobuf\nenum Level {\n    trace = 0;\n    debug = 1;\n    info = 2;\n    warn = 3;\n    error = 4;\n    fatal = 5;\n    reserved 6 to 8;\n    panic = 9;\n}\n```\n\nThe [generated code](./log/event/event.pb.go) creates a type and two maps which set these levels:\n\n```go\ntype Level int32\n\nconst (\n\tLevel_trace Level = 0\n\tLevel_debug Level = 1\n\tLevel_info  Level = 2\n\tLevel_warn  Level = 3\n\tLevel_error Level = 4\n\tLevel_fatal Level = 5\n\tLevel_panic Level = 9\n)\n\n// Enum value maps for Level.\nvar (\n\tLevel_name = map[int32]string{\n\t\t0: \"trace\",\n\t\t1: \"debug\",\n\t\t2: \"info\",\n\t\t3: \"warn\",\n\t\t4: \"error\",\n\t\t5: \"fatal\",\n\t\t9: \"panic\",\n\t}\n\tLevel_value = map[string]int32{\n\t\t\"trace\": 0,\n\t\t\"debug\": 1,\n\t\t\"info\":  2,\n\t\t\"warn\":  3,\n\t\t\"error\": 4,\n\t\t\"fatal\": 5,\n\t\t\"panic\": 9,\n\t}\n)\n```\n\nThis way, the enum is ready for changes in a consistent and seamless way. Here is how you'd define a log level as you create a new event:\n\n```go\n// import \"github.com/zalgonoise/zlog/log/event\"\n\ne := event.New().\n           Message(\"Critical warning!\"). // add a message body to event\n           Level(event.Level_warn).\t\t // set log level\n           Build()\t\t\t\t\t\t // build it\n```\n\nThe [`Level` type](./log/event/event.pb.go#L25) also has an exposed (custom) method, [`Int() int32`](./log/event/level.go#L6), which acts as a quick converter from the map value to an `int32` value.\n\n##### Structured metadata\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/metadata.png\" /\u003e\n\u003c/p\u003e\n\n\n_output to the call above:_\n\n```json\n{\n  \"timestamp\": \"2022-07-14T15:48:23.386745176Z\",\n  \"service\": \"module\",\n  \"module\": \"service\",\n  \"level\": \"info\",\n  \"message\": \"Logger says hi!\",\n  \"metadata\": {\n    \"multi-value\": true,\n    \"nested-metadata\": {\n      \"inner-with-type-field\": {\n        \"ok\": true\n      }\n    },\n    \"three-numbers\": [\n      0,\n      1,\n      2\n    ],\n    \"type\": \"structured logger\"\n  }\n}\n```\n\n\nMetadata is added to the [`event.Event`](./log/event/event.pb.go#L96) as a `map[string]interface{}` which is compatible with JSON output (for the most part, for most the common data types). This allows a list of key-value pairs where the key is always a string (an identifier) and the value is the data itself, regardless of the type.\n\nThe event package also exposes a unique type ([`event.Field`](./log/event/field.go#L11)):\n\n```go\n// Field type is a generic type to build Event Metadata\ntype Field map[string]interface{}\n```\n\nThe [`event.Field`](./log/event/field.go#L11) type exposes three methods to allow fast / easy conversion to [`structpb.Struct`](https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb#Struct) pointers; needed for the protobuf encoders:\n\n```go\n// AsMap method returns the Field in it's (raw) string-interface{} map format\nfunc (f Field) AsMap() map[string]interface{} {}\n\n// ToStructPB method will convert the metadata in the protobuf Event as a pointer to a\n// structpb.Struct, returning this and an error if any.\n//\n// The metadata (a map[string]interface{}) is converted to JSON (bytes), and this data is\n// unmarshalled into a *structpb.Struct object.\nfunc (f Field) ToStructPB() (*structpb.Struct, error) {}\n\n// Encode method is similar to Field.ToStructPB(), but it does not return any errors.\nfunc (f Field) Encode() *structpb.Struct {}\n```\n\n#### Callstack in metadata \n\n```json\n{\n  \"timestamp\": \"2022-07-17T14:59:41.793879193Z\",\n  \"service\": \"log\",\n  \"level\": \"error\",\n  \"message\": \"operation failed\",\n  \"metadata\": {\n    \"callstack\": {\n      \"goroutine-1\": {\n        \"id\": \"1\",\n        \"stack\": [\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/trace.(*stacktrace).getCallStack(...)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/trace/trace.go:54\"\n          },\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/trace.New(0x30?)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/trace/trace.go:41 +0x7f\"\n          },\n          {\n            \"method\": \"github.com/zalgonoise/zlog/log/event.(*EventBuilder).CallStack(0xc000075fc0, 0x30?)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/log/event/builder.go:99 +0x6b\"\n          },\n          {\n            \"method\": \"main.subOperation(0x0)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:31 +0x34b\"\n          },\n          {\n            \"method\": \"main.operation(...)\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:15\"\n          },\n          {\n            \"method\": \"main.main()\",\n            \"reference\": \"/go/src/github.com/zalgonoise/zlog/examples/logger/callstack_in_metadata/callstack_md.go:42 +0x33\"\n          }\n        ],\n        \"status\": \"running\"\n      }\n    },\n    \"error\": \"input cannot be zero\",\n    \"input\": 0\n  }\n}\n```\n\n\u003e See the [_Callstack in Metadata_ example](#callstack-in-metadata---example)\n\nIt's also possible to include the current callstack (at the time of the log event being built / created) as metadata to the log entry, by calling the event's [`Callstack(all bool)`](./log/event/builder.go#L94/) method.\n\nThis call will add the `map[string]interface{}` output of a [`trace.New(bool)` call](./log/trace/trace.go#L27), to the event's metadata element, as an object named `callstack`. This `trace` package will fetch the call stack from a [`runtime.Stack([]byte, bool)` call](https://pkg.go.dev/runtime#Stack), where the limit in size is 1 kilobyte (`make([]byte, 1024)`).\n\nThis package will parse the contents of this call and build a JSON document (as a `map[string]interface{}`) with key `callstack`, as a list of objects. These objects will have three elements:\n\n- an `id` element, as the numeric identifier for the goroutine in question\n- a `status` element, like `running`\n- a `stack` element, which is a list of objects, each object contains:\n    - a `method` element (package and method / function call)\n\t- a `reference` element (path in filesystem, with a pointer to the file and line)\n\n#### Multi-everything\n\n\u003e See the [_Multilogger_ example](#multilogger---example)\n\nIn this library, there are many implementations of `multiSomething`, following the same logic of [`io.MultiWriter()`](https://pkg.go.dev/io#MultiWriter).\n\nIn the reference above, the data structure holds a slice of [`io.Writer` interface](https://pkg.go.dev/io#Writer), and implements the same methods as an [`io.Writer`](https://pkg.go.dev/io#Writer). Its implementation of the `Write()` method will involve iterating through all configured [`io.Writer`](https://pkg.go.dev/io#Writer), and calling its own `Write()` method accordingly.\n\nIt is a very useful concept in the sense that you're able to _merge_ a slice of interfaces while working with a single one. It allows greater manouverability with maybe a few downsides or restrictions. It is not a required module but merely a helper, or a wrapper for a simple purpose.\n\nThe actual [`io.MultiWriter()`](https://pkg.go.dev/io#MultiWriter) is used when defining a [`io.Writer`](https://pkg.go.dev/io#Writer) for the logger; to allow setting it up with multiple writers:\n\n\n```go\nfunc (l *logger) SetOuts(outs ...io.Writer) Logger {\n\t// (...)\n\n\tl.out = io.MultiWriter(newouts...)\n\treturn l\n}\n```\n\u003e from [log/logger.go](./log/logger.go#L180)\n\n```go\nfunc (l *logger) AddOuts(outs ...io.Writer) Logger {\n\t// (...)\n\n\tl.out = io.MultiWriter(newouts...)\n\treturn l\n}\n```\n\u003e from [log/logger.go](./log/logger.go#L207)\n\n```go\nfunc WithOut(out ...io.Writer) LoggerConfig {\n\t// (...)\n\n\tif len(out) \u003e 1 {\n\t\treturn \u0026LCOut{\n\t\t\tout: io.MultiWriter(out...),\n\t\t}\n\t}\n\n\t// (...)\n}\n```\n\u003e from [log/conf.go](./log/conf.go#L179)\n\n...but even beyond this useful implementation, it is _mimicked_ in other pars of the code base:\n\n- as a [`LoggerConfig` merger](./log/conf.go#L25):\n\n```go\ntype LoggerConfig interface {\n\tApply(lb *LoggerBuilder)\n}\n\ntype multiconf struct {\n\tconfs []LoggerConfig\n}\n\nfunc (m multiconf) Apply(lb *LoggerBuilder) {\n\tfor _, c := range m.confs {\n\t\tif c != nil {\n\t\t\tc.Apply(lb)\n\t\t}\n\t}\n}\n\nfunc MultiConf(conf ...LoggerConfig) LoggerConfig {\n\t// (...)\n}\n```\n\n-  as a [`Logger` interface merger](./log/multilog.go#L11):\n\n```go\ntype multiLogger struct {\n\tloggers []Logger\n}\n\n// func (l *multiLogger) {every single method in Logger}\n\nfunc MultiLogger(loggers ...Logger) Logger {\n\t// (...)\n}\n```\n\n- as a [`MultiWriteCloser`](./store/db/db.go#L96) for databases:\n\n```go\nfunc MultiWriteCloser(wc ...io.WriteCloser) io.WriteCloser {\n\t// (...)\n}\n\n```\n\n- as a [`LogClientConfig` merger](./grpc/client/conf.go#L61):\n\n```go\ntype LogClientConfig interface {\n\tApply(ls *gRPCLogClientBuilder)\n}\n\ntype multiconf struct {\n\tconfs []LogClientConfig\n}\n\nfunc (m multiconf) Apply(lb *gRPCLogClientBuilder) {\n\tfor _, c := range m.confs {\n\t\tc.Apply(lb)\n\t}\n}\n\nfunc MultiConf(conf ...LogClientConfig) LogClientConfig {\n\t// (...)\n}\n```\n\n- as a [`LogServerConfig` merger](./grpc/server/conf.go#L57):\n\n```go\ntype LogServerConfig interface {\n\tApply(ls *gRPCLogServerBuilder)\n}\n\ntype multiconf struct {\n\tconfs []LogServerConfig\n}\n\nfunc (m multiconf) Apply(lb *gRPCLogServerBuilder) {\n\tfor _, c := range m.confs {\n\t\tc.Apply(lb)\n\t}\n}\n\nfunc MultiConf(conf ...LogServerConfig) LogServerConfig {\n\t// (...)\n}\n```\n\n-  as a [`GRPCLogger` interface (client) merger](./grpc/client/multilog.go#L13):\n\n```go\ntype multiLogger struct {\n\tloggers []GRPCLogger\n}\n\n// func (l *multiLogger) {every single method in Logger and ChanneledLogger}\n\nfunc MultiLogger(loggers ...GRPCLogger) GRPCLogger {\n\t// (...)\n}\n```\n\n-  as a [`LogServer` interface merger](./grpc/server/multilog.go#L13):\n\n```go\ntype multiLogger struct {\n\tloggers []LogServer\n}\n\n// func (l *multiLogger) Serve() {}\n// func (l *multiLogger) Stop() {}\n\nfunc MultiLogger(loggers ...LogServer) LogServer {\n\t// (...)\n}\n```\n\n#### Different formatters\n\n\u003e See the [_Output formats_ example](#output-formats---example)\n\nThe logger can output events in several different formats, listed below:\n\n##### Text\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/text_formatter.png\" /\u003e\n\u003c/p\u003e\n\n\nThe text formatter allows an array of options, with the text formatter sub-package exposing a builder to create a text formatter. Below is the list of methods you can expect when calling [`text.New()....Build()`](./log/format/text/text.go#L87):\n\nMethod | Description\n:--:|:--:\n[`Time(LogTimestamp)`](./log/format/text/text.go#L93) | define the timestamp format, based on the exposed [list of timestamps](#log-timestamps), from the table below\n[`LevelFirst()`](./log/format/text/text.go#L100) | place the log level as the first element in the line\n[`DoubleSpace()`](./log/format/text/text.go#L107) | place double-tab-spaces between elements (`\\t\\t`)\n[`Color()`](./log/format/text/text.go#L114) | add color to log levels (it is skipped on Windows CLI, as it doesn't support it)\n[`Upper()`](./log/format/text/text.go#L121) | make log level, prefix and sub-prefix uppercase\n[`NoTimestamp()`](./log/format/text/text.go#L128) | skip adding the timestamp element\n[`NoHeaders()`](./log/format/text/text.go#L135) | skip adding the prefix and sub-prefix elements\n[`NoLevel()`](./log/format/text/text.go#L142) | skip adding the log level element\n\n##### Log Timestamps\n\nRegarding the timestamp constraints, please note the available timestamps for the text formatter:\n\nConstant | Description\n:--:|:--:\n[`LTRFC3339Nano`](./log/format/text/text.go#L48) | Follows the standard in [`time.RFC3339Nano`](https://pkg.go.dev/time#pkg-constants)\n[`LTRFC3339`](./log/format/text/text.go#L49) | Follows the standard in [`time.RFC3339`](https://pkg.go.dev/time#pkg-constants)\n[`LTRFC822Z`](./log/format/text/text.go#L50) | Follows the standard in [`time.RFC822Z`](https://pkg.go.dev/time#pkg-constants)\n[`LTRubyDate`](./log/format/text/text.go#L51) | Follows the standard in [`time.RubyDate`](https://pkg.go.dev/time#pkg-constants)\n[`LTUnixNano`](./log/format/text/text.go#L52) | Displays a Unix timestamp, in nanos\n[`LTUnixMilli`](./log/format/text/text.go#L53) | Displays a Unix timestamp, in millis\n[`LTUnixMicro`](./log/format/text/text.go#L54) | Displays a Unix timestamp, in micros\n\nThe library also exposes a few initialized preset configurations using text formatters, as in the list below. While these are [`LoggerConfig`](./log/conf.go#L21) presets, they're a wrapper for [the same formatter](./log/format.go#L18), which is also available by not including the `Cfg` prefix:\n\n```go\nvar (\n\tCfgFormatText                  = WithFormat(text.New().Build())  // default\n\tCfgTextLongDate                = WithFormat(text.New().Time(text.LTRFC3339).Build())  // with a RFC3339 date format\n\tCfgTextShortDate               = WithFormat(text.New().Time(text.LTRFC822Z).Build())  // with a RFC822Z date format\n\tCfgTextRubyDate                = WithFormat(text.New().Time(text.LTRubyDate).Build())  // with a RubyDate date format\n\tCfgTextDoubleSpace             = WithFormat(text.New().DoubleSpace().Build()) // with double spaces\n\tCfgTextLevelFirstSpaced        = WithFormat(text.New().DoubleSpace().LevelFirst().Build()) // with level-first and double spaces\n\tCfgTextLevelFirst              = WithFormat(text.New().LevelFirst().Build()) // with level-first\n\tCfgTextColorDoubleSpace        = WithFormat(text.New().DoubleSpace().Color().Build()) // with color and double spaces\n\tCfgTextColorLevelFirstSpaced   = WithFormat(text.New().DoubleSpace().LevelFirst().Color().Build()) // with color, level-first and double spaces\n\tCfgTextColorLevelFirst         = WithFormat(text.New().LevelFirst().Color().Build()) // with color and level-first\n\tCfgTextColor                   = WithFormat(text.New().Color().Build()) // with color\n\tCfgTextOnly                    = WithFormat(text.New().NoHeaders().NoTimestamp().NoLevel().Build()) // with only the text content\n\tCfgTextNoHeaders               = WithFormat(text.New().NoHeaders().Build()) // without headers\n\tCfgTextNoTimestamp             = WithFormat(text.New().NoTimestamp().Build()) // without timestamp\n\tCfgTextColorNoTimestamp        = WithFormat(text.New().NoTimestamp().Color().Build()) // without timestamp\n\tCfgTextColorUpperNoTimestamp   = WithFormat(text.New().NoTimestamp().Color().Upper().Build()) // without timestamp and uppercase headers\n)\n\n```\n\n\n##### JSON\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/json_formatter.png\" /\u003e\n\u003c/p\u003e\n\n\nThe JSON formatter allow generating JSON events in different ways. These formatters are already initialized as [`LoggerConfig`](./log/format.go#L69) and [`LogFormatter`](./log/format.go#L39) objects.\n\n[This formatter](./log/format/json/json.go#L13) allows creating JSON events separated by newlines or not, and also to optionally add indentation:\n\n```go\ntype FmtJSON struct {\n\tSkipNewline bool\n\tIndent      bool\n}\n```\n\nAlso note how the [`LoggerConfig`](./log/format.go#L69) presets are exposed. While these are a wrapper for the same formatter, they are also available as [`LogFormatter`](./log/format.go#L39) by not including the `Cfg` prefix:\n\n```go\nvar (\n\tCfgFormatJSON                  = WithFormat(\u0026json.FmtJSON{})  // default\n\tCfgFormatJSONSkipNewline       = WithFormat(\u0026json.FmtJSON{SkipNewline: true}) // with a skip-newline config\n\tCfgFormatJSONIndentSkipNewline = WithFormat(\u0026json.FmtJSON{SkipNewline: true, Indent: true}) // with a skip-newline and indentation config\n\tCfgFormatJSONIndent            = WithFormat(\u0026json.FmtJSON{Indent: true}) // with an indentation config\n)\n```\n\n\n\n##### BSON\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/bson_formatter.png\" /\u003e\n\u003c/p\u003e\n\n\n\n##### CSV\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/csv_formatter.png\" /\u003e\n\u003c/p\u003e\n\n##### XML\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/xml_formatter.png\" /\u003e\n\u003c/p\u003e\n\n\n##### Protobuf\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/pb_formatter.png\" /\u003e\n\u003c/p\u003e\n\n\n##### Gob\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/gob_formatter.png\" /\u003e\n\u003c/p\u003e\n\n#### Data stores\n\n##### Writer interface\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/log_writer.png\" /\u003e\n\u003c/p\u003e\n\n\u003e Output of the [_Logger as a Writer_ example](#logger-as-a-writer---example)\n\n\nNot only [`Logger` interface](./log/logger.go#L95) uses the [`io.Writer` interface](https://pkg.go.dev/io#Writer) to write to its outputs with its [`Output()` method](./log/print.go#L88), it also implements it in its own [`Write()` method](./log/logger.go#L307) so it can be used directly as one. This gives the logger more flexibility as it can be vastly integrated with other modules.\n\n\nThe the input slice of bytes is decoded, in case the input is an encoded [`event.Event`](./log/event/event.pb.go#L96). If the conversion is successful, the input event is logged as-is.\n\nIf it is not an [`event.Event`](./log/event/event.pb.go#L96) (there will be an error from the [`Decode()` method](./log/event/event.go#L32)), then a new message is created where:\n- Log level is set to the default value (info)\n- Prefix, sub-prefix and metadata are added from the logger's configuration, or defaults if they aren't set.\n- input byte stream is converted to a string, and that will be log event message body:\n\n```go\nfunc (l *logger) Write(p []byte) (n int, err error) {\n\t// decode bytes\n\tm, err := event.Decode(p)\n\n\t// default to printing message as if it was a byte slice payload for the log event body\n\tif err != nil {\n\t\treturn l.Output(event.New().\n\t\t\tLevel(event.Default_Event_Level).\n\t\t\tPrefix(l.prefix).\n\t\t\tSub(l.sub).\n\t\t\tMessage(string(p)).\n\t\t\tMetadata(l.meta).\n\t\t\tBuild())\n\t}\n\n\t// print message\n\treturn l.Output(m)\n}\n```\n\n##### Logfile \n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/datastore_file.png\" /\u003e\n\u003c/p\u003e\n\n\u003e See the [example in `examples/datastore/file/`](./examples/datastore/file/logfile.go)\n\nThis library also provides a simple [`Logfile`](./store/fs/logfile.go#L18) (an actual file in the disk where log entries are written to) configuration with appealing features for simple applications.\n\n```go\ntype Logfile struct {\n\tpath   string\n\tfile   *os.File\n\tsize   int64\n\trotate int\n}\n```\n\nThe [`Logfile`](./store/fs/logfile.go#L18) exposes a few methods that could be helpful to keep the events organized:\n\nMethod | Description\n:--:|:--:\n[`MaxSize(mb int) *Logfile`](./store/fs/logfile.go#L59) | sets the rotation indicator for the Logfile, or, the target size when should the logfile be rotated (in MBs)\n[`Size() (int64, error)`](./store/fs/logfile.go#L139) | a wrapper for an [`os.File.Stat()`](https://pkg.go.dev/os#File.Stat) followed by [`fs.FileInfo.Size()`](https://pkg.go.dev/io/fs#FileInfo)\n[`IsTooHeavy() bool`](./store/fs/logfile.go#L151) | verify the file's size and rotate it if exceeding the set maximum weight (in the Logfile's rotate element)\n[`Write(b []byte) (n int, err error)`](./store/fs/logfile.go#L174) | implement the [`io.Writer` interface](https://pkg.go.dev/io#Writer), for Logfile to be compatible with Logger as an output to be used\n\n\n##### Databases\n\nIt's perfectly possible to write log events to a database instead of the terminal, a buffer, or a file. It makes it more reliable for a larger scale operation or for the long-run.\n\nThis library leverages an ORM to handle interactions with most of the databases, for the sake of simplicity and streamlined testing -- these should focus on using a database as a writer, and not re-testing the database connections, configurations, etc. This is why an ORM is being used. This library uses [GORM](https://gorm.io/) for this purpose.\n\nDatabases are not configured to loggers as an [`io.Writer` interface](https://pkg.go.dev/io#Writer) using the [`WithOut()` method](./log/conf.go#L179), but with their dedicated [`WithDatabase()` method](./log/conf.go#L211). This takes an [`io.WriterCloser` interface](https://pkg.go.dev/io#WriteCloser).\n\n\nTo create this [`io.WriterCloser`](https://pkg.go.dev/io#WriteCloser), either the database package's appropriate `New()` method can be used; or by using its package function for the same purpose, `WithXxx()`.\n\nNote the available database writers, and their features:\n\n##### SQLite\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/datastore_sqlite.png\" /\u003e\n\u003c/p\u003e\n\n\n\u003e See the [example in `examples/datastore/db/sqlite/`](./examples/datastore/db/sqlite/sqlite.go)\n\nSymbol | Type | Description\n:--:|:--:|:--:\n[`New(path string) (sqldb io.WriteCloser, err error)`](./store/db/sqlite/sqlite.go#L22) | function | takes in a path to a `.db` file; and create a new instance of a [`SQLite3`](./store/db/sqlite/sqlite.go#L15) object; returning an [`io.WriterCloser` interface](https://pkg.go.dev/io#WriteCloser) and an error.\n[`*SQLite.Create(msg ...*event.Event) error`](./store/db/sqlite/sqlite.go#L39) | method | will register any number of [`event.Event`](./log/event/event.pb.go#L96) in the SQLite database, returning an error (exposed method, but it's mostly used internally )\n[`*SQLite.Write(p []byte) (n int, err error)`](./store/db/sqlite/sqlite.go#L71) | method | implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer), for SQLite DBs to be used with a [`Logger` interface](./log/logger.go#L95), as its writer.\n[`*SQLite.Close() error`](./store/db/sqlite/sqlite.go#L97) | method | method added for compatibility with DBs that require it\n[`WithSQLite(path string) log.LoggerConfig`](./store/db/sqlite/sqlite.go#L113) | function | takes in a path to a `.db` file, and a table name; and returns a [`LoggerConfig`](./log/conf.go#L21) so that this type of writer is defined in a [`Logger`](./log/logger.go#L95)\n\n##### MySQL\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/datastore_mysql.png\" /\u003e\n\u003c/p\u003e\n\n\n\u003e See the [example in `examples/datastore/db/mysql/`](./examples/datastore/db/mysql/mysql.go)\n\n\u003e Using this package will require the following environment variables to be set:\n\nVariable | Type | Description\n:--:|:--:|:--:\n`MYSQL_USER` | string | username for the MySQL database connection\n`MYSQL_PASSWORD` | string | password for the MySQL database connection\n\nSymbol | Type | Description\n:--:|:--:|:--:\n[`New(address, database string) (sqldb io.WriteCloser, err error)`](./store/db/mysql/mysql.go#L32) | function | takes in a MySQL DB address and database name; and create a new instance of a [`MySQL`](./store/db/mysql/mysql.go#L24) object; returning an [`io.WriterCloser` interface](https://pkg.go.dev/io#WriteCloser) and an error.\n[`*MySQL.Create(msg ...*event.Event) error`](./store/db/mysql/mysql.go#L50) | method | will register any number of [`event.Event`](./log/event/event.pb.go#L96) in the MySQL database, returning an error (exposed method, but it's mostly used internally )\n[`*MySQL.Write(p []byte) (n int, err error)`](./store/db/mysql/mysql.go#L82) | method | implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer), for MySQL DBs to be used with a [`Logger` interface](./log/logger.go#L95), as its writer.\n[`*MySQL.Close() error`](./store/db/mysql/mysql.go#L112) | method | method added for compatibility with DBs that require it\n[`WithMySQL(addr, database string) log.LoggerConfig`](./store/db/mysql/mysql.go#L166) | function | takes in an address to a MySQL server, and a database name; and returns a [`LoggerConfig`](./log/conf.go#L21) so that this type of writer is defined in a [`Logger`](./log/logger.go#L95)\n\n\n##### PostgreSQL\n\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/datastore_postgres.png\" /\u003e\n\u003c/p\u003e\n\n\u003e See the [example in `examples/datastore/db/postgres/`](./examples/datastore/db/postgres/postgres.go)\n\n\u003e Using this package will require the following environment variables to be set:\n\nVariable | Type | Description\n:--:|:--:|:--:\n`POSTGRES_USER` | string | username for the Postgres database connection\n`POSTGRES_PASSWORD` | string | password for the Postgres database connection\n\nSymbol | Type | Description\n:--:|:--:|:--:\n[`New(address, port, database string) (sqldb io.WriteCloser, err error)`](./store/db/postgres/postgres.go#L33) | function | takes in a Postgres DB address, port and database name; and create a new instance of a [`Postgres`](./store/db/postgres/postgres.go#L24) object; returning an [`io.WriterCloser` interface](https://pkg.go.dev/io#WriteCloser) and an error.\n[`*Postgres.Create(msg ...*event.Event) error`](./store/db/postgres/postgres.go#L52) | method | will register any number of [`event.Event`](./log/event/event.pb.go#L96) in the Postgres database, returning an error (exposed method, but it's mostly used internally )\n[`*Postgres.Write(p []byte) (n int, err error)`](./store/db/postgres/postgres.go#L84) | method | implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer), for Postgres DBs to be used with a [`Logger` interface](./log/logger.go#L95), as its writer.\n[`*Postgres.Close() error`](./store/db/postgres/postgres.go#L118) | method | method added for compatibility with DBs that require it\n[`WithPostgres(addr, port, database string) log.LoggerConfig`](./store/db/postgres/postgres.go#L172) | function | takes in an address and port to a Postgres server, and a database name; and returns a [`LoggerConfig`](./log/conf.go#L21) so that this type of writer is defined in a [`Logger`](./log/logger.go#L95)\n\n\n\n##### MongoDB\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/ZalgoNoise/zlog/raw/media/img/datastore_mongo.png\" /\u003e\n\u003c/p\u003e\n\n\u003e See the [example in `examples/datastore/db/mongo/`](./examples/datastore/db/mongo/mongo.go)\n\n\u003e Using this package will require the following environment variables to be set:\n\nVariable | Type | Description\n:--:|:--:|:--:\n`MONGO_USER` | string | username for the Mongo database connection\n`MONGO_PASSWORD` | string | password for the Mongo database connection\n\nSymbol | Type | Description\n:--:|:--:|:--:\n[`New(address, database, collection string) (io.WriteCloser, error)`](./store/db/mongo/mongo.go#L36) | function | takes in a MongoDB address, database and collection names; and create a new instance of a [`Mongo`](./store/db/mongo/mongo.go#L26) object; returning an [`io.WriterCloser` interface](https://pkg.go.dev/io#WriteCloser) and an error.\n[`*Mongo.Create(msg ...*event.Event) error`](./store/db/mongo/mongo.go#L85) | method | will register any number of [`event.Event`](./log/event/event.pb.go#L96) in the Mongo database, returning an error (exposed method, but it's mostly used internally )\n[`*Mongo.Write(p []byte) (n int, err error)`](./store/db/mongo/mongo.go#L132) | method | implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer), for Mongo DBs to be used with a [`Logger` interface](./log/logger.go#L95), as its writer.\n[`*Mongo.Close() error`](./store/db/mongo/mongo.go#L79) | method | used to terminate the live connection to the MongoDB instance\n[`WithPostgres(addr, port, database string) log.LoggerConfig`](./store/db/mongo/mongo.go#L163) | function | takes in the address to the mongo server, and a database and collection name; and returns a [`LoggerConfig`](./log/conf.go#L21) so that this type of writer is defined in a [`Logger`](./log/logger.go#L95)\n\n#### gRPC\n\nTo provide a solution to loggers that write _over the wire_, this library implements a [gRPC](https://grpc.io/) log server with a number of useful features; as well a [gRPC](https://grpc.io/) log client which will act as a regular logger, but one that writes the log messages to a gRPC log server.\n\nThe choice for gRPC was simple. The framework is very solid and provides both fast and secure transmission of messages over a network. This is all that it's needed, right? Nope! There are also protocol buffers which helped in shaping the structure of this library in a more organized way (in my opinion).\n\nOriginally, the plan was to create the event data structures in Go (manually), and from that point integrate the logger logic as an HTTP Writer or something -- note this is already possible as the [`Logger` interface](./log/logger.go#L95) implements the [`io.Writer` interface](https://pkg.go.dev/io#Writer) already. But the problem there would be a repetition in defining the event data structure. If gRPC was in fact the choice, it would mean that there would be a data structure for Go and another for gRPC (with generated Go code, for the same thing).\n\nSo, easy-peasy: scratch off the Go data structure and keep the protocol buffers, even for (local) events and loggers. This worked great, it was easy enough to switch over, and the logic remained _kinda_ the same way, in the end.\n\nThe added benefit is that gRPC and protobuf will create this generated code (from [`proto/event.proto`](./proto/event.proto) and [`proto/service.proto`](./proto/service.proto), to [`log/event/event.pb.go`](./log/event/event.pb.go) and [`proto/service/service.pb.go`](./proto/service/service.pb.go) respectively); which it a huge boost to productivity.\n\nAn added bonus is a very lightweight encoded format of the exchanged messages, as you are able to convert the protocol buffer messages into byte slices, too.\n\nLastly, no network logic implementation headaches as creating a gRPC server and client is super smooth -- it only takes a few hours reading documentation and examples. The benefit is being able to very quickly push out a server-client solution to your app, with zero effort in the _engine_ transmitting those messages, only what you actually do with them.\n\nOn this note, it's also possible to easily implement a log client in a different programming language supported by [gRPC](https://grpc.io/). This means that if you really love this library, then you could create a Java-based gRPC log client for your Android app, while you run your gRPC log server in Go, in your big-shot datacenter.\n\nThis section will cover features that you will find in both server and client implementations.\n\n##### gRPC Log Service\n\nThe service, defined in [`proto/service/service.go`](./proto/service/service.go), is the implementation of the log server core logic, from the [gRPC generated code](./proto/service/service.pb.go).\n\nThis file will have the implementation of the [`LogServer` struct](./proto/service/service.go#L34) and its [`Log()` method](./proto/service/service.go#L75) and [`LogStream()` method](./proto/service/service.go#L92) -- on how the server handles the messages exchanged in either configuration, as a unary RPC logger or a stream RPC logger.\n\nIt also contains additional methods used within the core logic of the gRPC Log Server; such as its [`Done()` method](./proto/service/service.go#L321) and [`Stop` method](./store/service/service.go#L247).\n\n##### gRPC Log Server\n\n\n\u003e See the [examples in `examples/grpc/simple_unary_client_server/server/`](./examples/grpc/simple_unary_client_server/server/server.go) and [in `examples/grpc/simple_stream_client_server/server/`](./examples/grpc/simple_stream_client_server/server/server.go)\n\nThe [`LogServer` interface](./grpc/server/server.go#L24) is found in [`grpc/server/server.go`](./grpc/server/server.go), which defines how the server is initialized, how can it be configured, and other features. This should be perceived as a simple wrapper for setting up a gRPC server using the logic in [`proto/service/service.go`](./proto/service/service.go), with added features to make it even more useful:\n\n```go\ntype LogServer interface {\n\tServe()\n\tStop()\n\tChannels() (logCh, logSvCh chan *event.Event, errCh chan error)\n}\n```\n\nA new Log Server is created with the public function [`New(...LogServerConfig)`](./grpc/server/server.go#L102), which parses any number of configurations (covered below). The resulting [`GRPCLogServer` pointer](./grpc/server/server.go#L31) will expose the following methods:\n\nMethod | Description\n:--:|:--:\n[`Serve()`](./grpc/server/server.go#L241) | a long-running, blocking function which will launch the gRPC server \n[`Stop()`](./grpc/server/server.go#L277) | a wrapper for the routine involved to (gracefully) stop this gRPC Log Server.\n[`Channels() (logCh, logSvCh chan *event.Event, errCh chan error)`](./grpc/server/server.go#L289) | returns channels for a Log Server's I/O. It returns a channel for log messages (for actual log event writes), a channel for the service logger (the server's own logger), and an error channel to collect Log Server errors from.\n\n##### Log Server Configs\n\nThe Log Server can be configured in a number of ways, like specifying exposed address, the output logger for your events, a _service logger_ for the Log Server activity (yes, a logger for your logger), added metadata like timing, and of course TLS. \n\nHere is the list of exposed functions to allow a granular configuration of your Log Server:\n\nFunction | Description \n:--:|:--:\n[`WithAddr(string)`](./grpc/server/conf.go#L161) | takes one address for the gRPC Log Server to listen to. Defaults to `localhost:9099`\n[`WithLogger(...log.Logger)`](./grpc/server/conf.go#L180) | defines this gRPC Log Server's logger(s)\n[`WithServiceLogger(...log.Logger)`](./grpc/server/conf.go#L204) | defines this gRPC Log Server's service logger(s) (for the gRPC Log Server activity)\n[`WithServiceLoggerV(...log.Logger)`](./grpc/server/conf.go#230) | defines this gRPC Log Server's service logger(s) (for the gRPC Log Server activity) in verbose mode -- by adding an interceptor that checks each transaction, if OK or not, and for errors (added overhead)\n[`WithTiming()`](./grpc/server/conf.go#L254) | sets a gRPC Log Server's service logger to measure the time taken when executing RPCs, as added metadata (added overhead)\n[`WithGRPCOpts(...grpc.ServerOption)`](./grpc/server/conf.go#L263) | sets a gRPC Log Server's service logger to measure the time taken when executing RPCs, as added metadata (added overhead)\n[`WithTLS(certPath, keyPath string, caPath ...string)`](./grpc/server/conf.go#L288) | allows configuring TLS / mTLS for a gRPC Log Server. If only two parameters are passed (certPath, keyPath), it will run its TLS flow. If three parameters are set (certPath, keyPath, caPath), it will run its mTLS flow.\n\n\nLastly, the library also exposes some preset configurations:\n\n```go\nvar (\n\tdefaultConfig LogServerConfig = \u0026multiconf{\n\t\tconfs: []LogServerConfig{\n\t\t\tWithAddr(\"\"),\n\t\t\tWithLogger(),\n\t\t\tWithServiceLogger(),\n\t\t},\n\t}\n\n\tDefaultCfg        = LogServerConfigs[0] // default LogServerConfig\n\tServiceLogDefault = LogServerConfigs[1] // default logger as service logger\n\tServiceLogNil     = LogServerConfigs[2] // nil-service-logger LogServerConfig\n\tServiceLogColor   = LogServerConfigs[3] // colored, level-first, service logger\n\tServiceLogJSON    = LogServerConfigs[4] // JSON service logger\n\tLoggerDefault     = LogServerConfigs[5] // default logger\n\tLoggerColor       = LogServerConfigs[6] // colored, level-first logger\n\tLoggerJSON        = LogServerConfigs[7] // JSON logger\n)\n```\n\n##### gRPC Log Client\n\n\n\u003e See the [examples in `examples/grpc/simple_unary_client_server/client/`](./examples/grpc/simple_unary_client_server/client/client.go) and [in `examples/grpc/simple_stream_client_server/client/`](./examples/grpc/simple_stream_client_server/client/client.go)\n\nThere is a gRPC Log Client implementation in Go, for the sake of providing an out-of-the-box solution for communicating with the gRPC Log Server; although this can simply serve as a reference for you to implement your own gRPC Log Client -- in any of the gRPC-supported languages.\n\nThis client will act just like a regular (channeled) [`Logger` interface](./log/logger.go#L95), with added features (and configurations):\n\n```go\n// import (\n// \t\"github.com/zalgonoise/zlog/log\"\n// \t\"github.com/zalgonoise/zlog/log/logch\"\n// )\n\ntype GRPCLogger interface {\n\tlog.Logger\n\tlogch.ChanneledLogger\n}\n```\n\nCreating a new gRPC Log Client depends on whether you're setting up a Unary gRPC logger or a Stream gRPC one. The [`New(...LogClientConfig)` function](./grpc/client/client.go#L166) will serve as a factory, where depending on the configuration it will either spawn a [Unary gRPC logger](./grpc/client/client.go#L192) or a [Stream gRPC logger](./grpc/client/client.go#L199). Similar to other modules, the underlying builder pattern as you create a [`GRPCLogger`](./grpc/client/client.go#L62) will apply the default configuration before overwriting it with the user's configs.\n\nThis client will expose the public methods as per the interfaces it contains, and nothing else. There are a few things to keep in mind:\n\nMethod | Description\n:--:|:--:\n[`Close()`](./grpc/client/client.go#L582) | iterates through all (alive) connections in the `ConnAddr` map, and close them. After doing so, it sends the done signal to its channel, which causes all open streams to cancel their context and exit gracefully\n[`Output(*event.Event) (int, error)`](./grpc/client/client.go#L622) |  pushes the incoming Log Message to the message channel, which is sent to a gRPC Log Server, either via a Unary or Stream RPC. Note that it will always return `1, nil`.\n[`SetOuts(...io.Writer) log.Logger`](./grpc/client/client.go#L643) | for compatibility with the Logger interface, this method must take in io.Writers. However, this is not how the gRPC Log Client will work to register messages. Instead, the input io.Writer needs to be of type `ConnAddr`. More info on this type below. This method overwrites the configured addresses.\n[`AddOuts(...io.Writer) log.Logger`](./grpc/client/client.go#L703) | for compatibility with the Logger interface, this method must take in io.Writers. However, this is not how the gRPC Log Client will work to register messages. Instead, the input io.Writer needs to be of type `ConnAddr`. More info on this type below. This method adds addresses to the configured ones.\n[`Write([]byte) (int, error)`](./grpc/client/client.go#L776) | consider that `Write()` will return a call of `Output()`. This means that you should expect it to return `1, nil`.\n[`IsSkipExit() bool`](./grpc/client/client.go#L859) | returns a boolean on whether the gRPC Log Client's __service logger__ is set to skip os.Exit(1) or panic() calls.\n\n\n##### Log Client Configs\n\nThis Log Client can be configured in a number of ways, like specifying exposed address, the output logger for your events, a _service logger_ for the gRPC / Log Server activity (yes, a logger for your logger), added metadata like timing, and of course TLS. \n\nHere is the list of exposed functions to allow a granular configuration of your Log Server:\n\nFunction | Description \n:--:|:--:\n[`WithAddr(...string)`](./grpc/client/conf.go#L176) | take in any amount of addresses, and create a [connections map](#connection-addresses) with them, for the gRPC client to connect to the server. Defaults to `localhost:9099`\n[`StreamRPC()`](./grpc/client/conf.go#L199) | sets this gRPC Log Client type as Stream RPC\n[`UnaryRPC()`](./grpc/client/conf.go#L208) | sets this gRPC Log Client type as Unary RPC\n[`WithLogger(...log.Logger)`](./grpc/client/conf.go#L223) | defines this gRPC Log Client's service logger. This logger will register the gRPC Client transactions; and not the log messages it is handling.\n[`WithLoggerV(...log.Logger)`](./grpc/client/conf.go#L265) | defines this gRPC Log Client's service logger, in verbose mode. This logger will register the gRPC Client transactions; and not the log messages it is handling. (added overhead)\n[`WithBackoff(time.Duration, BackoffFunc)`](./grpc/client/conf.go#L310) | takes in a [`time.Duration`](https://pkg.go.dev/time#Duration) value to set as the exponential backoff module's retry deadline, and a [`BackoffFunc`](./grpc/client/backoff.go#L33) to customize the backoff pattern. [Backoff](#log-client-backoff) is further described in the next section.\n[`WithTiming()`](./grpc/client/conf.go#L340) | sets a gRPC Log Client's service logger to measure the time taken when executing RPCs. It is only an option, and is directly tied to the configured service logger. (added overhead)\n[`WithGRPCOpts(...grpc.DialOption)`](./grpc/client/conf.go#L349) | allows passing in any number of grpc.DialOption, which are added to the gRPC Log Client.\n[`Insecure()`](./grpc/client/conf.go#L372) | allows creating an insecure gRPC connection (maybe for testing purposes) by adding a new option for insecure transport credentials (no TLS / mTLS).\n[`WithTLS(string, ...string)`](./grpc/client/conf.go#L372) | allows configuring TLS / mTLS for a gRPC Log Client. If only one parameter is passed (caPath), it will run its TLS flow. If three parameters are set (caPath, certPath, keyPath), it will run its mTLS flow.\n\nLastly, the library also exposes some preset configurations:\n\n```go\nvar (\n\tdefaultConfig LogClientConfig = \u0026multiconf{\n\t\tconfs: []LogClientConfig{\n\t\t\tWithAddr(\"\"),\n\t\t\tWithGRPCOpts(),\n\t\t\tInsecure(),\n\t\t\tWithLogger(),\n\t\t\tWithBackoff(0, BackoffExponential()),\n\t\t},\n\t}\n\n\tDefaultCfg     = LogClientConfigs[0] // default LogClientConfig\n\tBackoffFiveMin = LogClientConfigs[1] // backoff config with 5-minute deadline\n\tBackoffHalfMin = LogClientConfigs[2] // backoff config with 30-second deadline\n)\n```\n\n\n##### Log Client Backoff\n\nThere is a backoff module available, in order to retry transactions in case they fail in any (perceivable) way. While this is optional, it was implemented to consider that connections over a network may fail.\n\nThis package exposes the following types to serve as a core logic for any backoff implementation:\n\n```go\ntype BackoffFunc func(uint) time.Duration\n\ntype Backoff struct {\n\tcounter     uint\n\tmax         time.Duration\n\twait        time.Duration\n\tcall        interface{}\n\tmsg         []*event.Event\n\tbackoffFunc BackoffFunc\n\tlocked      bool\n\tmu          sync.Mutex\n}\n```\n\n[`BackoffFunc`](./grpc/client/backoff.go#L33) takes in a(n unsigned) integer representing the attempt counter, and returns a time.Duration value of how much should the module wait before the next attempt / retry.\n\n[`Backoff`](./grpc/client/backoff.go#L44) struct defines the elements of a backoff module, which is configured by setting a [`BackoffFunc`](./grpc/client/backoff.go#L33) to define the interval between each attempt.\n\nBackoff will also try to act as a message buffer in case the server connection cannot be established -- as it will attempt to flush these records to the server as soon as connected. \n\nImplementing backoff logic is as simple as writing a function which will return a function with the same signature as [`BackoffFunc`](./grpc/client/backoff.go#L33). The parameters that your function takes or how it arrives to the return [`time.Duration`](https://pkg.go.dev/time#Duration) value is completely up to you.\n\nTwo examples below, one for [`NoBackoff()`](./grpc/client/backoff.go#L58) and one for [`BackoffExponential()`](./grpc/client/backoff.go#L89):\n\n```go\nfunc NoBackoff() BackoffFunc {\n\treturn func(attempt uint) time.Duration {\n\t\treturn 0\n\t}\n}\n\nfunc BackoffExponential() BackoffFunc {\n\treturn func(attempt uint) time.Duration {\n\t\treturn time.Millisecond * time.Duration(\n\t\t\tint64(math.Pow(2, float64(attempt)))+rand.New(\n\t\t\t\trand.NewSource(time.Now().UnixNano())).Int63n(1000),\n\t\t)\n\t}\n}\n```\n\nWith this in mind, regardless if the exposed [`BackoffFunc`](./grpc/client/backoff.go#L33), you may pass a deadline and your own [`BackoffFunc`](./grpc/client/backoff.go#L33) to [`WithBackoff()`](./grpc/client/conf.go#L310), as you create your gRPC Log Client.\n\nHere is a list of the preset [`BackoffFunc`](./grpc/client/backoff.go#L33) factories, available in this library:\n\nFunction | Description\n:--:|:--:\n[`NoBackoff()`](./grpc/client/backoff.go#L58) | returns a [`BackoffFunc`](./grpc/client/backoff.go#L33) that overrides the backoff module by setting a zero wait-between duration. This is detected as a sign that the module should be overriden.\n[`BackoffLinear(time.Duration)`](./grpc/client/backoff.go#L67) | returns a [`BackoffFunc`](./grpc/client/backoff.go#L33) that sets a linear backoff according to the input duration. If the input duration is 0, then the default ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fzlog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzalgonoise%2Fzlog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fzlog/lists"}