157

What's the pattern for application logging in Go? If I've got, say, 5 goroutines I need to log from, should I...

  • Create a single log.Logger and pass it around?
  • Pass around a pointer to that log.Logger?
  • Should each goroutine or function create a logger?
  • Should I create the logger as a global variable?

8 Answers 8

68
  • Create a single log.Logger and pass it around?

That is possible. A log.Logger can be used concurrently from multiple goroutines.

  • Pass around a pointer to that log.Logger?

log.New returns a *Logger which is usually an indication that you should pass the object around as a pointer. Passing it as value would create a copy of the struct (i.e. a copy of the Logger) and then multiple goroutines might write to the same io.Writer concurrently. That might be a serious problem, depending on the implementation of the writer.

  • Should each goroutine or function create a logger?

I wouldn't create a separate logger for each function or goroutine. Goroutines (and functions) are used for very lightweight tasks that will not justify the maintenance of a separate logger. It's probably a good idea to create a logger for each bigger component of your project. For example, if your project uses a SMTP service for sending mails, creating a separate logger for the mail service sounds like a good idea so that you can filter and turn off the output separately.

  • Should I create the logger as a global variable?

That depends on your package. In the previous mail service example, it would be probably a good idea to have one logger for each instance of your service, so that users can log failures while using the gmail mail service differently than failures that occured while using the local MTA (e.g. sendmail).

0
46

For simple cases, there is a global logger defined in the log package, log.Logger. This global logger can be configured through log.SetFlags.

Afterwards one can just call the top level functions of the log package like log.Printf and log.Fatalf, which use that global instance.

2
  • Thought you can set the flags you can't use a custom logger.
    – 0xcaff
    Commented Apr 28, 2014 at 18:44
  • @caffinatedmonkey actually, you can use custom loggers if they implement the io.Writer interface and you change the output of the default logger via SetOutput(). Commented Nov 19, 2014 at 4:58
29

This is a simple logger

package customlogger

import (
    "log"
    "os"
    "sync"
)

type logger struct {
    filename string
    *log.Logger
}

var logger *logger
var once sync.Once

// start loggeando
func GetInstance() *logger {
    once.Do(func() {
        logger = createLogger("mylogger.log")
    })
    return logger
}

func createLogger(fname string) *logger {
    file, _ := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)

    return &logger{
        filename: fname,
        Logger:   log.New(file, "My app Name ", log.Lshortfile),
    }
}

You can use it in this way

package main

import (
    "customlogger"
    "fmt"
    "net/http"
)

func main() {
    logger := customlogger.GetInstance()
    logger.Println("Starting")

    http.HandleFunc("/", sroot)
    http.ListenAndServe(":8080", nil)
}

func sroot(w http.ResponseWriter, r *http.Request) {
    logger := customlogger.GetInstance()

    fmt.Fprintf(w, "welcome")
    logger.Println("Starting")
}
2
  • I got the error: package customlogger is not in GOROOT (C:\Program Files\Go\src\customlogger) Commented Sep 3, 2022 at 17:32
  • Keep it simple! This helps in getting project started. I kept wraping my head about how to handle single instances / global instance over Services and http.handlers for logger, database, sessions, sqlite3, redis, leveldb, etc.
    – John
    Commented Aug 13 at 5:48
11

I know this question is a bit old, but if, like me, your projects are made up of multiple smaller files I vote for your 4th option - I've created a logger.go that is part of package main. This go file creates the logger, assigns it to a file, and provides it to the rest of main. Note I have not come up with a graceful way to close errorlog...

package main

import (
    "fmt"
    "log"
    "os"
)

var errorlog *os.File
var logger *log.Logger

func init() {
    errorlog, err := os.OpenFile(logfile,  os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        fmt.Printf("error opening file: %v", err)
        os.Exit(1)
    }

    logger = log.New(errorlog, "applog: ", log.Lshortfile|log.LstdFlags)
}
1
  • 9
    For graceful closing, you could probably defer errorlog.Close() at the end of execution, or to better ensure its closed, set up signal handlers using Go's signal package golang.org/pkg/os/signal
    – Anfernee
    Commented Mar 22, 2016 at 12:39
3

This is an older question, but I would like to suggest the use of http://github.com/romana/rlog (which we developed). It is configured through environment variables, the logger object is created and initialized when rlog is imported. Therefore, no need to pass around a logger.

rlog has quite a few features:

  • Fully configurable date/time stamps
  • Simultaneous output to stderr or stdout as well as file.
  • Standard log levels (Debug, Info, etc.) as well as freely-configurable multi-level logging.
  • On demand logging of caller info (file, line number, function).
  • Ability to set different log levels for different source files.

It is very small, has no external dependencies, except the standard Golang library and is actively being developed. Examples are provided in the repo.

1
  • 3
    Thank you for disclosing your affiliation with the product you are recommending! It is appreciated. Commented Dec 5, 2016 at 1:51
2

I found the default log package (https://golang.org/pkg/log/) somewhat limiting. For example, no support for info vs. debug logs.
After some poking around, settled on using https://github.com/golang/glog . This seems to be a port of https://github.com/google/glog and gives decent flexibility in logging. For example when running an application locally you may want DEBUG level log but might want to run only in INFO/ERROR level in production. The list of full features/guide is, here https://google-glog.googlecode.com/svn/trunk/doc/glog.html (Its for the c++ module, but for the most part translates to the golang port)

2

a more up-to-date (Apr 2024) is to use the "log/slog" built-in package in go 1.22

There you create the log, config it and then SetDefault(configuredLogger)

from now on you just import "log/slog" and use it directly. IS kind of what you do in python with getLogger

Full post here: https://betterstack.com/community/guides/logging/logging-in-go/#getting-started-with-slog

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()
}

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.