Logur is an opinionated collection of logging best practices

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.

Owner
Logur
An opinionated collection of logging best practices for Go applications and libraries
Logur
Comments
  • 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?

  • 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!

  • 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.

  • 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
  • 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.

  • 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.

  • Add a basic KV Logger implementation

    Add a basic KV Logger implementation

    It's not a complete implementation, as a lot of extensions (context, withFields, etc) are missing, but provides an adapter for existing, Logger implementations.

    Related #25

  • 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

  • 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.

  • 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
    
Gomol is a library for structured, multiple-output logging for Go with extensible logging outputs

gomol Gomol (Go Multi-Output Logger) is an MIT-licensed structured logging library for Go. Gomol grew from a desire to have a structured logging libra

Sep 26, 2022
A simple logging module for go, with a rotating file feature and console logging.

A simple logging module for go, with a rotating file feature and console logging. Installation go get github.com/jbrodriguez/mlog Usage Sample usage W

Dec 14, 2022
FactorLog is a logging infrastructure for Go that provides numerous logging functions for whatever your style may be
FactorLog is a logging infrastructure for Go that provides numerous logging functions for whatever your style may be

FactorLog FactorLog is a fast logging infrastructure for Go that provides numerous logging functions for whatever your style may be. It could easily b

Aug 3, 2022
Package logging implements a logging infrastructure for Go
Package logging implements a logging infrastructure for Go

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

Nov 10, 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

Dec 14, 2022
Simple and blazing fast lockfree logging library for golang
Simple and blazing fast lockfree logging library for golang

glg is simple golang logging library Requirement Go 1.11 Installation go get github.com/kpango/glg Example package main import ( "net/http" "time"

Nov 28, 2022
Logging library for Golang

GLO Logging library for Golang Inspired by Monolog for PHP, severity levels are identical Install go get github.com/lajosbencz/glo Severity levels Deb

Sep 26, 2022
Simple and configurable Logging in Go, with level, formatters and writers

go-log Logging package similar to log4j for the Golang. Support dynamic log level Support customized formatter TextFormatter JSONFormatter Support mul

Sep 26, 2022
The Simplest and worst logging library ever written

gologger A Simple Easy to use go logger library. Displays Colored log into console in any unix or windows platform. You can even store your logs in fi

Sep 26, 2022
Go implementation of systemd Journal's native API for logging

journald Package journald offers Go implementation of systemd Journal's native API for logging. Key features are: based on a connection-less socket wo

Dec 23, 2022
Structured logging package for Go.
Structured logging package for Go.

Package log implements a simple structured logging API inspired by Logrus, designed with centralization in mind. Read more on Medium. Handlers apexlog

Dec 24, 2022
Simple, configurable and scalable Structured Logging for Go.

log Log is a simple, highly configurable, Structured Logging library Why another logging library? There's allot of great stuff out there, but also tho

Sep 26, 2022
LogVoyage - logging SaaS written in GoLang
LogVoyage - logging SaaS written in GoLang

No longer maintained, sorry. Completely rewritten v2 is going to be released soon. Please follow http://github.com/logvoyage LogVoyage - fast and simp

Sep 26, 2022
Structured, composable logging for Go
Structured, composable logging for Go

log15 Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is mo

Dec 18, 2022
Minimalistic logging library for Go.
Minimalistic logging library for Go.

logger Minimalistic logging library for Go. Blog Post Features: Advanced output filters (package and/or level) Attributes Timers for measuring perform

Nov 16, 2022
Structured, pluggable logging for Go.
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

Jan 9, 2023
Utilities for slightly better logging in Go (Golang).

logutils logutils is a Go package that augments the standard library "log" package to make logging a bit more modern, without fragmenting the Go ecosy

Dec 16, 2022
A Go (golang) package providing high-performance asynchronous logging, message filtering by severity and category, and multiple message targets.

ozzo-log Other languages 简体中文 Русский Description ozzo-log is a Go package providing enhanced logging support for Go programs. It has the following fe

Dec 17, 2022
Structured Logging Made Easy
Structured Logging Made Easy

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

Jan 3, 2023