I needed to write my own lightweight GO multi-output logger for a work project and I've come up with https://github.com/asticode/go-logger.

Basically the projects works like this:

// Create the logger
l, e := logger.NewLogger("myproject", logger.LevelDebug, "%message%")

// Add a stream handler
h, e := logger.NewHandlerStream("")
l.AddHandler("stream", h)

// Add a syslog handler
h, e = logger.NewHandlerSyslog("[%level%]%message%", "syslog_id")
l.AddHandler("syslog", h)

// Add a file handler
h, e = logger.NewHandlerFile("", "/path/to/file")
l.AddHandler("file", h)

// Log a warning message
l.Warning("This is a warning message")

And this will output the message to the specified handlers :)

I'm not here to advertise at all and I'm rather looking for Gopher's opinions (good or bad) on my code and my implementation since I'd like to know whether I've done things right or whether I've gone completely off tracks.

My points of emphasis would be:

  • Have I made good use of interfaces?
  • Have I made good use of const and global vars?
  • Have I made good use of GO project structure?
  • Have I made good use of GO unit testing package?
  • ...

For instance here's how I expose the main logger (https://github.com/asticode/go-logger/blob/master/logger/logger.go):

type Logger interface {
    ID() int
    SetID(id int)
    Prefix() string
    SetPrefix(p string)
    Level() Level
    SetLevel(l Level)
    Format() string
    SetFormat(f string)
    Handlers() map[string]Handler
    AddHandler(k string, h Handler)
    GetHandler(k string) (Handler, error)
    DelHandler(k string)
    Emergency(message string) error
    Critical(message string) error
    Error(message string) error
    Warning(message string) error
    Notice(message string) error
    Info(message string) error
    Debug(message string) error

type logger struct {
    id       int
    prefix   string
    level    Level
    format   string
    handlers map[string]Handler

And I use a global func to create a new logger such as:

func NewLogger(prefix string, level Level, format string) (Logger, error) {
    // Check level
    if _, ok := levelToInt[level]; !ok {
        return nil, ErrUndefinedLevel

    // Check Format
    if len(format) == 0 {
        format = defaultFormat

    // Increment count

    // Return
    return &logger{
        id:       count,
        prefix:   prefix,
        level:    level,
        format:   format,
        handlers: make(map[string]Handler),
    }, nil

Is this the proper way to do it ?

  • \$\begingroup\$ Seems relevant: dave.cheney.net/2015/11/05/lets-talk-about-logging \$\endgroup\$ – zmb Nov 11 '15 at 15:25
  • \$\begingroup\$ As of best practices: I am of the opinion that all loggers should implement the default "log" interface, mapping the standard funcs to something useful. For example, Logger.PrintX could log a message with level "info". So that one can use your logger as a drop in replacement for the standard libraries logger, which makes adoption much easier. Changing say 30 lines of code is something entirely different than to change a couple of hundred lines of code, as levels would need to get adjusted. Extra point for the option of mapping the log levels via configuration. \$\endgroup\$ – Markus W Mahlberg Nov 14 '15 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.