Nate Finch started on the Go Forum.
This article focuses on Go, but if you find that your approach is outdated, the ideas presented in this article may be broadly applicable.
Why is there no love?
Go’s log packages are not classified, you must manually add prefixes such as DEBUG, INFO, WARN, and error. Additionally, Go’s Logger type has no method for turning these levels on or off on a per-package basis. By comparison, let’s look at some third-party alternatives.
Google’s Glog offers the following levels:
- Info
- Warning
- Error (Error)
- Fatal
Another library we developed for Juju, LogGo, provides the following levels:
- I’m tracing it.
- Debug.
- Info
- Warning
- Error (Error)
- Critical
Loggo also provides the ability to adjust the level of log detail on a per-packet basis.
Here are two examples, obviously influenced by other logging libraries in other languages. In fact, their command lines can be traced back to Syslog (3) or even earlier. I think they’re wrong.
I want to take a paradoxical position. I think all logging libraries are bad because they offer too many features; Programmers are dazzled by a bewildering array of choices and must think clearly about how to communicate with future readers; Who will consume their logs.
In my opinion, a successful logging package requires much less functionality, and certainly fewer levels.
Let’s talk about Warning
Let’s start with the simplest. No one needs a warning log level.
No one looks at warning because, by definition, there is no big problem. Maybe something will go wrong in the future, but that sounds like someone else’s problem.
Also, if you are using a certain level of logging, why set the level to Warning? You set the level to info or error. Setting the level to warning recognizes that you may be recording errors at warning.
Eliminate the warning level, which is either an informational message or an error condition.
Setting the level to warning acknowledges that you may record errors at warning.
Eliminate the warning level, which is either an informational message or an error condition.
Let’s talk about fatal
Fatal is effectively logging the message and then calling OS.exit (1). In principle this means:
- The defer statement in the other Goroutines does not run.
- The buffer is not flushed.
- Temporary files and directories are not deleted.
Log.Fatal is actually more detailed than panic, but semantically equivalent.
It is generally considered that libraries should not use panic, but if a call to log.fatal has the same effect, of course this should also be outlawed.
It is recommended to resolve this cleanup problem by registering a shutdown handler with the logging system, which tightly couples the logging system and each location where the cleanup occurs; This also violates the separation of concerns.
Instead of logging at the fatal level, return an error to the caller. If the error persists until main.main, this is the correct place to handle any cleanup operations before exiting.
Let’s talk about error
Error handling is closely related to logging, so on the face of it, error-level logging should be reasonable. I disagree with you there.
In Go, if a function or method call returns an error value, there are actually two options:
- Processing error.
- Returns the error to the caller. You can choose to wrap errors, but that’s not important for this discussion.
If you choose to handle an error by logging, then by definition it is no longer an error – you have already handled it. The act of logging an error handles the error and is therefore no longer appropriate to log as an error.
Let me try to convince you with code:
err := somethingHard()
iferr ! =nil {
log.Error("oops, something was too hard", err)
return err // what is this, Java ?
}
Copy the code
You should not record anything at the error level because you should either handle the error or pass it back to the caller.
To be clear, I’m not saying you shouldn’t record the conditions under which an error occurs
iferr := planA(); err ! =nil {
log.Infof("could't open the foo file, continuing with plan b: %v", err)
planB()
}
Copy the code
But log.info and log.error serve the same purpose.
I’m not saying don’t record mistakes! Instead, the question is, what is the smallest logging API? When it comes to errors, I believe that the vast majority of entries recorded at the error level are simple because they relate to errors. In fact, they are only informational, so we can remove error-level logging from the API.
What’s left?
We exclude warnings, believing that nothing should be logged at the error level, and stating that only the top level of app should have some type of log.fatal behavior. What’s left?
I think there are only two things you should record:
- Things that developers care about when developing or debugging software.
- What users care about when using software.
Obviously, they are the debug level and the information level, respectively.
Log.info simply writes this line to log output. You should not choose to turn it off because you only need to tell users what works for them. If an unhandled error occurs, it pops up at the end of the program main.main. Inserting the FATAL prefix before the last log message, or writing os.stderr directly with FMT.Fprintf, is not sufficient for logging package growth with the log.fatal method.
Log.debug is a completely different thing. It is controlled by the developer or support engineer. Debug statements should be rich during development without having to resort to the Trace or Debug2 (you know who you are) level. The log package should support fine-grained control to enable or disable debugging, and debug statements should be enabled or disabled only within the package or finer scope.
conclusion
If this were a Twitter poll, I’d give you a choice between the two
- Records are important
- Records are difficult to
But the truth is, logging is both. The solution to this problem must be to structure and ruthlessly pair unnecessary distractions.
What do you think? Is it crazy enough to work, or just plain crazy?
notes
-
Some libraries may use panic/ Recover as an internal control flow mechanism, but it is important that they do not allow these control flow operations to leak outside the package boundaries.
-
Ironically, although it lacks debug-level output, the Go standard log package has both Fatal and Panic capabilities. In this package, the number of functions that cause a program to exit abruptly outweighs those that do not.
Related links:
- Talk, then code
- Stack traces and the errors package
- The package level logger anti pattern
- Context is for cancelation
Posted by Dave Cheney on November 5, 2015
The original connection: dave.cheney.net/2015/11/05/…