/log

Structured, canonical, context-based log lines for any Golang logging backend

Primary LanguageGoMIT LicenseMIT

log

go.dev reference GitHub Workflow Status

github.com/ucarion/log provides structured, canonical, context-based logs in Golang on top of any existing logging backend. The core idea of canonical logging is to output fewer, better logs -- say, one per HTTP request served. With this package, you will add a bit more context to the log in many places:

// Set a field to the current canonical log line -- this does not output anything.
log.Set("user_cache_latency_seconds", 5)

And then at the end of every request, you fire off a single log:

// This will write out all of the fields you've added using Set, along with a message.
log.Log(ctx, "http_request")

You'll find this approach will give you far more useful logs. For instance, you can easily load your logs into an analytics database, and then perform queries to better understand the performance of your application.

Installation

To install this package, run:

go get github.com/ucarion/log

Usage

First, you need to choose what backend you want to use. See the Backends section in this README for more, but supposing you wanted to use Logrus:

// All you have to do to enable Logrus as your logger is import:
import _ "github.com/ucarion/log/loggers/logrus"

In most of your code, using github.com/ucarion/log looks like this:

// Prepare a context to keep track of the log-line properties.
ctx := log.NewContext(context.Background())

// Add some properties to the message.
log.Set(ctx, "start_time", time.Now())
log.Set(ctx, "rpc", "ListUsers")
log.Set(ctx, "err", err)

// Fire off a canonical log line.
log.Log("request")

For a more advanced usage, you can also have nested properties by using WithPrefix:

ctx1 := log.NewContext(context.Background())
log.Set(ctx1, "root", "level")

ctx2 := log.WithPrefix(ctx1, "nested")
log.Set(ctx2, "foo", "bar")

// Outputs something like, depending your backend:
//
// { "msg": "example", "data": { "root": "level", "nested": { "foo": "bar" }}}}
log.Log("example")

Backends

github.com/ucarion/log is agnostic to what backend you want to use. But for your convenience, it ships with support for a few popular backends.

The standard library's log

To use github.com/ucarion/log with the standard library log package, just import:

import _ "github.com/ucarion/log/loggers/log"

See examples/log for a working example you can play with. See the GoDocs for details on how you can use other loggers than the standard stdlib logger.

sirupsen/logrus

To use github.com/ucarion/log with Logrus, just import:

import _ "github.com/ucarion/log/loggers/logrus"

See examples/logrus for a working example you can play with. See the GoDocs for details on how you can use other loggers than the standard Logrus logger.

uber/zap

To use github.com/ucarion/log with Uber's Zap, just import:

import _ "github.com/ucarion/log/loggers/zap"

You'll also need to update the global logger, if you haven't done so already. You can do this by running:

// You could also use NewDevelopment or NewProduction, or a custom Zap logger.
zap.ReplaceGlobals(zap.NewExample())

See examples/zap for a working example you can play with. See the GoDocs for details on how you can use other loggers than the global Zap logger.

segmentio/events

To use github.com/ucarion/log with Segment's events, just import:

import _ "github.com/ucarion/log/loggers/events"

See examples/events for a working example you can play with. See the GoDocs for details on how you can use other loggers than the standard events logger.

Implementing your own backend

Implementing your own custom backend for github.com/ucarion/log is just a question of updating log.Logger, which just requires confirming to an interface:

var Logger interface {
	Log(msg string, fields map[string]interface{})
}

For example, here's some working code that uses fmt.Println as a logging backend:

type fmtLogger struct{}

func (l *fmtLogger) Log(msg string, fields map[string]interface{}) {
	fmt.Println("my custom logger", msg, fields)
}

func main() {
	log.Logger = &fmtLogger{}

	// ...
}

See examples/custom for a working example you can play with.