Logur is an opinionated collection of logging best practices

Related tags

logging go-logging logur
Overview

Logur

Mentioned in Awesome Go

GitHub Workflow Status Codecov Go Report Card Go Version PkgGoDev

Logur is an opinionated collection of logging best practices.

Table of Contents

Preface

Logur is an opinionated logging library targeted at producing (application) logs. It does not try to solve every problem around logging, only a few considered important by the developers, thus it's highly opinionated.

The main focus of the library:

  • provide a unified interface that does not require developers to import external dependencies
  • encourage leveled and structured logging
  • provide tools for easy integration of other logging libraries and components

Logur does not care about log output, you can use whatever library/formatting/forwarder you want (ie. use an existing logging library with one of the adapters).

Despite the opinionated nature, Logur encourages you to create custom logging interfaces for your needs and only use Logur as an integration layer/tool. Use the features you need/want and just omit the rest.

As mentioned above, Logur aims to cover only 95% of the use cases, so Logur might not be for you. Read on for more details.

Features

Installation

Logur uses Go Modules introduced in Go 1.11, so the recommended way is using go get:

$ go get logur.dev/logur

Usage

An opinionated library should come with some best practices for usage and so does this one.

TL;DR: See example usage and best practices in github.com/sagikazarmark/modern-go-application. Also, check out the example package in this repository.

Create a custom interface

Interfaces should be defined by the consumer, so the Logger interface in this library should not be used directly. A custom interface should be defined instead:

type MyLogger interface {
	Trace(msg string, fields ...map[string]interface{})
	Debug(msg string, fields ...map[string]interface{})
	Info(msg string, fields ...map[string]interface{})
	Warn(msg string, fields ...map[string]interface{})
	Error(msg string, fields ...map[string]interface{})
}

In a lucky scenario all Logur loggers are compatible with the above interface, so you can just use them in your code:

func main() {
    logger := logur.NewNoopLogger()

    myFunc(logger)
}

func myFunc(logger MyLogger) {
	logger.Debug("myFunc ran")
	// OR
	logger.Debug("myFunc ran", map[string]interface{}{"key": "value"})
}

In case you need to populate the logger with some common context, the interface becomes a bit more complicated:

type MyLogger interface {
	Trace(msg string, fields ...map[string]interface{})
	Debug(msg string, fields ...map[string]interface{})
	// ...
	WithFields(fields map[string]interface{}) MyLogger
}

As you can see MyLogger holds a reference to itself, which makes it incompatible with the Logur implementations. The solution in this case is implementing a custom adapter:

type myLogger struct {
	logger logur.Logger
}

func (l *myLogger) Debug(msg string, fields ...map[string]interface{}) { l.logger.Debug(msg, fields...) }
// ...
func (l *myLogger) WithFields(fields map[string]interface{}) MyLogger {
	return myLogger{logur.WithFields(l.logger, fields)}
}

Now you can easily use Logur provided loggers inside your code:

func main() {
    logger := &myLogger{logur.NewNoopLogger()}

    myFunc(logger)
}

func myFunc(logger MyLogger) {
	logger.WithFields(map[string]interface{}{"key": "value"}).Debug("myFunc ran", nil)
}

Wrap helper functions with custom ones

In many cases it is unavoidable to maintain a simple integration layer between third-party libraries and your application. Logur is no exception. In the previous section you saw how the main interface works with adapters, but that's not all Logur provides. It comes with a set of other tools (eg. a standard library logger compatible io.Writer) to make logging easier. It might be tempting to just use them in your application, but writing an integration layer is recommended, even around functions.

The following example creates a simple standard library logger for using as an HTTP server error log:

func newStandardErrorLogger() *log.Logger {
	return logur.NewStandardLogger(logur.NewNoopLogger(), logur.ErrorLevel, "", 0)
}

func main() {
	server := &http.Server{
		Handler: nil,
		ErrorLog: newStandardErrorLogger(),
	}
}

FAQ

Why not just X logger?

To be honest: mostly because I don't care. Logging libraries proliferated in the Go ecosystem in the past few years. Each tries to convince you it's the most performant or the easiest to use. But the fact is your application doesn't care which you use. In fact, it's happier if it doesn't know anything about it at all. Logging libraries (just like every third-party library) are external dependencies. If you wire them into your application, it will be tied to the chosen libraries forever. That's why using a custom interface is a highly recommended practice.

Let's consider the following logger interface:

type Logger interface {
	Trace(msg string, fields ...map[string]interface{})
	Debug(msg string, fields ...map[string]interface{})
	Info(msg string, fields ...map[string]interface{})
	Warn(msg string, fields ...map[string]interface{})
	Error(msg string, fields ...map[string]interface{})
}

You can easily create an interface like this and implement an adapter for the logging library of your choice without wiring it into your application which makes the actual library a less important detail.

Why not Go kit logger?

Go-kit deserves it's own entry because for quite some time I was really fond of the its logger interface and it was the closest thing to become an official Go logging solution. I still think it is great, because the interface is very simple, yet it's incredibly powerful. But this simplicity is why I ultimately stopped using it as my primary logger (or I should say: stopped knowing that I actually use it).

Just a short recap of the interface itself:

type Logger interface {
	Log(keyvals ...interface{}) error
}

It's really simple and easy to use in any application. Following Go's guidelines of using interfaces, one can easily copy this interface and just use it to decouple the code from Go kit itself.

The problem with this interface appears when you try to do "advanced stuff" (like structured logging or adding a level to a log event):

import (
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
)

// ...

logger := log.With(logger, "key", "value")
level.Info(logger).Log("msg", "message")

As you can see doing any kind of structured or leveled logging requires to import Go kit packages after all, which takes us back to Why not just X logger?.

In short: Using Go kit directly - no matter how awesome its interface is - suffers from the same problem as using any other logging library.

One could implement all those functions for a custom interface based on Go kit, but it probably isn't worth the hassle. Defining a more verbose, custom interface is a lot easier to work with. That being said, Go kit logger can very well serve as a perfect base for an implementation of that interface.

The proposal linked above contains many examples why the authors ended up with an interface like this. Go check it out, you might just have the same use cases which could make the Go kit interface a better fit than the one in this library.

Why not logger.With(keyvals ...interface{})?

There is an increasing tendency of logging libraries implementing the following interface:

type Logger interface {
	// ...
	With(keyvals ...interface{})
}

The arguments behind this interface are being simple and convenient, not as verbose as a map of fields. There are also usual arguments against the alternative solutions, (eg. a map[string]interface{} endorsed by this library) like not being able to order fields, being forced to import a concrete type, like logur.Fields or (quite often) performance questions.

Ultimately, this is not completely independent from personal taste, so one will always prefer one or the other. (You can read more in the above linked Go kit proposal).

Let's take a look at these arguments one by one:

1. Simplicity and verbosity

This is something that's hard to argue with, but also a little bit subjective. Here is a comparison of a single line context logging:

logger = log.With(logger, "key", "value", "key2", "value")
logger = logur.WithFields(logger, map[string]interface{}{"key": "value", "key2": "value"})

Obviously the second one is more verbose, takes a bit more efforts to write, but it is rather a question of habits.

Let's take a look at a multiline example as well:

logger = log.With(logger,
	"key", "value",
	"key2", "value",
)

logger = logur.WithFields(logger, map[string]interface{}{
	"key": "value",
	"key2": "value",
})

The difference is less visible in this case and harder to argue that one is better than the other.

Also, defining a custom type is relatively easy which makes the difference even smaller:

logger = log.With(logger,
	"key", "value",
	"key2", "value",
)

type LogFields map[string]interface{}

logger = logur.WithFields(logger, LogFields{
	"key": "value",
	"key2": "value",
})

2. Ordering fields

This is one of the less known arguments against maps in the context of logging, you can read about it in the Go kit proposal.

Since maps are unordered in Go, fields added to a log line might not always look like the same on the output. Variadic (slice) arguments do not suffer from this issue. However, most implementations convert slices internally to maps, so if ordering matters, most logging libraries won't work anyway.

Also, this library is not a one size fits all proposal and doesn't try to solve 100% of the problems (unlike the official logger proposal), but rather aim for the most common use cases which doesn't include ordering of fields.

3. Performance

Comparing the performance of different solutions is not easy and depends heavily on the interface.

For example, Uber's Zap comes with a rich interface (thus requires you to couple your code to Zap), but also promises zero-allocation in certain scenarios, making it an extremely fast logging library. If being very fast is a requirement for you, even at the expense tight coupling, then Zap is a great choice.

More generic interfaces often use ... well interface{} for structured context, but interface allocations are much cheaper now.

The performance debate these days is often between two approaches:

  • variadic slices (...interface{})
  • maps (map[string]interface{})

Specifically, how the provided context can be merged with an existing, internal context of the logger. Admittedly, appending to a slice is much cheaper than merging a map, so if performance is crucial, using an interface with variadic slices will always be slightly faster. But that difference in performance is negligible in most of the cases, so you won't even notice it, unless you start logging with hundreds of structured context fields (which will have other problems anyway).

There is a problem with Logur adapters though: since the interface uses maps for structured context, libraries like Zap that use variadic slices does not perform too well because of the map -> slice conversion. In most of the cases this should still be acceptable, but you should be aware of this fact when choosing an adapter.

Partly because of this, I plan to add a KVLogger interface that uses variadic slices for structured context. Obviously, map based libraries will suffer from the same performance penalty, but then the choice of the interface will be up to the developer.

Comparing the slice and the map solution, there are also some arguments against using a variadic slice:

1. Odd number of arguments

The variadic slice interface implementation has to deal with the case when an odd number of arguments are passed to the function. While the Go kit proposal argues that this is extremely hard mistake to make, the risk is still there that the logs will lack some information.

2. Converting the slice to key value pairs

In order to display the context as key-value pairs the logging implementations has to convert the key parameters to string in most of the cases (while the value parameter can be handled by the marshaling protocol). This adds an extra step to outputting the logs (an extra loop going through all the parameters). While there is no scientific evidence proving one to be slower than the other (yet), it seems to be an unnecessary complication at first.

Why no *f (format) functions?

A previous version of this interface contained a set of functions that allowed messages to be formatted with arguments:

type Logger interface {
	// ...
	Tracef(format string, args ...interface{})
	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Errorf(format string, args ...interface{})
}

The reason why they were originally included in the interface is that most logging libraries implement these methods, but experience showed that they are not used frequently. Also, nowadays structured logging is a better practice than formatting log messages with structured data, thus these methods were removed from the core interface.

Why no *ln functions?

Another common group of logging functions originally included in the interface is *ln function group:

type Logger interface {
	// ...
	Traceln(args ...interface{})
	Debugln(args ...interface{})
	Infoln(args ...interface{})
	Warnln(args ...interface{})
	Errorln(args ...interface{})
}

Usually separate log events are represented on separate lines anyway, so the added value is not newlines in this case, but the different semantics between fmt.Print and fmt.Println. See this example illustrating the difference.

Common logging libraries include these functions, but experience showed they are not used frequently, so they got removed.

Inspiration

This package is heavily inspired by a set of logging libraries:

Development

Contributions are welcome! :)

  1. Clone the repository
  2. Make changes on a new branch
  3. Run the test suite:
    ./pleasew build
    ./pleasew test
    ./pleasew gotest
    ./pleasew lint
  4. Commit, push and open a PR

License

The MIT License (MIT). Please see License File for more information.

Issues
  • Where are the adapters?

    Where are the adapters?

    The README mentioned that you have pre-built adapters for other logging frameworks, but I don't see where they are. I found this from here:

    https://github.com/sagikazarmark/modern-go-application

    and in particular I see this import:

    logrusadapter "logur.dev/adapter/logrus"
    

    https://github.com/sagikazarmark/modern-go-application/blob/master/internal/platform/log/logger.go

    But I don't see an adapter subfolder here. Help!

    opened by ghostsquad 5
  • Excessive amount of dependencies

    Excessive amount of dependencies

    The amount of dependencies is a returning issue with go modules: go mod download downloads all transitive (test or non-test) dependencies for a module, which is usually much more than what ends up in a binary. Especially in case of modules like this one, where the module contains a set of adapters. (In this case: adapters for logging libraries which essentially means that go mod download will download all of the logging libraries.

    The main problem here is not the number of dependencies, but the overall download size. For example: this library generates about 500MB of downloaded cache, most of which will not even be used.

    Possible solutions

    Currently this use case (adapters) is not really supported go modules and unfortunately none of the possible solutions are ideal:

    Create submodules

    Create "submodules" within the main module (repository): unfortunately this didn't really work for me (TODO: write down why, look up on slack)

    Here is one implementation that probably wouldn't work with logur: https://github.com/goph/idgen/pull/1

    Logur adapters has a dependency on the parent module in which case versioning can cause serious trouble.

    Separate packages

    Move adapters to separate repositories (this can make development much harder though, given that all adapters implement a single interface)

    Go modules plans for 2019

    According to this blog post, this should become a non-issue in 2019 with the introduction of mirrors.

    For the case of adapter libraries, if each library is a separate package you should be fine once module proxies are available: users will need to download go.mod files for transitive dependencies, but not full sources.

    Other projects affected

    • golang-migrate/migrate#174
    opened by sagikazarmark 2
  • ImportError: cannot import name 'Logger' from 'loguru'

    ImportError: cannot import name 'Logger' from 'loguru'

    from logure import Logger
    
    def f(log:Logure):
        pass
    
    opened by heckad 2
  • Require logrus >= 1.4.2 for IsLevelEnabled

    Require logrus >= 1.4.2 for IsLevelEnabled

    The logrus adapter uses (*logrus.Logger).IsLevelEnabled, which was first introduced in v1.4.2.

    See https://github.com/sirupsen/logrus/commit/90bf2e7f391a08ecb7d505856924ed3bf1bc771f.

    opened by johanbrandhorst 2
  • Add logr integration

    Add logr integration

    https://github.com/go-logr/logr

    opened by sagikazarmark 1
  • Merge integration and adapter packages for the same logger

    Merge integration and adapter packages for the same logger

    Currently adapters and integrations for the same logger packages are separated. Let's merge them!

    It also aligns with the idea of having separate packages (modules) for integrations/adapters per logging library.

    opened by sagikazarmark 1
  • Add grpclog integration

    Add grpclog integration

    opened by sagikazarmark 1
  • logrusadapter: add NewFromEntry to adapt loggers with fields

    logrusadapter: add NewFromEntry to adapt loggers with fields

    logrusadapter does not care much with the logrus.Logger, it extracts the Entry from it. Let users adapt directly the Entry (which they only have in a typical function of a codebase that uses logrus), to ease the transition between using logrus and logur interfaces within a code base.

    opened by orymate 1
  • Add grpc log integration

    Add grpc log integration

    opened by sagikazarmark 0
  • fieldLogger should implement LevelEnabler

    fieldLogger should implement LevelEnabler

    here is my use case: playground link

    package main
    
    import (
    	"fmt"
    
    	"logur.dev/logur"
    	"play.ground/logging"
    )
    
    type ml struct {
    	logging.Logger
    }
    
    // Printf is like fmt.Printf
    func (l *ml) Printf(format string, v ...interface{}) {
    	l.Logger.Info(fmt.Sprintf(format, v...))
    }
    
    // Verbose should return true when verbose logging output is wanted
    func (l *ml) Verbose() bool {
    	return l.Logger.LevelEnabled(logur.Debug)
    }
    func main() {
    	log := logging.NewLogrusLogger()
    	migrateLog := ml{log}
    	if migrateLog.Verbose() {
    		migrateLog.Printf("hello migrate log")
    	}
    
    	log = log.WithField("foo", "bar")
    	migrateLog = ml{log}
    	if migrateLog.Verbose() {
    		migrateLog.Printf("hello migrate log again")
    	}
    }
    -- go.mod --
    module play.ground
    -- migrate/log.go --
    package migrate
    
    // migrate logger is taken from:
    // https://pkg.go.dev/github.com/golang-migrate/migrate/v4#Logger
    
    // Logger is an interface so you can pass in your own
    // logging implementation.
    type Logger interface {
    	// Printf is like fmt.Printf
    	Printf(format string, v ...interface{})
    
    	// Verbose should return true when verbose logging output is wanted
    	Verbose() bool
    }
    -- logging/logger.go --
    package logging
    
    import (
    	"fmt"
    
    	"github.com/sirupsen/logrus"
    	logrusadapter "logur.dev/adapter/logrus"
    	"logur.dev/logur"
    )
    
    // Logger is the fundamental interface for all log operations.
    type Logger interface {
    	Trace(msg string, fields ...map[string]interface{})
    	Debug(msg string, fields ...map[string]interface{})
    	Info(msg string, fields ...map[string]interface{})
    	Warn(msg string, fields ...map[string]interface{})
    	Error(msg string, fields ...map[string]interface{})
    
    	// WithFields annotates a logger with some context and it as a new instance.
    	WithFields(fields map[string]interface{}) Logger
    	WithField(key string, value interface{}) Logger
    
    	logur.LevelEnabler
    }
    
    type logger struct {
    	logur.Logger
    }
    
    func (l *logger) WithFields(fields map[string]interface{}) Logger {
    	return &logger{logur.WithFields(l, fields)}
    }
    
    func (l *logger) WithField(key string, value interface{}) Logger {
    	return &logger{logur.WithField(l, key, value)}
    }
    
    func (l *logger) LevelEnabled(lvl logur.Level) bool {
    	if ll, ok := l.Logger.(logur.LevelEnabler); ok {
    		return ll.LevelEnabled(lvl)
    	}
    	fmt.Println("shouldn't reach here")
    	return true
    }
    
    func NewLogrusLogger() Logger {
    	rus := logrus.New()
    	lvlstr := "debug" //os.Getenv("LOG_LEVEL")
    	lvl, err := logrus.ParseLevel(lvlstr)
    	if err != nil {
    		rus.WithField("LOG_LEVEL", lvlstr).WithError(err).Warn("wrong level, using warn")
    		lvl = logrus.WarnLevel
    	}
    	rus.SetLevel(lvl)
    
    	l := logrusadapter.New(rus)
    	return &logger{Logger: l}
    }
    

    When I use logur.WithField, it should really give me the LevelEnabler as well, otherwise there is no easy way for me to implement the LevelEnabled method

    opened by jackieli-tes 0
  • apex/log adapter

    apex/log adapter

    I have created a logur adapter for the apex/log logging library using the adapter template:

    • https://github.com/davidalpert/adapter-apex

    Would this be of interest for folding into the logur family of adapters?

    opened by davidalpert 7
  • Improve logger interface compliance tests

    Improve logger interface compliance tests

    • Make tests better
    • Improve test coverage/compliance test coverage for loggers (eg. WithFields)
    opened by sagikazarmark 0
  • Add equals function to LogEvent

    Add equals function to LogEvent

    LogEvent.AssertEquals might be a better place for what LogEventsEqual currently is

    opened by sagikazarmark 0
  • Global log package

    Global log package

    Set a logger globally

    opened by sagikazarmark 3
  • Add prefix to context keys

    Add prefix to context keys

    Sometimes it makes sense to prepend a prefix to context keys to create unique keys.

    opened by sagikazarmark 0
  • Consider implementing a variadic interface instead of fields

    Consider implementing a variadic interface instead of fields

    Currently log functions accept a map[string]interface{} as the second argument which is far from ideal:

    1. It's cumbersome to write
    2. ~It's a mandatory argument~ (#36 introduces optional maps)
    3. Merging context is slow this way (compared to slices)

    Consider switching to a variadic slice of key-value pairs.

    Some pro-contra arguments are listed here: https://github.com/goph/logur#why-not-loggerwithkeyvals-interface

    A possible midway solution:

    Info(msg string, keyvals ...interface{ Key() string Value() interface{}})
    

    Downsides: cumbersome to extend and implement.

    opened by sagikazarmark 4
  • Add format logger

    Add format logger

    Add a logger with format and ln functions

    opened by sagikazarmark 0
  • Improve logger performance

    Improve logger performance

    Try playing with fields and disabled levels in Zap and Hclog

    Original benchmark results
    goos: darwin
    goarch: amd64
    pkg: github.com/goph/logur/benchmarks
    BenchmarkDisabledWithoutFields/zap-12     	100000000	        12.9 ns/op	      32 B/op	       2 allocs/op
    BenchmarkDisabledWithoutFields/hclog-12   	50000000	        33.1 ns/op	      96 B/op	       3 allocs/op
    BenchmarkDisabledWithoutFields/zerolog-12 	20000000	        61.9 ns/op	      96 B/op	       3 allocs/op
    BenchmarkDisabledWithoutFields/kitlog-12  	20000000	       113 ns/op	     288 B/op	       8 allocs/op
    BenchmarkDisabledWithoutFields/logrus-12  	100000000	        13.9 ns/op	      32 B/op	       2 allocs/op
    BenchmarkDisabledAccumulatedContext/logrus-12         	100000000	        13.7 ns/op	      32 B/op	       2 allocs/op
    BenchmarkDisabledAccumulatedContext/zap-12            	100000000	        16.0 ns/op	      32 B/op	       2 allocs/op
    BenchmarkDisabledAccumulatedContext/hclog-12          	30000000	        39.2 ns/op	      96 B/op	       3 allocs/op
    BenchmarkDisabledAccumulatedContext/zerolog-12        	20000000	        75.1 ns/op	      96 B/op	       3 allocs/op
    BenchmarkDisabledAccumulatedContext/kitlog-12         	 5000000	       261 ns/op	    1249 B/op	       8 allocs/op
    BenchmarkDisabledAddingFields/logrus-12               	 3000000	       487 ns/op	    1580 B/op	      15 allocs/op
    BenchmarkDisabledAddingFields/zap-12                  	  300000	      4693 ns/op	    8742 B/op	      73 allocs/op
    BenchmarkDisabledAddingFields/hclog-12                	 1000000	      1184 ns/op	    3507 B/op	      40 allocs/op
    BenchmarkDisabledAddingFields/zerolog-12              	  300000	      4690 ns/op	    7707 B/op	      58 allocs/op
    BenchmarkDisabledAddingFields/kitlog-12               	 2000000	       851 ns/op	    2946 B/op	      32 allocs/op
    BenchmarkWithoutFields/zap-12                         	10000000	       207 ns/op	      96 B/op	       3 allocs/op
    BenchmarkWithoutFields/hclog-12                       	 2000000	       686 ns/op	     128 B/op	       4 allocs/op
    BenchmarkWithoutFields/zerolog-12                     	20000000	        71.1 ns/op	      96 B/op	       3 allocs/op
    BenchmarkWithoutFields/kitlog-12                      	 3000000	       538 ns/op	    1058 B/op	      21 allocs/op
    BenchmarkWithoutFields/logrus-12                      	  500000	      2936 ns/op	     513 B/op	      14 allocs/op
    BenchmarkAccumulatedContext/logrus-12                 	   50000	     27717 ns/op	    4663 B/op	      92 allocs/op
    BenchmarkAccumulatedContext/zap-12                    	10000000	       214 ns/op	      96 B/op	       3 allocs/op
    BenchmarkAccumulatedContext/hclog-12                  	   50000	     26948 ns/op	    8089 B/op	     123 allocs/op
    BenchmarkAccumulatedContext/zerolog-12                	20000000	        78.9 ns/op	      96 B/op	       3 allocs/op
    BenchmarkAccumulatedContext/kitlog-12                 	  200000	      6090 ns/op	    6645 B/op	     110 allocs/op
    BenchmarkAddingFields/logrus-12                       	   50000	     30147 ns/op	    6245 B/op	     106 allocs/op
    BenchmarkAddingFields/zap-12                          	  300000	      5333 ns/op	    9058 B/op	      74 allocs/op
    BenchmarkAddingFields/hclog-12                        	   50000	     31727 ns/op	   11503 B/op	     160 allocs/op
    BenchmarkAddingFields/zerolog-12                      	  300000	      5021 ns/op	    7707 B/op	      58 allocs/op
    BenchmarkAddingFields/kitlog-12                       	  200000	      6988 ns/op	    8374 B/op	     134 allocs/op
    PASS
    ok  	github.com/goph/logur/benchmarks	50.774s
    
    Benchmark results after single message argument
    goos: darwin
    goarch: amd64
    pkg: github.com/goph/logur/benchmarks
    BenchmarkDisabledWithoutFields/logrus-12  	200000000	         6.94 ns/op	      16 B/op	       1 allocs/op
    BenchmarkDisabledWithoutFields/zap-12     	200000000	         8.82 ns/op	      16 B/op	       1 allocs/op
    BenchmarkDisabledWithoutFields/hclog-12   	2000000000	         1.69 ns/op	       0 B/op	       0 allocs/op
    BenchmarkDisabledWithoutFields/zerolog-12 	50000000	        26.1 ns/op	       0 B/op	       0 allocs/op
    BenchmarkDisabledWithoutFields/kitlog-12  	20000000	        76.5 ns/op	     192 B/op	       5 allocs/op
    BenchmarkDisabledAccumulatedContext/logrus-12         	200000000	         8.26 ns/op	      16 B/op	       1 allocs/op
    BenchmarkDisabledAccumulatedContext/zap-12            	100000000	        10.3 ns/op	      16 B/op	       1 allocs/op
    BenchmarkDisabledAccumulatedContext/hclog-12          	2000000000	         1.77 ns/op	       0 B/op	       0 allocs/op
    BenchmarkDisabledAccumulatedContext/zerolog-12        	50000000	        32.6 ns/op	       0 B/op	       0 allocs/op
    BenchmarkDisabledAccumulatedContext/kitlog-12         	 5000000	       236 ns/op	    1152 B/op	       5 allocs/op
    BenchmarkDisabledAddingFields/logrus-12               	 3000000	       512 ns/op	    1564 B/op	      14 allocs/op
    BenchmarkDisabledAddingFields/zap-12                  	  300000	      5107 ns/op	    8728 B/op	      72 allocs/op
    BenchmarkDisabledAddingFields/hclog-12                	 1000000	      1215 ns/op	    3390 B/op	      36 allocs/op
    BenchmarkDisabledAddingFields/zerolog-12              	  300000	      4896 ns/op	    7586 B/op	      54 allocs/op
    BenchmarkDisabledAddingFields/kitlog-12               	 2000000	       854 ns/op	    2830 B/op	      28 allocs/op
    BenchmarkWithoutFields/logrus-12                      	  500000	      2981 ns/op	     497 B/op	      13 allocs/op
    BenchmarkWithoutFields/zap-12                         	10000000	       200 ns/op	      80 B/op	       2 allocs/op
    BenchmarkWithoutFields/hclog-12                       	 3000000	       507 ns/op	      32 B/op	       1 allocs/op
    BenchmarkWithoutFields/zerolog-12                     	100000000	        26.2 ns/op	       0 B/op	       0 allocs/op
    BenchmarkWithoutFields/kitlog-12                      	 3000000	       501 ns/op	     961 B/op	      18 allocs/op
    BenchmarkAccumulatedContext/kitlog-12                 	  200000	      6371 ns/op	    6534 B/op	     107 allocs/op
    BenchmarkAccumulatedContext/logrus-12                 	   50000	     28836 ns/op	    4646 B/op	      91 allocs/op
    BenchmarkAccumulatedContext/zap-12                    	10000000	       206 ns/op	      80 B/op	       2 allocs/op
    BenchmarkAccumulatedContext/hclog-12                  	   50000	     27934 ns/op	    7993 B/op	     120 allocs/op
    BenchmarkAccumulatedContext/zerolog-12                	50000000	        31.1 ns/op	       0 B/op	       0 allocs/op
    BenchmarkAddingFields/zerolog-12                      	  300000	      4693 ns/op	    7587 B/op	      54 allocs/op
    BenchmarkAddingFields/kitlog-12                       	  200000	      7123 ns/op	    8243 B/op	     130 allocs/op
    BenchmarkAddingFields/logrus-12                       	   50000	     31767 ns/op	    6230 B/op	     105 allocs/op
    BenchmarkAddingFields/zap-12                          	  300000	      5263 ns/op	    9048 B/op	      73 allocs/op
    BenchmarkAddingFields/hclog-12                        	   50000	     32059 ns/op	   11391 B/op	     156 allocs/op
    PASS
    ok  	github.com/goph/logur/benchmarks	58.244s
    
    opened by sagikazarmark 2
Owner
Logur
An opinionated collection of logging best practices for Go applications and libraries
Logur
Parse awesome-go README file and generate a new README file with repo info.

Awesome Go Extra All data are from awesome-go and GitHub API. Audio and Music Libraries for manipulating audio. Name Description Star Open Issues Crea

Wendell Sun 13 Jul 24, 2021
Zero Allocation JSON Logger

Zero Allocation JSON Logger The zerolog package provides a fast and simple logger dedicated to JSON output. Zerolog's API is designed to provide both

Olivier Poitrey 5k Jul 23, 2021
A pure Go contextual logging library with "batteries included"

Cue Overview Cue implements contextual logging with "batteries included". It has thorough test coverage and supports logging to stdout/stderr, file, s

Bob Ziuchkovski 26 Sep 16, 2019
Golang logging library

Golang logging library Package logging implements a logging infrastructure for Go. Its output format is customizable and supports different logging ba

Örjan Fors 1.7k Jul 17, 2021
Structured, pluggable logging for Go.

Logrus Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. Logrus is in maintenance-mode. We wi

Simon Eskildsen 18.3k Jul 24, 2021
Logrus is a structured, pluggable logging for Go.

Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger.

Simon Eskildsen 274 May 25, 2021
A simple logging interface for Go

A more minimal logging API for Go Before you consider this package, please read this blog post by the inimitable Dave Cheney. I really appreciate what

null 430 Jul 23, 2021
Dead simple, super fast, zero allocation and modular logger for Golang

Onelog Onelog is a dead simple but very efficient JSON logger. It is one of the fastest JSON logger out there. Also, it is one of the logger with the

Francois Parquet 396 Jul 16, 2021
Logging, distilled

What is distillog? distillog aims to offer a minimalistic logging interface that also supports log levels. It takes the stdlib API and only slightly e

Akshay Moghe 26 Jun 21, 2021
Structured Logging Made Easy

Structured Logging Made Easy Features Dependency Free Simple and Clean Interface Consistent Writer IOWriter, io.Writer wrapper FileWriter, rotating &

phuslu 386 Jul 22, 2021
A minimal and extensible structured logger

⚠️ PRE-RELEASE ⚠️ DO NOT IMPORT THIS MODULE YOUR PROJECT WILL BREAK package log package log provides a minimal interface for structured logging in ser

Go kit 54 Jul 21, 2021
Parametrized JSON logging library in Golang which lets you obfuscate sensitive data and marshal any kind of content.

Noodlog Summary Noodlog is a Golang JSON parametrized and highly configurable logging library. It allows you to: print go structs as JSON messages; pr

Gyoza Tech 23 Apr 28, 2021
Fully asynchronous, structured, pluggable logging for Go.

logr Logr is a fully asynchronous, contextual logger for Go. It is very much inspired by Logrus but addresses two issues: Logr is fully asynchronous,

Mattermost 10 Jun 16, 2021
Simple, customizable, leveled and efficient logging in Go

log Simple, customizable, leveled and efficient logging in Go Installation go get -u github.com/ermanimer/log Features log is a simple logging package

Erman İmer 22 Jul 13, 2021