This is a highly opinionated, structured logging library for Go.
This logging library will keep the last N log messages around. This allows you to dump the logs at trace or debug level if you encounter an error, contextualizing the error.
As an example, say you're doing a database transaction. During this you'll pre-process some data, prepare the transaction and then commit it. If the commit fails, you'll want to know what data was being processed, what the transaction looked like and what the error was. This library will keep the last N log messages around, so they can be dumped when you encounter an error.
func performTx() {
logger := chainsaw.NewLogger("database")
logger.SetBackTraceLevel(chainsaw.ErrorLevel) // will trigger a backtrace on error
log.Info("Preparing data")
data, err := prepareData()
// ...
log.Info("Committing transaction")
// ...
tx, err := commitTx()
if err != nil {
log.Error("Failed to commit transaction", err) // will dump the logs
}
// ...
}
It also allows you to start a stream of log messages, making it suitable for applications which have sub-applications where you might want to look at different streams of logs. Say in an application which has a bunch of goroutines which have more or less independent logs which you want to present live.
There are two types of streams you can create. One in the form of a channel, which will deliver struct with log messages. The other is a stream of bytes, in the form of an io.Writer.
Note that care should be taken to maintain these streams. If you don't read from the channel, or let the io.Writer write, it will lock up the package, likely taking your application down. Chainsaw tries to detect a dead stream channel and will log a warning and remove the channel from subsequent log messages.
Chainsaw supports fields. These can either be set on a logger using the SetFields
methods
or passed when using the fields enabled logging functions and methods (ending in 'w').
A field is a Pair
consisting of a key, string
and value, interface{}
.
You can choose to instantiate a logger. Doing this will allow you to set a name as well as to add some fields to the logger.
package main
import "github.com/celerway/chainsaw"
func main() {
chainsaw.Infof("Application %f starting up", version)
err := checkGloop()
if err != nil {
chainsaw.Fatal("Out of gloop")
}
// with fields:
chainsaw.Infow("Can't open file", chainsaw.P{"file", file}, chainsaw.P{"err", err})
}
package main
import "github.com/celerway/chainsaw"
func main() {
logger := chainsaw.MakeLogger("main")
logger.SetFields(chainsaw.P{"hostname", hostname})
logger.Error("Error in file:", err)
}
}
package main
import (
"context"
"fmt"
log "github.com/celerway/chainsaw"
"os"
"time"
)
func main() {
log.Info("Application starting up")
log.Trace("This is a trace message. Doesn't get printed directly")
logMessages := log.GetMessages(log.TraceLevel)
for i, mess := range logMessages {
fmt.Println("Fetched message: ", i, ":", mess.Content)
}
log.RemoveWriter(os.Stdout) // stop writing to stdout
log.Flush()
log.Info("Doesn't show up on screen.")
ctx, cancel := context.WithCancel(context.Background())
stream := log.GetStream(ctx)
go func() {
for mess := range stream {
fmt.Println("From stream: ", mess.Content)
}
fmt.Println("Log stream is closed")
}()
log.Info("Should reach the stream above")
cancel()
time.Sleep(time.Second)
}
The logging is async so messages might take a bit of time before showing up. Control messages are synchronously, however, so a Flush will not return unless all pending messages are processed.
log.go is generated by gen/main.go
chainsaw is not fast - it is about as slow as logrus. Channels in Go aren't really that fast and if you're logging thousands of messages per second you might wanna at a high performance logging framework like zerolog.
On my laptop a regular log invocation takes about 600ns and control messages take 500ns.
- Increase compatibility with other logging libraries.
- Add support for formatting
- Documentation, at least when we're somewhat certain that the basic design is sane.
- Adapt a logging interface. Shame there isn't one in Stdlib.