log.Fatal vs log.Panic

Update: probably the best alternative to log.Fatalf inside main is log.Printf followed by a return statement. If your main function contains a lot of such exit points, consider using a step driven evaluation pattern.

I personally don’t like log.Fatal/Fatalf/Fatalln. I feel sad because of their ubiquity in Go examples as a form of reaction to an error. I personally prefer log.Panic.

The log.Panic vs log.Fatal is essentially panic vs os.Exit(1). The latter will not let deferred calls to run. Most of the time, it’s not what you want. It also makes testing much harder. It’s quite simple to stop panic in test by the means of recover. It’s much harder to cope with a code that does os.Exit somewhere while you’re trying to do end2end testing without loosing coverage info.

I have written exitAfterDefer check for the go-critic linter. It warns about log.Fatal if the function where it’s used deferred any calls prior to that line.

For example, that check would generate a warning for this kind of code:

defer os.Remove(filename)
if err != nil {
    log.Fatalf("error: %v", err)
}

warning: log.Fatalf clutters defer os.Remove(filename)

If you can, avoid log.Fatal. Don’t reject log.Panic just because most people follow “don’t panic” mantra too heavily. Exit is worse that panic, period. If you have a choice, choose something that does less potential damage.

So, you know what to do now:

if err != nil {
-   log.Fatalf("ooops: %v", err)
+   log.Panicf("ooops: %v", err)
}