Go logger implements logger for Golang, current implementation is majorly inspired by Python logger.
go get github.com/dl1998/go-logging
or
go install github.com/dl1998/go-logging@[version]
Note: replace [version]
with the version you want to install.
Check examples provided in the examples.
Logger supports 11 logging levels + 2 (when not set):
- All (special level, cannot be used for logging)
- Trace
- Debug
- Verbose
- Info
- Notice
- Warning
- Severe
- Error
- Alert
- Critical
- Emergency
- Null (special level, cannot be used for logging)
Default logger could be used like in the following example:
-
Standard logger
logger.Warning("Message for logging: %s.", "my message")
-
Structured logger
structuredlogger.Warning("message", "My message.")
or
structuredlogger.Warning(map[string]string{ "message": "My message.", })
By default, root logger prints on console only, and starting from Warning level. It could be changed by setting logging level:
-
Standard logger
logger.Configure(logger.NewConfiguration(logger.WithFromLevel(level.All)))
-
Structured logger
structuredlogger.Configure(logger.NewConfiguration(logger.WithFromLevel(level.All)))
After changing log level to "All" it will print messages for any level.
You could also change the format of the default structured logger by setting the format (default: json).
structuredlogger.Configure(logger.NewConfiguration(logger.WithFormat("key-value")))
All options available for the configuration are:
For Standard Logger
Method | Default | Description |
---|---|---|
WithErrorLevel | level.Error | Set logging level used to log raised or captured error. |
WithPanicLevel | level.Critical | Set logging level used to log panic. |
WithRequestTemplate | "Request: [{Method}] {URL}" | Set template for the http.Request wrapper. |
WithResponseTemplate | "Response: [{StatusCode}] {Status}" | Set template for the http.Response wrapper. |
WithFromLevel | level.Warning | Set logging level from which logger should log messages. |
WithToLevel | level.Null | Set logging level till which logger should log messages. |
WithTemplate | "%(level):%(name):%(message)" | Set template for logging message. |
WithFile | "" | Set file where to log messages, if not set, then logging to file will be disabled. |
WithName | "root" | Set logger name. |
WithTimeFormat | time.RFC3339 | Set time format for logging message. |
For Structured Logger
Method | Default | Description |
---|---|---|
WithErrorLevel | level.Error | Set logging level used to log raised or captured error. |
WithPanicLevel | level.Critical | Set logging level used to log panic. |
WithRequestMapping | map[string]string { "url": "URL", "method": "Method", } |
Set mapping for the http.Request wrapper. |
WithResponseMapping | map[string]string { "status": "Status", "status-code": "StatusCode", } |
Set mapping for the http.Response wrapper. |
WithFromLevel | level.Warning | Set logging level from which logger should log messages. |
WithToLevel | level.Null | Set logging level till which logger should log messages. |
WithTemplate | map[string]string { "timestamp": "%(timestamp)", "level": "%(level)", "name": "%(name)", } |
Set template for logging structure. |
WithFile | "" | Set file where to log messages, if not set, then logging to file will be disabled. |
WithFormat | "json" | Set format for structured logging. Could be one of the following
|
WithPretty | false | Set if json message should be pretty printed. Option works only with "json" format. |
WithKeyValueDelimiter | "=" | Set key-value delimiter (eg. "key=value", where '=' is the delimiter). Option works only with "key-value" format. |
WithPairSeparator | " " | Set key-value separator (eg. "key1=value1,key2=value2", where ',' is the separator). Option works only with "key-value" format. |
WithName | "root" | Set logger name. |
WithTimeFormat | time.RFC3339 | Set time format for logging message. |
Alternatively you could create application logger. To do this you would need to create a new logger.
-
Standard logger
applicationLogger := logger.New("application-logger", time.RFC3339)
-
Standard async logger
applicationLogger := logger.NewAsyncLogger("application-logger", time.RFC3339, 100)
-
Structured logger
applicationLogger := structuredlogger.New("application-logger", time.RFC3339)
-
Structured async logger
applicationLogger := structuredlogger.NewAsyncLogger("application-logger", time.RFC3339, 100)
After this you need to set up it, for this create a new formatter that says how to log the message by providing a template.
Available template options:
Option | Scope | Description |
---|---|---|
%(name) | Both | Logger name. |
%(level) | Both | Log level name. |
%(levelnr) | Both | Log level number. |
%(datetime) | Both | Current date and/or time formatted using time format. Default: time.RFC3339. |
%(timestamp) | Both | Current timestamp. |
%(fname) | Both | Name of the file from which logger has been called. |
%(fline) | Both | Line in the file in which logger has been called. |
%(message) | standard logger | Log message. |
-
Standard logger
applicationFormatter := formatter.New("%(datetime) [%(level)] %(message)")
-
Structured logger
-
JSON format
applicationFormatter := formatter.NewJSON(map[string]string{ "time": "%(timestamp)", "level": "%(level)", }, false)
-
Key-Value format
applicationFormatter := formatter.NewKeyValue(map[string]string{ "time": "%(timestamp)", "level": "%(level)", }, "=", " ")
-
After creation of the formatter, you need to create a new handler that tells where to write log messages.
There are three predefined types of handler (for standard and structured logger each):
-
Console Handler - it takes log level starting from which it would log messages, log level till which it would log messages, and formatter that tells how to log message. It logs messages to standard output.
newConsoleHandler := handler.NewConsoleHandler(level.Debug, level.Null, applicationFormatter)
-
Console Error Handler - it takes log level starting from which it would log messages, log level till which it would log messages, and formatter that tells how to log message. It logs messages to error output.
newConsoleErrorHandler := handler.NewConsoleErrorHandler(level.Debug, level.Null, applicationFormatter)
-
File Handler - it takes log level starting from which it would log messages, log level till which it would log messages, formatter that tells how to log message, and path to the file where to log those data.
newFileHandler := handler.NewFileHandler(level.Debug, level.Null, applicationFormatter, "system.log")
You could create your custom handler:
customHandler := handler.New(level.Debug, level.Null, applicationFormatter, os.Stdout)
It takes two additional arguments writer for standard messages and for error messages. Standard message logs till "Error" level, after this error writer is used.
After handler has been created it shall be registered.
// Register console stdout handler.
applicationLogger.AddHandler(newConsoleHandler)
// Register console stderr handler.
applicationLogger.AddHandler(newConsoleErrorHandler)
// Register file handler.
applicationLogger.AddHandler(newFileHandler)
Now it could be used to log the message, simply by calling respective level of logging and providing message with arguments.
-
Standard logger
applicationLogger.Info("My message: %s.", "logged using application logger")
-
Standard async logger
applicationLogger.Info("My message: %s.", "logged using application async logger") // Wait for all messages to be logged before exiting the program. applicationLogger.WaitToFinishLogging()
-
Structured logger
-
Varargs
applicationLogger.Info("message", "Logged using structured logger with varargs.")
-
Map
applicationLogger.Info(map[string]string{ "message": "Logged using structured logger with map.", })
-
-
Structured async logger
-
Varargs
applicationLogger.Info("message", "Logged using structured logger with varargs.") // Wait for all messages to be logged before exiting the program. applicationLogger.WaitToFinishLogging()
-
Map
applicationLogger.Info(map[string]string{ "message": "Logged using structured logger with map.", }) // Wait for all messages to be logged before exiting the program. applicationLogger.WaitToFinishLogging()
-
Async loggers are used to log messages asynchronously. It is useful when you want to log messages without blocking the
main thread. However, you need to wait for all messages to be logged before exiting the program. You can do this by
calling the WaitToFinishLogging
method, it will block the main thread until all messages are logged. Alternatively,
you can close the logger by calling the Close
method, it will close the message queue without waiting for all messages
to be logged. This is useful when you want to exit the program without waiting for all messages to be logged. After
calling the Close
method, you can open the logger again by calling the Open
method, it accepts the new message queue
size as an argument. Open
method will open the logger with the new message queue size and start listening for the
messages.
Example that waits for all messages to be logged, then close the logger and open it again with a new message queue size:
for index := 0; index < 1000; index++ {
applicationLogger.Info("Counter: %d.", index)
}
// Wait for all messages to be logged before exiting the program.
applicationLogger.WaitToFinishLogging()
// Close the logger.
applicationLogger.Close()
// Open the logger with a new message queue size.
if err := applicationLogger.Open(100); err != nil {
panic(err)
}
Note: if you assign a new message queue size that is smaller than the number of messages sent to the queue, the logger will add messages to the queue until it is not full, then it will wait (blocking the process) until the message from the queue will be processed and free up the space in the message queue.
You could wrap error or raise a new error and log error message using the logger. By default, it will log error message
using the level.Error
level. However, it could be changed by setting the error level in the logger configuration.
-
Standard logger
var err error // Raise Error with default error level (level.Error) err = applicationLogger.RaiseError("exit code: %d", 1) // Change error level applicationLogger.SetErrorLevel(level.Alert) // Capture Error with new error level (level.Alert) applicationLogger.CaptureError(err)
-
Structured logger
var err error // Raise Error with default error level (level.Error) and additional fields err = applicationLogger.RaiseError("exit code: 1", "hostname", "localhost") // Change error level applicationLogger.SetErrorLevel(level.Alert) // Capture Error with new error level (level.Alert) and additional fields applicationLogger.CaptureError(err, "hostname", "localhost")
Similarly, you could panic and log panic message using the logger. By default, it will log panic message using the
level.Critical
level. However, it could be changed by setting the panic level in the logger configuration.
-
Standard logger
// Change panic level applicationLogger.SetPanicLevel(level.Emergency) // Raise Panic with new panic level (level.Emergency) applicationLogger.Panic("exit code: %d", 1)
-
Structured logger
// Change panic level applicationLogger.SetPanicLevel(level.Emergency) // Raise Panic with new panic level (level.Emergency) and additional fields applicationLogger.Panic("exit code: 1", "hostname", "localhost")
You could wrap a struct and log its public fields using the logger. To do this, you need to provide template (standard logger), mapping (structured logger) of the struct fields to the logger fields. Optionally, for structured logger you could also provide additional fields that will be logged with the struct fields.
-
Standard logger
type MyStruct struct { String string Int int } myStruct := MyStruct{ String: "example", Int: 10, } applicationLogger.WrapStruct(level.Info, "{String}: {Int}", myStruct)
-
Structured logger
type MyStruct struct { String string Int int } myStruct := MyStruct{ String: "example", Int: 10, } applicationLogger.WrapStruct(level.Info, map[string]string{ "log-string": "String", "log-int": "Int", }, myStruct, "hostname", "localhost")
You could wrap http.Request and http.Response and log their fields using the logger. By default, logger has predefined template (standard logger), mapping (structured logger) for http.Request and http.Response fields. However, you could change it by providing your own template / mapping. Optionally, for structured logger you could also provide additional fields that will be logged with the http.Request and http.Response fields.
-
Standard logger
request, _ := http.NewRequest("GET", "http://example.com", nil) response, _ := http.Get("http://example.com") // Set custom template for http.Request and http.Response applicationLogger.SetRequestTemplate("[{Method}] {URL}") applicationLogger.SetResponseTemplate("[{StatusCode}] {Status}") applicationLogger.WrapRequest(level.Info, request) applicationLogger.WrapResponse(level.Info, response)
-
Structured logger
request, _ := http.NewRequest("GET", "http://example.com", nil) response, _ := http.Get("http://example.com") // Set custom mapping for http.Request and http.Response applicationLogger.SetRequestMapping(map[string]string{ "Method": "Method", "Url": "URL", }) applicationLogger.SetResponseMapping(map[string]string{ "StatusCode": "StatusCode", "Status": "Status", }) applicationLogger.WrapRequest(level.Info, request, "hostname", "localhost") applicationLogger.WrapResponse(level.Info, response, "hostname", "localhost")
You could also read configuration from a file. Configuration file should be in one of the following formats: *.json
,
*.yaml
, *.xml
. Configuration file should contain the following fields:
- Loggers (array of loggers)
- Name (string)
- Time Format (string)
- Error Level (string)
- Panic Level (string)
- Request Template (string)
- Response Template (string)
- Request Mapping (map of string to string)
- Response Mapping (map of string to string)
- Message Queue Size (int)
- Handlers (array of handlers)
- Type (string)
- From Level (string)
- To Level (string)
- File (string)
- Formatter (string)
- Type (string)
- Pretty Print (bool)
- Pair Separator (string)
- Key Value Delimiter (string)
- Template (template)
- String Value (string)
- Map Value (map of string to string)
Example of the configuration files:
-
JSON
{ "loggers": [ { "name": "example-logger", "time-format": "2006-01-02 15:04:05", "error-level": "error", "panic-level": "critical", "request-template": "Request: [{Method}] {URL}", "response-template": "Response: [{StatusCode}] {Status}", "request-mapping": { "method": "Method", "url": "URL" }, "response-mapping": { "status-code": "StatusCode", "status": "Status" }, "message-queue-size": 100, "handlers": [ { "type": "stdout", "from-level": "all", "to-level": "severe", "formatter": { "type": "json", "pretty-print": false, "template": { "string": "%(datetime) - %(level) - %(message)", "map": { "timestamp": "%(datetime)", "level": "%(level)", "name": "%(name)" } } } }, { "type": "stderr", "from-level": "error", "to-level": "null", "formatter": { "type": "key-value", "pair-separator": " ", "key-value-delimiter": ":", "template": { "string": "%(datetime) - %(level) - %(message)", "map": { "timestamp": "%(datetime)", "level": "%(level)", "name": "%(name)" } } } }, { "type": "file", "from-level": "all", "to-level": "null", "file": "example.log", "formatter": { "type": "json", "pretty-print": true, "template": { "string": "%(datetime) - %(level) - %(message)", "map": { "timestamp": "%(datetime)", "level": "%(level)", "name": "%(name)" } } } } ] } ] }
-
YAML
loggers: - name: example-logger time-format: "2006-01-02 15:04:05" error-level: error panic-level: critical request-template: "Request: [{Method}] {URL}" response-template: "Response: [{StatusCode}] {Status}" request-mapping: method: Method url: URL response-mapping: status-code: StatusCode status: Status message-queue-size: 100 handlers: - type: stdout from-level: all to-level: severe formatter: type: json pretty-print: false template: string: "%(datetime) - %(level) - %(message)" map: timestamp: "%(datetime)" level: "%(level)" name: "%(name)" - type: stderr from-level: error to-level: "null" formatter: type: key-value pair-separator: " " key-value-delimiter: ":" template: string: "%(datetime) - %(level) - %(message)" map: timestamp: "%(datetime)" level: "%(level)" name: "%(name)" - type: file from-level: all to-level: "null" file: example.log formatter: type: json pretty-print: true template: string: "%(datetime) - %(level) - %(message)" map: timestamp: "%(datetime)" level: "%(level)" name: "%(name)"
-
XML
<root> <loggers> <logger> <name>example-logger</name> <time-format>2006-01-02 15:04:05</time-format> <error-level>error</error-level> <panic-level>critical</panic-level> <request-template>Request: [{Method}] {URL}</request-template> <response-template>Response: [{StatusCode}] {Status}</response-template> <request-mapping> <method>Method</method> <url>URL</url> </request-mapping> <response-mapping> <status-code>StatusCode</status-code> <status>Status</status> </response-mapping> <message-queue-size>100</message-queue-size> <handlers> <handler> <type>stdout</type> <from-level>all</from-level> <to-level>severe</to-level> <formatter> <type>json</type> <pretty-print>false</pretty-print> <template> <string>%(datetime) - %(level) - %(message)</string> <map> <timestamp>%(datetime)</timestamp> <level>%(level)</level> <name>%(name)</name> </map> </template> </formatter> </handler> <handler> <type>stderr</type> <from-level>error</from-level> <to-level>null</to-level> <formatter> <type>key-value</type> <pair-separator> </pair-separator> <key-value-delimiter>:</key-value-delimiter> <template> <string>%(datetime) - %(level) - %(message)</string> <map> <timestamp>%(datetime)</timestamp> <level>%(level)</level> <name>%(name)</name> </map> </template> </formatter> </handler> <handler> <type>file</type> <from-level>all</from-level> <to-level>null</to-level> <file>example.log</file> <formatter> <type>json</type> <pretty-print>true</pretty-print> <template> <string>%(datetime) - %(level) - %(message)</string> <map> <timestamp>%(datetime)</timestamp> <level>%(level)</level> <name>%(name)</name> </map> </template> </formatter> </handler> </handlers> </logger> </loggers> </root>
To create a logger from the configuration file, you need to:
-
Create a new Parser with the Configuration object. You shall use parser from the
logger
orstructuredlogger
.-
Create a new Configuration object manually and initialize parser with it.
-
Parse configuration file to receive the Configuration. You could do this by calling the
ReadFromJSON
,ReadFromYAML
,ReadFromXML
methods respectively, it will return the Configuration object.// Parse configuration from JSON file. newConfiguration, err := parser.ReadFromJSON("path/to/configuration/file.json") if err != nil { panic(err) } // Parse configuration from YAML file. newConfiguration, err := parser.ReadFromYAML("path/to/configuration/file.yaml") if err != nil { panic(err) } // Parse configuration from XML file. newConfiguration, err := parser.ReadFromXML("path/to/configuration/file.xml") if err != nil { panic(err) }
-
Create a new Parser with the Configuration object. You shall use parser from the
logger
orstructuredlogger
packages respectively, depending on which one you need.newParser := parser.NewParser(newConfiguration)
-
-
Create Parser from the configuration file directly.
// Create a new Parser from JSON configuration file. newParser, err := parser.ParseJSON("path/to/configuration/file.json") if err != nil { panic(err) } // Create a new Parser from YAML configuration file. newParser, err := parser.ParseYAML("path/to/configuration/file.yaml") if err != nil { panic(err) } // Create a new Parser from XML configuration file. newParser, err := parser.ParseXML("path/to/configuration/file.xml") if err != nil { panic(err) }
-
-
Get a logger from the Parser.
// Standard Logger newLogger := newParser.GetLogger("example-logger") // Async Logger newLogger := newParser.GetAsyncLogger("example-logger")