logze
is a logging package that wraps the efficient zerolog, providing a user-friendly interface similar to slog. It offers the performance and features of zerolog with an interface that developers familiar with slog can easily adapt to.
If you want to log an error in zerolog
you will write something like this:
log.Error().Err(err).Str("address", "127.0.0.1").Int("retry", n).Msg("cannot start server")
This is a quite long piece of code and you should remember the names of these methods. Let's look at a logze
example:
logze.Err(err, "cannot start server", "address", "127.0.0.1", "retry", n)
Firstly, it is shorter. Secondly, you call only one method, which names Err
, like an err
that you want to log.
In other hand, you may say, that it is better to use slog
with the similar interface instead of logze
. But logze
also provides a zerolog
efficency — it is 3 times faster that slog
and only 15% slower that zerolog
due to using Fields()
instead of separate method for each type.
To install logze
, use:
go get -u github.com/maxbolgarin/logze/v2
To start using logze
in your project, you can initialize a logger as follows:
package main
import (
"github.com/maxbolgarin/logze/v2"
)
func main() {
logger := logze.New(logze.C().WithConsoleJSON(), "application", "logze-example")
logger.Info("Starting application", "version", "1.0.0")
}
// Output: {"level":"info","message":"Starting application","version":"1.0.0","application":"logze-example","time":"2023-08-24T15:30:00Z"}
Once you have a logger, you can log messages at various levels:
logger.Trace("Trace application", "version", "1.0.0")
logger.Debug("Debugging application start", "version", "1.0.0")
logger.Info("Application started successfully", "number", 123)
logger.Warn("Low memory warning", "data", map[string]int{"key1": 1, "key2": 2})
logger.Error("Disk space low", "error", errors.New("disk space error"))
logger.Err(errors.New("disk space error"), "disk space low")
/* Example Output:
{"level":"trace","caller":"/Users/alex/code/logze/logze_test.go:210","time":"2024-12-12T17:24:24+03:00","message":"Trace application","version":"1.0.0"}
{"level":"debug","message":"Debugging application start","version":"1.0.0","time":"2023-08-24T15:30:00Z"}
{"level":"info","message":"Application started successfully","number":123,"time":"2023-08-24T15:30:00Z"}
{"level":"warning","message":"Low memory warning","data":{"key1":1,"key2":2},"time":"2023-08-24T15:30:00Z"}
{"level":"error","message":"Disk space low","error":"disk space error","time":"2023-08-24T15:30:00Z"}
{"level":"error","message":"disk space low","error":"disk space error","time":"2023-08-24T15:30:00Z"}
*/
You can also log messages with formatting and fields in the structured way: you add formatting args and then key-value pairs. For example:
logger.Debugf("Debugging application start at %s", time.Now(), "version", "1.0.0")
logger.Infof("Application %s started successfully", "name", "number", 123)
logger.Warnf("Low memory warning %d times", times)
// {"level":"debug","message":"Debugging application start at 2023-08-24T15:30:00Z","version":"1.0.0","time":"2023-08-24T15:30:00Z"}
// {"level":"info","message":"Application name started successfully","number":123,"time":"2023-08-24T15:30:00Z"}
// {"level":"warning","message":"Low memory warning 3 times","time":"2023-08-24T15:30:00Z"}
You can use the global logger from logze
package instead of creating a new one:
// Init global logger with trace level and one field pair (optional to provide options and fields)
logze.Init(logze.NewConfig().WithConsole().WithLevel(logze.TraceLevel), "foo", "bar")
// Trace log will print Caller
logze.Trace("trace message")
// Example of logging an error
logze.Err(errm.New("some_error"), "message")
// Here is the result
// 10:32:57 TRC test/main.go:16 > trace message foo=bar
// 10:32:57 ERR message error=some_error foo=bar
You can configure the logger with various options:
- Log Level: Set the log level (
trace
,debug
,info
,warn
,error
,fatal
). - Many Output Writers: Direct logs to console, files or network writers, you can provide as many
io.Writer
as you want. - Ignore Messages: Ignore specific log messages using
WithToIgnore
, that will check usingstrings.Contains
on log message. - Error Counter: Add error counters using
WithErrorCounter
orWithSimpleErrorCounter
; it may be useful for metrics to count errors. - Stack Trace: Enable/disable stack trace of errors; you can use errm to get stack trace out of the box.
- Diode Buffering: Enable/disable and configure diode buffering.
Example:
config := logze.NewConfig(w1, w2).
WithConsole().
WithLevel(logze.DebugLevel).
WithToIgnore("ignore me").
WithSimpleErrorCounter().
WithStackTrace().
WithDiodeSize(10000)
logger := logze.New(config)
- High Performance: 3 times faster than
slog
, leveragingzerolog
efficient engine. - Structured Logging: Native support for key-value pairs with any type of value.
- Easy Transition: Compatible interface with
slog
. - Easy Configuration: Simplified configuration and initialization process.
- Diode Buffering: Supports non-blocking IO operations for high performance.
- Slight Overhead: Approximately 15% slower than raw
zerolog
due to the usage of fields instead of typed methods. - Complexity: Advanced features such as diode might be complex for new users.
By default, logze
uses diode buffering to prevent blocking on log IO operations. This ensures high throughput but requires careful shutdown handling to prevent log loss. It's crucial to note that if your application shuts down immediately after logging, some log messages might be lost. This can be mitigated waiting for flushing logs or disabling the diode feature. Diode also can drop messages if too many logs are generated in a short span.
To disable diode buffering:
config := logze.NewConfig().WithNoDiode()
logger := logze.New(config)
Thoughts from benchmarks:
logze
is about 3 times faster thanslog
and for 15% slower thanzerolog
- format methods like
logze.Infof
orMsgf
doesn't add big overhead (only 30% slower and +1 alloc) - stack trace is very slow in
logze
andzerolog
- console writer is very slow in
logze
andzerolog
and should be used only in development (that the case when slog wins overlogze
- if you want to use text writer in production) logze.Err
is slightly faster thatlogze.Error
Here is result of go test -bench=. -benchmem -benchtime=2s
:
goos: darwin
goarch: arm64
cpu: Apple M1 Pro
Logging Info
and two fields:
BenchmarkZerologInfo-8 14365629 151.3 ns/op 0 B/op 0 allocs/op
BenchmarkLogzeInfo-8 13851320 171.5 ns/op 0 B/op 0 allocs/op
BenchmarkSLogInfo-8 4491897 533.2 ns/op 0 B/op 0 allocs/op
Logging Infof
(formatted) and two fields:
BenchmarkZerologInfoFormat-8 11758495 207.0 ns/op 24 B/op 1 allocs/op
BenchmarkLogzeInfoFormat-8 10047972 239.8 ns/op 24 B/op 1 allocs/op
BenchmarkSLogInfoFormat-8 4026775 601.0 ns/op 24 B/op 1 allocs/op
Logging Error
with error and two fields:
BenchmarkZerologError-8 13900646 173.6 ns/op 0 B/op 0 allocs/op
BenchmarkLogzeError-8 10827030 221.7 ns/op 0 B/op 0 allocs/op
BenchmarkSLogError-8 3757587 635.7 ns/op 0 B/op 0 allocs/op
Logging Error
with error, stack trace and two fields:
BenchmarkZerologErrorWithStack-8 545072 4379 ns/op 3298 B/op 73 allocs/op
BenchmarkLogzeErrorWithStack-8 291956 8065 ns/op 5460 B/op 121 allocs/op
BenchmarkSLogErrorWithStack-8 612562 3922 ns/op 1617 B/op 3 allocs/op
Logging Info
and two fields using text handler for console / development:
BenchmarkZerologInfoConsole-8 682558 3306 ns/op 1922 B/op 51 allocs/op
BenchmarkLogzeInfoConsole-8 704247 3366 ns/op 1922 B/op 51 allocs/op
BenchmarkSLogInfoConsole-8 4058850 588.1 ns/op 0 B/op 0 allocs/op
Additional logze
features
BenchmarkLogzeErr-8 11857878 200.9 ns/op 0 B/op 0 allocs/op
BenchmarkLogzeToIgnore5-8 10040202 238.5 ns/op 0 B/op 0 allocs/op
Contributions are welcome! Please open issues or submit pull requests.
This project is licensed under the MIT License. See the LICENSE file for details.