Search code examples
gologgingdatadog

Logging unhandled Golang panics


I have a logrus log handler in my Golang application. Logs are formatted with JSONFormatter and are submitted as a single line to Datadog, which aggregates them and displays them nicely. However, I recently discovered a case where there's an unhandled panic, and this is not captured with the logrus logger. This results in the actual panic and stack trace being spread across multiple output lines, which Datadog collects individually. This costs us money and makes the logs very difficult to read.

I'm going to fix the issue, but in the event that any further unhandled panics happen, I'd like to be able to capture them using the logrus JSONFormatter.

Something like this:

package main

import (
    "os"
    "sync"

    "github.com/sirupsen/logrus"
)

var (
    loggerInstance *logrus.Entry
    once           sync.Once
    logger  = GetLogger()
)

// GetLogger initializes and returns a reference to a CustomLogger object.
func GetLogger() *logrus.Entry {
    once.Do(func() {
        logrus.SetFormatter(&logrus.JSONFormatter{})

        // We'll just pipe everything to stdout. It's json so Datadog will parse the level regardless
        logrus.SetOutput(os.Stdout)
        logrus.SetLevel(logrus.TraceLevel)                            // We'll leave this as trace and just filter out logs when we index them
        loggerInstance = logrus.WithFields(logrus.Fields{"env": "local"}) // Add env tag for easy log parsing
    })
    return loggerInstance
}

func main() {
    logger.Info("Logrus using json logging")
    logger.Warn("We are about to panic")

    var things = []string{
        "hi",
        "yes",
        "thing"}

    print("%s", things[len(things)])
}

This produces the following output. As you can see, the first two logs use logrus, but the unhandled panic does not.

{"env":"local","level":"info","msg":"Logrus using json logging","time":"2020-03-03T15:12:30-08:00"}
{"env":"local","level":"warning","msg":"We are about to panic","time":"2020-03-03T15:12:30-08:00"}
panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
        /Users/nathan.deren/Library/Preferences/GoLand2019.3/scratches/scratch.go:38 +0xbe

Process finished with exit code 2

Is it possible to get those last several lines to log using logrus?


Solution

  • Try recover to catch all panics and log them. Without that, it'll write the panic msg to stderr:

    func main() {
       defer func() {
            if r := recover(); r != nil {
                logger.Errorf("Panic: %v", r)
                os.Exit(1)
            }
        }()
        // rest of main here
    }