Go (Golang) - Logging that matters

Published on 2019-07-14.

Some people complain about the logging capabilities of the log package from the Go standard library, but I not only find it adequate, I really like it.

As with everything else in Go the standard log package has been designed as a very simple, pragmatic, and easily customizable logger and there is by no means need for any third party logging packages.

Think about it for a minute, logging is all about information, relevant, clear, and useful information. You don't need several levels of logging categories.

In other environments you often see level categories such as these.

In reality we only really deal with issues that can be compacted into three types.

Type 1

Type 2

Type 3

Which we can concatenate into the following:

The DEBUG level is the most verbose level of logging and it is only required during development, testing, and bug hunting. At DEBUG level you need trace information and as much information as you can get.

With the runtime/debug package you can put together a very simple debug level of logging with:

log.Printf("%s", debug.Stack())

You can then wrap that up in a custom debug log function with what ever else you need.

You don't need WARNING, NOTICE, or ALERT levels of logging as that's just basic information, no real errors have occurred.

What about the ERROR level then?

Well, if a function returns an error you need to make your application handle the error all the way back to the calling function, in which case you have handled the error and then there really isn't an error anymore because you made sure your application handled it.

A part of the process of handling errors is of course to log such issues, but then you're not dealing with an error anymore because you have just handled it.

An error is when the application fails for some reason - which you then have to deal with and all you need is INFORMATION about the event.

err := makeMeHappy()
if err != nil {
    log.Println("INFO: We could not make you happy.")

    // Now what!?
}

Your application needs to take adequate steps to handle issues that might not go according to plan. This IS error handling. Of course you need to log such issues, but an issue is not really an error when it has been handled.

With the Go log package you can specify flags with the SetFlags function which allows you to get more relevant information such as function name, line numbering, etc. In most cases this basic stack trace information is all that is required to investigate the cause of the issue. It is always a good idea to prefix each message with the name of the subsystem and other relevant information such that it becomes more easy to filter messages.

Besides from handling errors, you require useful information about user activity, and depending on the situation the user sometimes requires feedback from the system. All such types of data is just INFORMATION.

The only thing left to log is an error that cannot be handled, that is a FATAL error or a PANIC. Fatal errors are things that are so bad that your application cannot and should not continue with this malfunction. If your application is dependent upon a database and it suddenly cannot connect to the database, then that is a fatal error that not only should be logged as FATAL, but it should of course also halt the application in some graceful manner.

If you're dealing with a web application you can wrap panics up in a custom recover function with defer and recover and have the application display a standard error message to the user and then have the application email or SMS you about the situation while logging the relevant information - this is a graceful panic. All of which can be implemented using the standard library only.

You can wrap your logging up with all you need using just the standard library and some custom functions, including the ability to communicate with syslog and remote logging servers.

Nothing more is needed.