I saw this post https://www.fpcomplete.com/blog/error-handling-is-hard/ on hacker news and wanted to share my thoughts on error messaging (not in rust, but in a more general sense). In rust, this can be tackled by wrapping errors inside other errors.
When logging an error to the console, which one seems more useful?:
expected byte {, found ","
ERROR config load: parse json: at index 32, expected "{", found ","
For most languages, it’s relatively easy to build error types and strings so that your messaging is helpful. In a lot of cases, such as with python, you can make custom error types which can stringify to a helpful message like the ones in this document.
Message Type
We need to be clear if this log is an error the user needs to care about. Most logging libraries have independent levels debug
, info
, trace
which can be filtered, and the level automatically printed. Some of these even inject (or can inject) filenames / line numbers, which is good for locating an error when you’re not sure exactly where it’s coming from.
Module Name
This can be accomplished by auto-printed filenames. If that’s not available, I usually try to include the high-level task that’s occurring to provide context.
In the above example, config load:
is the string under this category. That way, it’s easy to tell what is actually meaningful to something you’re debugging.
Error Type
This is the kind of error you’re experiencing - for this could be a string, or error type if your language’s error handling supports it.
In the above example, this is the json parse error:
bit. In python, you could print the exception’s class name.
Error Context
This includes error context. In the case of a failed system call, this should probably be the error code / messaging, if parsing / validating inputs, this should be information on what was expected vs. what was found.
next at index: 32, expected "{", found ","
- context on exactly what went wrong, and how to fix it.
This is usually the message passed into your exception, e.g. throw new ValueError('you must specify a name')
Fix Info
If this error is caused by something the user can correct (incorrect config, or something related), it may be useful to append a string explaining what they can do to fix this in (concise) plain english. You can sometimes embed this directly into the error message
This section may or may not be desired depending on the audience for your log output. If producing a command line tool to be used by the software community at large, this section may be useful for people who are new/getting used to your tool.
Sometimes this isn’t needed, as the context also explains the fix.
Stack Trace?
This is also useful to log, when you’re not sure exactly how your code got to the place where it errored out.
Customizing
Some of those sections may not be needed in all cases, but often, more info about an error is better. But in a lot of cases, anything is better than a plain console.log
or print
message that doesn’t indicate what the error is, where it occurs, etc.
A base format
<log type> <module/task>: <error type>: <error context> <fix info?>
More Examples
ERROR startup: no bind address specified. Specify this via the --bind-addr command line flag.
ERROR newsletter-subscribe: failed to send email: the upstream service responded with status code 503.
ERROR blog: failed to load post: connection timeout. Is the database offline?
WARNING auth: the current user does not have 2FA enabled.
INFO config: consider enabling the 'content-security-policy' flag.