Skip to content
/ log Public
forked from phuslu/log

Fast Structured Logging for Humans

License

Notifications You must be signed in to change notification settings

httpsgithu/log

 
 

Repository files navigation

Structured Logging for Humans

gopkg goreport coverage

Features

  • No External Dependences
  • Intuitive Interfaces
  • JSON/TSV/Printf Loggers
  • Rotating File Writer
  • Pretty Console Writer
  • Dynamic Log Level
  • High Performance

Interfaces

Logger

// DefaultLogger is the global logger.
var DefaultLogger = Logger{
	Level:      DebugLevel,
	Caller:     0,
	TimeField:  "",
	TimeFormat: "",
	Timestamp:  false,
	HostField:  "",
	Writer:     os.Stderr,
}

// A Logger represents an active logging object that generates lines of JSON output to an io.Writer.
type Logger struct {
	// Level defines log levels.
	Level Level

	// Caller determines if adds the file:line of the "caller" key.
	Caller int

	// TimeField defines the time filed name in output.  It uses "time" in if empty.
	TimeField string

	// TimeFormat specifies the time format in output. It uses time.RFC3389 in if empty.
	TimeFormat string

	// Timestamp determines if time is formatted as an UNIX timestamp as integer.
	// If set, the value of TimeField and TimeFormat will be ignored.
	Timestamp bool

	// HostField specifies the key for hostname in output if not empty
	HostField string

	// Writer specifies the writer of output. It uses os.Stderr in if empty.
	Writer io.Writer
}

FileWriter & ConsoleWriter

// FileWriter is an io.WriteCloser that writes to the specified filename.
type FileWriter struct {
	// Filename is the file to write logs to.  Backup log files will be retained
	// in the same directory.
	Filename string

	// FileMode represents the file's mode and permission bits.  The default
	// mode is 0644
	FileMode os.FileMode

	// MaxSize is the maximum size in megabytes of the log file before it gets
	// rotated.
	MaxSize int64

	// MaxBackups is the maximum number of old log files to retain.  The default
	// is to retain all old log files
	MaxBackups int

	// LocalTime determines if the time used for formatting the timestamps in
	// backup files is the computer's local time.  The default is to use UTC
	// time.
	LocalTime bool

	// HostName determines if the hostname used for formatting in backup files.
	HostName bool
}

// ConsoleWriter parses the JSON input and writes it in an
// (optionally) colorized, human-friendly format to Out.
type ConsoleWriter struct {
	ANSIColor bool
}

Getting Started

Simple Logging Example

A out of box example. playground

package main

import (
	"github.com/phuslu/log"
)

func main() {
	log.Printf("Hello, %s", "世界")
	log.Info().Str("foo", "bar").Int("number", 42).Msg("a structured logger")
}

// Output:
//   {"time":"2020-03-22T09:58:41.828Z","level":"debug","message":"Hello, 世界"}
//   {"time":"2020-03-22T09:58:41.828Z","level":"info","foo":"bar","number":42,"message":"a structure logger"}

Note: By default log writes to os.Stderr

Customize the configuration and formatting:

To customize logger filed name and format. playground

log.DefaultLogger = log.Logger{
	Level:      log.InfoLevel,
	Caller:     1,
	TimeField:  "date",
	TimeFormat: "2006-01-02",
	HostField:  "host",
	Writer:     os.Stderr,
}
log.Info().Str("foo", "bar").Msgf("hello %s", "world")

// Output: {"date":"2019-07-04","level":"info","host":"hk","caller":"test.go:42","foo":"bar","message":"hello world"}

Rotating File Writer

package main

import (
	"time"

	"github.com/phuslu/log"
	"github.com/robfig/cron/v3"
)

func main() {
	logger := log.Logger{
		Level:      log.ParseLevel("info"),
		Writer:     &log.FileWriter{
			Filename:   "main.log",
			FileMode:   0600,
			MaxSize:    50*1024*1024,
			MaxBackups: 7,
			LocalTime:  false,
		},
	}

	runner := cron.New(cron.WithSeconds(), cron.WithLocation(time.UTC))
	runner.AddFunc("0 0 * * * *", func() { logger.Writer.(*log.FileWriter).Rotate() })
	go runner.Run()

	for {
		time.Sleep(time.Second)
		logger.Info().Msg("hello world")
	}
}

Pretty Console Writer

To log a human-friendly, colorized output, use log.ConsoleWriter. playground

if log.IsTerminal(os.Stderr.Fd()) {
	log.DefaultLogger = log.Logger{
		Caller: 1,
		Writer: &log.ConsoleWriter{ANSIColor: true},
	}
}

log.Printf("a printf style line")
log.Info().Err(errors.New("an error")).Int("everything", 42).Str("foo", "bar").Msg("hello world")

Pretty logging

Note: pretty logging also works on windows console

Dynamic log Level

To change log level on the fly, use log.DefaultLogger.SetLevel. playground

log.DefaultLogger.SetLevel(log.InfoLevel)
log.Debug().Msg("debug log")
log.Info().Msg("info log")
log.Warn().Msg("warn log")
log.DefaultLogger.SetLevel(log.DebugLevel)
log.Debug().Msg("debug log")
log.Info().Msg("info log")

// Output:
//   {"time":"2020-03-24T05:06:54.674Z","level":"info","message":"info log"}
//   {"time":"2020-03-24T05:06:54.674Z","level":"warn","message":"warn log"}
//   {"time":"2020-03-24T05:06:54.675Z","level":"debug","message":"debug log"}
//   {"time":"2020-03-24T05:06:54.675Z","level":"info","message":"info log"}

Logging to syslog

package main

import (
	"log/syslog"

	"github.com/phuslu/log"
)

func main() {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, 0)
	if err != nil {
		log.Fatal().Err(err).Msg("new syslog error")
	}

	log.DefaultLogger.Writer = logger.Writer()
	log.Info().Str("foo", "bar").Msg("a syslog message")
}

High Performance

A quick and simple benchmark with zap/zerolog/onelog

// go test -v -run=none -bench=. -benchtime=10s -benchmem log_test.go
package main

import (
	"io/ioutil"
	"testing"
	"time"

	"github.com/francoispqt/onelog"
	"github.com/phuslu/log"
	"github.com/rs/zerolog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var fakeMessage = "Test logging, but use a somewhat realistic message length. "

func BenchmarkZap(b *testing.B) {
	logger := zap.New(zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		zapcore.AddSync(ioutil.Discard),
		zapcore.InfoLevel,
	))
	for i := 0; i < b.N; i++ {
		logger.Info(fakeMessage, zap.String("foo", "bar"), zap.Int("int", 123))
	}
}

func BenchmarkOneLog(b *testing.B) {
	logger := onelog.New(ioutil.Discard, onelog.INFO)
	logger.Hook(func(e onelog.Entry) { e.Int64("time", time.Now().Unix()) })
	for i := 0; i < b.N; i++ {
		logger.InfoWith(fakeMessage).String("foo", "bar").Int("int", 123).Write()
	}
}

func BenchmarkZeroLog(b *testing.B) {
	logger := zerolog.New(ioutil.Discard).With().Timestamp().Logger()
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	for i := 0; i < b.N; i++ {
		logger.Info().Str("foo", "bar").Int("int", 123).Msg(fakeMessage)
	}
}

func BenchmarkPhusLog(b *testing.B) {
	logger := log.Logger{
		Timestamp: true,
		Writer:    ioutil.Discard,
	}
	for i := 0; i < b.N; i++ {
		logger.Info().Str("foo", "bar").Int("int", 123).Msg(fakeMessage)
	}
}

Performance results on my laptop:

BenchmarkZap-16        	14383234	       828 ns/op	     128 B/op	       1 allocs/op
BenchmarkOneLog-16     	42118165	       285 ns/op	       0 B/op	       0 allocs/op
BenchmarkZeroLog-16    	46848562	       252 ns/op	       0 B/op	       0 allocs/op
BenchmarkPhusLog-16    	78545636	       155 ns/op	       0 B/op	       0 allocs/op

About

Fast Structured Logging for Humans

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 100.0%