Blip is a high-performance, generic logging library for Go. It is designed to be fast, allocation-free, and simple to use, without compromising on performance or relying on hacks.
log.Info("Callback received", log.F{
"device_unique_id": "G4000E-1000-F",
"task_id": 123456,
"status": "success",
"template_name": "index.tpl",
})
Blip does not provide Printf-like methods, instead it encourages the use of
fields. Fields are defined as a map, making it look nicely indented with gofmt
.
There is also a standardized helper for the error type: log.Cause(err)
.
log.Error("Failed to process task", log.Cause(err), log.F{
"task_id": 123456,
})
The use of map[string]any
to define fields is optimized by the compiler and
avoids stressing the garbage collector thanks to memory pooling, making it an
ergonomic and worry-free way to log values without concern for their types.
Fields can be added to context allowing them to be propagated along with it. Such fields will be logged with every message.
ctx := context.Background()
ctx = log.ContextWithFields(ctx, log.F{
"task_id": task.ID,
})
if err := runTask(ctx, task); err != nil {
log.Error(ctx, "Task failed", log.Cause(err))
}
Blip offers both an instance-based API and a package-level API. In fact, two package-level variants: one with context, one without. These can be used directly or copied into a project as a foundation for building a custom logging package.
import "github.com/localhots/blip"
ctx := context.Background()
logger := blip.New(blip.DefaultConfig())
logger.Info(ctx, "Callback received", log.F{
"task_id": 123456,
})
import "github.com/localhots/blip/ctx/log"
ctx := context.Background()
log.Info(ctx, "Callback received", log.F{
"task_id": 123456,
})
import "github.com/localhots/blip/noctx/log"
log.Info("Callback received", log.F{
"task_id": 123456,
})
The logger can be configured with:
Level
— minimum logging level (Info
by default)Output
— log destination (stderr
by default)Encoder
— console, JSON, or a custom encoder (console by default)StackTraceLevel
— minimum level at which stack traces are logged (Error
by default)
Blip includes two built-in encoders: console and JSON, both are further customizable.
TimeFormat
TimePrecision
— when positive, caches timestamps until they change by the given amountMinMessageWidth
— controls padding between the message and fieldsSortFields
— enables sorting of fieldsColor
— enables color and bold text for messages
Fields are sorted using insertion sort, which is highly efficient for small collections.
TimeFormat
TimePrecision
— same behavior as in the console encoderBase64Encoding
— customizes how byte slices are base64-encodedKeyTime
,KeyLevel
,KeyMsg
,KeyStackTrace
— controls the JSON keys for corresponding values
Blip makes a few intentional compromises in favor of ergonomics and developer productivity: fields are passed as a map, and reflection is used instead of specialized functions to log field values. However, it mitigates most drawbacks through memory pooling for both messages and fields, and by caching timestamps to avoid expensive time serialization.
Overall, if developer productivity matters more than chasing the absolute lowest latency, and a slight performance tradeoff per log call isn't that critical, Blip might be the right choice.
While it's impossible to make a perfectly fair comparison, here are a few notes on how Blip fares against other popular logging libraries.
The main motivation behind Blip was to build a logger that is nicer than Logrus and is as fast as Zerolog.
Slog is a structured logger introduced in Go 1.21. It accepts fields as variadic
any
arguments or as typed attributes, and supports both console and JSON
encoders. It's allocation-free, fast (still ~2x slower than Blip) but not
particularly pretty.
Zerolog is an excellent logger and among the fastest available. Its performance comes from using typed functions to provide fields efficiently. When used as intended, Zerolog is roughly 25% faster than Blip.
However, if the Any
method is used instead of Str
, Int
, and other typed
functions, it starts allocating memory and falls behind, performing about twice
as slow as Blip.
In pretty mode, Zerolog first encodes messages as JSON, then parses and re-formats them for console output, which absolutely tanks its performance compared to the competition. Clearly an afterthought.
Zap is reasonably fast but doesn't offer a true pretty mode. It achieves most of its performance gains through message sampling. With sampling disabled, its performance drops and it becomes much slower than Blip in all use cases.
Logrus offers some of the nicest console formatting and was a major inspiration for Blip's console encoder. Unfortunately, it doesn't perform well, and active development has effectively stopped.
Phuslog appears to be the absolute fastest logger available. It employs a lot of complex low-level code to achieve its performance, demonstrating the significant effort that went into it.
One reason for its speed is its highly optimized time serialization.
Unfortunately, it accomplishes this by linking to unexported functions from the
time
package, which has
caused some grief
for the Go language developers.