106

Is there any good wrapper available for level based logging in golang? If not, how should I go about implementing one myself?

What I want is pretty simple. I want a few functions e.g.

log.Error()
log.Info()

etc that display their outputs to stdout as well as save these in a log file (based on the level given to the program as commandline argument). How do I implement this wrapper?

2
  • 2
    github.com/jcelliott/lumber Commented Jun 3, 2013 at 11:54
  • 1
    There is github.com/golang/glog which is probably more than you need but worth looking at. The implementation has a very interesting use of boolean types with methods. Commented Oct 11, 2014 at 17:35

12 Answers 12

78

Some more suggestions, now that the existing answers are quite old:

Sign up to request clarification or add additional context in comments.

4 Comments

nanolog does not meet the "display their outputs to stdout" requirement. Binlog has zero GitHub stars.
A little update from Dec. 2021, zerolog is one hell of an impressive framework github.com/rs/zerolog . A nice alternative to zap.
In 2023 today, Go 1.21 comes with the standard log/slog package. No external library is needed anymore for average users. See my answer for the detail.
69

In 2023 today, Go 1.21.0 finally comes with a standard level logger: log/slog package.

No external library is needed anymore for average users.

1. Basic Usage

package main

import "log/slog"

func main() {
    slog.Info("hello")
    slog.Warn("hello")
    slog.Error("hello")
}
2023/08/09 20:05:49 INFO hello
2023/08/09 20:05:49 WARN hello
2023/08/09 20:05:49 ERROR hello

1.1 Enable Debug Log Level

playground

Debug log level is disabled by default. To enable it, call SetLogLoggerLevel() (available in Go 1.22 or later):

slog.SetLogLoggerLevel(slog.LevelDebug)

1.2 Disable Logging

playground

If you want (temporarily) to disable logging, the cleanest solution is to define your own logger (see 4. Create Your Own Logger for the details).

However, this hacky one-liner works:

slog.SetLogLoggerLevel(math.MaxInt)

2. Add Context

The name slog is short for structured logging, meaning each log entry can have a structure.

The log functions can optionally receive any number of key-value pairs called attributes:

package main

import "log/slog"

func main() {
    slog.Info("hello", "username", "Mike", "age", 18)

    //same as above but more type-safe
    slog.Info("hello", slog.String("username", "Mike"), slog.Int("age", 18))
}
2023/08/09 20:07:51 INFO hello username=Mike age=18
2023/08/09 20:07:51 INFO hello username=Mike age=18 (same as above)

(You can even nest key-value pairs via Group() or GroupAttrs().)

3. Customize Format

log package can be used to customize the format of log/slog logger.

This is useful when you want to print finer-grained timestamps, file names and line numbers:

package main

import (
    "log"
    "log/slog"
)

func main() {
    slog.Info("hello")
    log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
    slog.Info("hello")
}
2023/08/09 20:15:36 INFO hello
2023/08/09 20:15:36.601583 /home/user/test/a/main.go:9: INFO hello

4. Create Your Own Logger

Basic usage is covered by the top-level functions (e.g. slog.Info()) but you can create your own logger for detailed customization.

A created logger can be set as the default logger via slog.SetDefault(). After that, the top-level functions (e.g. slog.Info()) use your logger.

4.1 Standard Loggers

Constructors are provided in log/slog package for some built-in loggers.

package main

import (
    "log/slog"
    "os"
)

func main() {
    //text logger
    {
        //The second argument enables `Debug` log level.
        handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
        slog.SetDefault(slog.New(handler))

        slog.Debug("hello", "username", "Mike", "age", 18)
    }

    //JSON logger
    {
        handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
        slog.SetDefault(slog.New(handler))

        slog.Debug("hello", "username", "Mike", "age", 18)
    }
}
time=2023-08-09T20:31:05.798+09:00 level=DEBUG msg=hello username=Mike age=18
{"time":"2023-08-09T20:31:05.798984192+09:00","level":"DEBUG","msg":"hello","username":"Mike","age":18}

4.2 User-Defined Loggers

By implementing Handler interface, you can create a fully-customized logger.

package main

import (
    "context"
    "fmt"
    "log/slog"
    "os"
    "time"
)

type MyHandler struct{}

func (h MyHandler) Enabled(context context.Context, level slog.Level) bool {
    switch level {
    case slog.LevelDebug:
        return false
    case slog.LevelInfo:
        fallthrough
    case slog.LevelWarn:
        fallthrough
    case slog.LevelError:
        return true
    default:
        panic("unreachable")
    }
}

func (h MyHandler) Handle(context context.Context, record slog.Record) error {
    message := record.Message

    //appends each attribute to the message
    //An attribute is of the form `<key>=<value>` and specified as in `slog.Error(<message>, <key>, <value>, ...)`.
    record.Attrs(func(attr slog.Attr) bool {
        message += fmt.Sprintf(" %v", attr)
        return true
    })

    timestamp := record.Time.Format(time.RFC3339)

    switch record.Level {
    case slog.LevelDebug:
        fallthrough
    case slog.LevelInfo:
        fallthrough
    case slog.LevelWarn:
        fmt.Fprintf(os.Stderr, "[%v] %v %v\n", record.Level, timestamp, message)
    case slog.LevelError:
        fmt.Fprintf(os.Stderr, "!!!ERROR!!! %v %v\n", timestamp, message)
    default:
        panic("unreachable")
    }

    return nil
}

// for advanced users
func (h MyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    panic("unimplemented")
}

// for advanced users
func (h MyHandler) WithGroup(name string) slog.Handler {
    panic("unimplemented")
}

func main() {
    logger := slog.New(MyHandler{})
    slog.SetDefault(logger)

    slog.Debug("hello") //=> does nothing (as `Enabled()` returns `false`)
    slog.Info("hello")  //=> [INFO] 2023-11-15T22:38:54+09:00 hello
    slog.Warn("hello")  //=> [WARN] 2023-11-15T22:38:54+09:00 hello
    slog.Error("hello") //=> !!!ERROR!!! 2023-11-15T22:38:54+09:00 hello

    //with attributes
    slog.Error("hello", "id", 5, "foo", "bar") //=> !!!ERROR!!! 2023-11-15T22:38:54+09:00 hello id=5 foo=bar

}

4.3 User-Defined Log Levels

As the type of the log levels is defined as type Level int, you can even define your own log levels: The Go Playground

3 Comments

Could you add an example of how to print complex types like maps while in debug level?
@tuxErrante How to enable the debug level is explained in the answer: see 1.1 Enable Debug Log Level or 4.2 User-Defined Loggers. In Go, pretty-print is another harder problem. See How to pretty print variables - Stack Overflow and use any method you want with 4.2 User-Defined Loggers in my answer. Personally I like pp package: pp.Println(yourMap).
Thanks indeed my issue was the positioning of the args in slog call and the printing of a complex map from there, I'm working on something similar to what you've linked me, but trying avoiding extra pkgs also because I'm not working with JSON here: goplay.tools/snippet/UcRUpWhPS8m
6

https://github.com/hashicorp/logutils I found this to be very easy to use and you don't even need to change the method calls to log.Printf of the std library.

1 Comment

"The simplest thing that could possibly work" +1
6
  • Uber-go/Zap: Fast, structured, leveled logging in Go
  • Logrus: Structured, pluggable logging for Go. (JSON and text formatting)

Both libraries have level hooks also, which is a very interesting feature. Hooks can be registered for particular log levels. So for example any error(logged using log.Error()) occurs you can report to some monitoring tool etc.

1 Comment

one drawback of logrus is that it does n't have log rotation, need to use some external programs for that...
4

I think seelog fits your requirements, and it seems to be pretty popular as it pops up often in log discussions. I never used it seriously, so I can't comment beyond that.

1 Comment

It looks as though seelog must acquire/release a global lock for each statement.
3

Take a look at http://cgl.tideland.biz and there at the package "applog". It's working that way.

PS: The whole CGL is currently reworked and will soon be released with new features, but under a different name. ;)

3 Comments

unfortunately the links are dead
All the links are dead or don't have anything to do with logging.
Sorry for all the movements over time. Needed some time to find the current one as the IMHO best one. The packages are at github.com/tideland/golib and the documentation at godoc.org/?q=tideland%2Fgolib.
2

stdlog fits exactly your requirements:

log := stdlog.GetFromFlags()
log.Info("Connecting to the server...")
log.Errorf("Connection failed: %q", err)

Comments

1

I am working with rlog at the moment and like their approach. The code looks good, simplistic and sufficiently documented.

What convinced me:

  • no external dependencies
  • i can use rlog.Info() anywhere without passing around references
  • good usage of environment variables
  • arbitrary number of trace levels e.g. rlog.Trace(4, "foo")

Comments

1

I have added logging level support to the built-in Go log package. You can find my code here:

https://github.com/gologme/log

In addition to adding support for Info, Warn, and Debug, users can also define their own arbitrary logging levels. Logging levels are enabled and disabled individually. This means you can turn on Debug logs without also getting everything else.

Comments

0

You can use the module midlog to implements any other log library, https://github.com/lingdor/midlog

1 Comment

A link to a solution is welcome, but please ensure your answer is useful without it: add context around the link so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. Answers that are little more than a link may be deleted.
0

One of the logging module that you can consider is klog . It support 'V' logging which gives the flexibility to log at certain level

klog is a fork of glog and overcomes following drawbacks

glog presents a lot "gotchas" and introduces challenges in containerized environments, all of which aren't well documented. glog doesn't provide an easy way to test logs, which detracts from the stability of software using it glog is C++ based and klog is a pure golang implementation

Sample Implementation

package main

import (
    "flag"

    "k8s.io/klog"


)

type myError struct {
    str string
}

func (e myError) Error() string {
    return e.str
}

func main() {
    klog.InitFlags(nil)
    flag.Set("v", "1")
    flag.Parse()

    klog.Info("hello", "val1", 1, "val2", map[string]int{"k": 1})
    klog.V(3).Info("nice to meet you")
    klog.Error(nil, "uh oh", "trouble", true, "reasons", []float64{0.1, 0.11, 3.14})
    klog.Error(myError{"an error occurred"}, "goodbye", "code", -1)
    klog.Flush()
}

Comments

0

From Official GO 1.23.4 package: https://pkg.go.dev/log/slog

fmt.Println("\n -- LOG -- ")
slog.SetLogLoggerLevel(slog.LevelDebug) // To print Debug. which is -4
slog.Debug("LOG Debug", "count", 3)
slog.Info("LOG Info", "count", 3)
slog.Warn("LOG Warn", "count", 3)
slog.Error("LOG Error", "count", 3)

Prints out in log:

 -- LOG -- 
2025/01/03 22:47:58 DEBUG LOG Debug count=3
2025/01/03 22:47:58 INFO LOG Info count=3
2025/01/03 22:47:58 WARN LOG Warn count=3
2025/01/03 22:47:58 ERROR LOG Error count=3

More into logging:

NOTE: the Level

const (
    LevelDebug Level = -4
    LevelInfo  Level = 0
    LevelWarn  Level = 4
    LevelError Level = 8
)
  1. slog.NewTextHandler : Output key=value pairs

same way we have,

logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
logger.Info("hello", "count", 3)

Output:

time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3

For JSON: slog#JSONHandler

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("hello", "count", 3)

Output:

{"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}

TRY: https://github.com/kataras/golog : with colors

import (
    "github.com/kataras/golog"
)

func main() {
    golog.SetLevel("debug")

    golog.Println("This is a raw message, no levels, no colors.")
    golog.Info("This is an info message, with colors (if the output is terminal)")
    golog.Warn("This is a warning message")
    golog.Error("This is an error message")
    golog.Debug("This is a debug message")
    golog.Fatal(`Fatal will exit no matter what,
    but it will also print the log message if logger's Level is >=FatalLevel`)
}

Output from win terminal

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.