HACKER Q&A
📣 Franck21

Should I log errors, throw errors or both?


Hi HN,

When programming an API, or any kind of application, software, ecc , at some point in the code you know that something could go wrong and you should notify it as an error, now, should I throw it? Or should I just log it as an error and let the program continue execution anyway? Or should I do both?

Let me know what you think are the best practices to notify errors and how you usually manage this situation.

Btw this is my first HN post:)

Thanks!


  👤 bheadmaster Accepted Answer ✓
Always remember the context of your code. All code is ultimately used by other humans, so we should make it convenient for them, depending on how will they use it.

Are you writing a library? Make it convenient for the user to handle errors - in a way that is consistent with the language. E.g. in Go, return an error. In Python, raise an exception.

Are you writing a CLI program? Print to stderr, and abort program if execution cannot be safely continued.

Are you writing a GUI program? Create an error dialog box, and abort program if execution cannot be safely continued.

Are you writing a web service? Return the appropriate error code (or the equivalent in whatever protocol you use) and a human-readable error message about what happened.

Et cetera.


👤 chrisguitarguy
Log (and presumably) deal with errors if you _can deal with them_ close to where they occur. Otherwise just let them throw and/or propagate them up. An application should have some sort of top level error handling to log the error and actually return a response (HTTP 500 or otherwise) to the client.

For something like a library: no logging, that's not a library's job -- unless it's a logging library! If the language has exceptions, then throwing only subclasses or implementations of a single exception parent/interface from the entire library is nice for clients of the library.


👤 lurker137
Here is what the static code analyzer Sonar [1] always says for Java:

> “In applications where the accepted practice is to log an Exception and then rethrow it, you end up with miles-long logs that contain multiple instances of the same exception. In multi-threaded applications debugging this type of log can be particularly hellish because messages from other threads will be interwoven with the repetitions of the logged-and-thrown Exception. Instead, exceptions should be either logged or rethrown, not both.”

1: https://rules.sonarsource.com/java/RSPEC-2139


👤 nottorp
Logging should be considered separately from what you do with an error inside the code. Log your errors.

Then when the customer calls you and says "it crashes but i have no idea what i was doing" you can just have them send you the log instead of trying to guess what happens.


👤 tlarkworthy
I throw errors (it's appropriate to stop code execution). But I also log it, so I can see it historically, but now I also do a 3rd thing!

I invoke a programmable breakpoint, so that if I happen to be debugging that code, I get the debugger setup where I want it!

Note this is only appropriate for things that should "never" happen.


👤 jameshush
This post is close to 20 years old but it's still how I handle errors: https://www.joelonsoftware.com/2003/10/13/13/

From the article:

    People have asked why I don’t like programming with 
    exceptions. In both Java and C++, my policy is:

    1. Never throw an exception of my own
    2. Always catch any possible exception that might be thrown by a library I’m using on the same line as it is thrown and deal with it immediately.
I've been coding this way for ~10 years now and it's made things a lot easier for me. If the user should know about the error, let them know about it, if it should be hidden from the user, I'll track it in something like Sentry or Datadog and send out an alert for whoever's on call.

👤 lhnz
You really shouldn't let the program continue if it's in an error state which hasn't been recovered from and can't be recovered from. You should hard fail if you're in such a state.

Otherwise, I would always throw and then only log errors in a single place within your application code. This avoids the need to be able to inject the correct type of logger into library code, etc. And makes it easier to attach other information about the execution of the application into the eventual error log line.

The important thing is that your errors are clearly understandable at the point that they are logged, therefore I also recommend creating your own granular application-level error types that wrap any underlying errors, writing sensible error messages, and attaching contextual state about the process that failed.


👤 jandrese
Lots of good advice in this thread. One thing I will request is that you don't listen to the Windows developers who tell you to suppress all errors because they will give away your secret sauce and/or scare the end user. It is never acceptable to pop up a "An error occurred" dialog box and exit with no further clue as to what happened. Even in a security context you need to give the user something that they can Google.

Of course it is mandatory to never ever put anything useful in the Windows Event Viewer. Ever. Absolutely forbidden. Only useless messages are ever allowed in there. So many times I've had an application crap out and I check the event viewer thinking this time will be different. It never is.


👤 Theodores
The real world is always going to be different to your development environment. You can't necessarily go into debug mode. Therefore logging is your friend.

Normally I leverage the underlying logging and get my code to write to its own log file. I like it if I can independently turn on the logging and the verbosity for my module, even if logging is turned off for performance reasons in the main code.

Once everything actually works you might end up with 'carpet logging' in your code, where every bit of data is getting logged just in case there is an error. You might also have catch blocks around everything.

At this stage you can remove the excess logging and allow exceptions to bubble up, resisting the temptation to refactor anything else. If this works and you have something stable, then you can optimise your code without it being 'premature'.

I agree with @jameshush, however, if the code you are working with throws exceptions and has a sophisticated way of dealing with them, then you can throw exceptions that are going to work with what there is already.


👤 shireboy
It’s important to distinguish between validation and exception. If a client passes in bad value you can handle:, out of range, not found db item etc, return an appropriate 400 with details about the error that the client can do something with. Handle it gracefully if you can without throwing exception.

If it’s something impossible to handle - say a db failure after n retries- then return a 500 with some info but not anything sensitive. No db names, queries, etc.

Sometimes your exceptions tell you about additional validation you need to add. Ie users keep passing in invalid ids and I get a db exception, maybe I should instead check for the row first and return a 404.

Log all the things.

There are standards for REST error responses: https://blog.restcase.com/rest-api-error-handling-problem-de....


👤 joshka
My thoughts (as a Java / C# developer that occasionally dabbles in Ruby, Python, Go, JS and other languages):

   * Throw exceptions when things are broken
   * Catch exceptions when the calling code can do something other than propagate the error
   * Log exceptions at the level where they are uncaught
   * Log information about errors that are handled rather than the exception detail
   * Never log and propagate the error. This often leads to double logging, especially when the error
     is further propogated. This in turn causes confusion about whether there is one error or two, as
     well as whether one error is a cause or is caused by the other error.
   * Always provide enough information in your own exceptions about:
     * what you were trying
     * what went wrong
     * what the caller can do to fix the issue (even if that's an indication that nothing can be done)
   * Never discard information about an exception unless it will truly never serve any purpose
   * Consider storing (but not writing) debug log messages that are only used when something does crash.
     How we got to this point is usually the most important thing a developer needs to know. Google "Ring
     Buffer Logging"
   * Always think about how you would prevent the error from happening rather than letting it fail
   * Log errors at system boundaries and report useful errors to the external system
   * Never include confidential info in errors (passwords, keys, tokens)
   * Ensure errors can be correlated in some way (request ID / correlation ID etc).
   * Ensure that the contextual level of the exception matches the level that the caller expects.
     E.g. an error throw by code that saves a customer record generally shouldn't ever be a FileNotFound
     or a DivideByZero error even if this is what happened somewhere down the stack.
These are generalizations, and like many rules can be broken if it makes sense. Do so with good reason though.

👤 Archelaos
Well, one could either write a whole book about it or just say: do whatever seems best in every individual case. Fundamentally, an error is just a branch in an execution path. Your program might recover from it or not, it might be important to inform the user about it or not, it might be important to inform the developer about it or not, ...

👤 AdamN
* If it's an actionable error, catch it as low as you can action on it. Otherwise let it propagate.

* If it's a useful error for another developer (or yourself) to see, log it.

* Use different loglevels in your code so another developer (or yourself) can increase or decrease the verbosity of your logging in different environments and under different circumstances.


👤 raxxorraxor
Depends a bit what you mean by API. For APIs I usually don't log anything, it is the responsibility of the user to provide logging. Otherwise you end up with countless logs from sub modules and probably many different logging frameworks. But not logging anything does mean that the API needs to supply important information in its responses to user calls. There are exceptions though and it depends on the type of API. The typical web API is already one in most cases.

Whether you just throw an exception or handle it yourself and package any error into a neat error object has advantages and disadvantages. Disadvantage to just throwing an exception is that the user might not get a sensible context. The call stack might not mean much to the user if he doesn't know about internal workings. But the advantage is that there is a uniform way of handling exception. The user immediately knows that something went wrong if an exception is thrown somewhere on a lower level. If the API handles the exception itself, it might be able to add the relevant information as to what caused the error, especially for common errors. In that case the user needs to know how to parse the error responses of your API though.

You should never just let the program run after it encountered an exception without informing the user. A log cannot sensibly inform an application about errors at runtime and this "swallowing" of exceptions is much more difficult for the user to handle or even to know if something went wrong.

I think the only rule of thumb is really that it is primarily the responsibility of the user to handle error and log application messages. But when I think of API I think of interfaces to specific devices or software, there are completely different rules for web APIs. Those cannot just throw exception to the user anyway. They also often have many users within one execution environment, so internal logging can certainly make sense.

Another topic is that error != exception and different rules might apply.


👤 tpoacher
I don't know if other people do this (or if it's considered bad or not), but I often find myself coding error branches that in theory should never occur, and are marked in the code as such.

This serves two purposes. One, if for whatever reason you end up with a scenario you hadn't anticipated, it will be picked up and the program will exit gracefully, informing you exactly which supposedly impossible condition was violated.

The second purpose is that, often enough, inserting such an "impossible error" branch makes the preceding code much clearer.

I'm so used to this idiom, in fact, that I now rarely have an 'else' clause catching a proper fallback case, when dealing with an if/else block. I treat the fallback case via another elseif, and almost always the else simply raises an error if triggerred.


👤 ozim
You question seems to be asked to look for one size fits all solution.

There is no single solution.

For example your database is down - only thing that you can do is to show user error "please try again" - while you should have detailed logs in place to find why application cannot connect to database. Your program most likely cannot continue even on other screens so you most likely throw exception that should bubble up to reach interface and stop any execution.

Other scenario some third party api call failed - your user does not care and he wants to continue working, you probably want to show warning "you can continue but our systems cannot reach XYZ". So you don't want to throw an exception because you still want let user work on whatever they need. You still want to have it logged with all details to check what is the problem.


👤 fareesh
Take for example some Null exception where the system is expecting a value but the value is null.

If it's safe for the user to proceed with a default or empty value, then you can probably log the error and proceed. For example if you have a page which shows a post, and for some reason the author's name is null, you could probably show the page and not display the element containing the author's name, that way the user can at least read the post.

If it's not safe to continue - say the post body itself is null for some reason, you can throw an exception as well and show the user an error.

In both cases, especially the latter, you'll want to have some kind of monitoring enabled that notifies you of the problem along with the relevant logging of context that will help you diagnose the source of the problem.


👤 narag
I use two levels. I log the closer to the error the better, with severity levels to filter non-error exceptions, only what I want to know if I get an incident report.

Then I free resources and let the exception reach user action level, where an informative, actionable error is shown to the user.


👤 MrSydar
Depends. Golang allows you to create a nice error chain with fmt.Errorf where you combine your message with the previous one up to the point where you should print the log message and either end program, or ignore the issue. This strategy makes sure code remains nice and clean. Don't forget to keep messages local to their context, that is, the function X shouldn't return an error saying "X failed due to event A", instead it should return "failed to do B, because an A has happened", then the caller say that X has failed because of `X message`, etc. You get sort of stack trace so programmer knows an exact path.

👤 dangerface
I throw exceptions for anything that isn't an expected result. Request a row from a database that doesn't exist throws an exception NoResults let the caller decide what to do.

The caller can then recover from the exception by searching else where or using a default etc.

If the exception can not be recovered then its an error. If its a user error like asking for a result that doesn't exist ill notify the user without logging. If its an unexpected error the exception is unhandled and bubbled up to a global exception handler that logs to a file.

Some times ill setup an email on error if the code is important enough.


👤 qalmakka
In C++, I like to return fallible values (std::optional or std::expected from C++23 are good choices), and use exceptions as something in between an error and an assert.

I personally find that exceptions are very good to handle rare runtime failures that are not expected, and that severely hamper the flow of a program. In general, these cases tend to be rare, and under their circumstances terminating is generally ok. Still, it's nice to give the developer some control over if and when they want to handle it, especially if it is a library.


👤 theshrike79
Fail fast.

Return an error with a proper HTTP error code to the client, log what happened and exit the process.

Every attempt at trying to recover from errors will eventually fail, in most cases it's better just to fail.


👤 TYPE_FASTER
I've gotten in the habit of throwing an exception that results in an error in the UI if the problem is caused by data or something else the user can fix, raising an alert to be handled by the team if there's an issue that needs to be fixed by the team to prevent an outage, and sending daily e-mail reports for things the team wants to monitor (usually temporarily).

It's more about the categorization and impact than the implementation. Feel free to send me an e-mail if you have questions.


👤 jstimpfle
The idea that there is only one way to propagate errors, namely "up" in the call stack, is totally misguided. IMHO. It leads to function call dependencies, and ultimately to the kind of code where we're thinking to much about the code and too little about the data structures.

There is an alternative to returning error codes or throwing exceptions: Put the errors on some context handle - e.g. set an error flag. This taints the context where the error occurred, and if you do it right you can contain the error and replace / restart / shutdown that context in isolation. And importantly, it allows the client to do the error handling at a more central point.

This kind of design can require that you do a little "paranoid" error checking at several entry points in the implementation of your API, but it's well worth it given that you save a lot of immediate error checking on the client side. Also, if you structure things right you can minimize the need for error checking in the implementation itself.

A simple and well-known example of this approach in C would be the FILE API from stdio.h (fopen()/fread()/ferror()).

This and returning NULL/error codes is how I structure my applications. I have a strong distaste for exceptions, for the same reasons mentioned in the other post citing Joel Spolsky. But exceptions are fine for quick and dirty scripts. And some people maintain that you should structure whole applications purely with RAII and rely on exceptions to cleanup your contexts perfectly and immediately. I'm not convinced that this is practical, but honestly as probably most, I'm content just living in my own bubble...


👤 Saphyel
I think depends on the context.

* If you are doing a library you can safely let the Error do the thing.

* If you are building a web/API catch all the errors, log them and send a nice generic error to the client.

* If you are doing a CLI tool I prefer to do like the library but if it's for a final client probably I'd try to do it like with the web/API.


👤 mekster
> should I just log it as an error and let the program continue execution anyway?

How can you even get to such a conclusion? These kind of attitude is what makes programs buggy.

Plant an error tracker, so thrown errors are automatically logged, so you can debug later but don't make the situation worse by continuing on.


👤 chrisweekly
Great question, and lots of good discussion in the comments so far.

My contribution is a slight tangent, specific to React and Remix.run: take a look at the docs and official blog posts about ErrorBoundary and in Remix also CatchBoundary for good examples / patterns.


👤 pimlottc
Generally you shouldn’t be logging errors at the point where you are (re-)throwing them. Otherwise you will likely end up logging the same error twice, or even worse, logging an error that is actually safely handled by code higher up the stack.

👤 mahmoudhossam
Errors should almost always be logged, that way it's easier for you to diagnose errors during runtime.

Throwing the error is useful when callers of a function need to know that error has occurred, otherwise it should be handled within the function.


👤 1ark
I found this[1] recent discussion very insightful.

1. How “let it fail” leads to simpler code. https://news.ycombinator.com/item?id=32080903


👤 marto1
Oh, that can of worms again. Just for fullness sake, in C-like languages there's also error codes and even the infamous "goto" statement as a potential way for dealing with errors.

👤 Aeolun
I tend to log errors right where they are thrown, and have a separate global error handler that deals with all the exceptions that come through from various parts of the application.

👤 oezmas
How is this dealt with in purely functional languages? I'm no expert, but aren't all possible exceptions catched in an Option<>-Type of sorts?

👤 brudgers
There are other engineering strategies, for example:

https://wiki.c2.com/?LetItCrash

Good luck.


👤 olvy0
Congrats on your first HN post! This place can be intimidating at first, but people here are generally very helpful.

As for your question: In what language? What ecosystem?

Different languages have different error handling paradigms, and "customs" (aka idiomatic code). Some languages have even changed slightly over the years. It also depends on other, existing methods in the API, if it's an existing one.

Try to be consistent and not to surprise your users.

It also depends on what you mean by an "API". The original meaning of this phrase (calling inside the boundary of a single executable, or between applications using a more or less invisible RPC mechanism) has largely changed in recent years to mean Web APIs. Most answers around me allude to this.

My own take is below. My experience is mainly with C, C++ and C#, with some dabbling with Haskell, D, Lisp, Rust, and JavaScript.

1) Never throw exceptions, except under exceptional circumstances, when your program should crash anyway since you're in a point in the code in which you have no idea how to continue. I like Rust's approach that where there aren't any catch-able exceptions, only panics that always crash the application.

2) In particular, don't throw exceptions for bad user input, bad data from a file or database, error codes from another website or API. You know and you should handle them inside your API. Don't let exceptions from lower-levels APIs you use propagate upward. They don't mean anything to the caller and effectively expose your internal implementation.

3) It might be ok to throw exceptions inside your method if it makes your life a bit easier, if you catch them at some topmost level and return an error. It depends on the language you use.

4) A crashing application should create a dump file, which make analyzing errors in production much easier and quicker. This doesn't concern on you as the writer of a lower-level API, but it something you should raise with your direct or indirect consumers.

5) A slightly better way is to use return codes. But it's still not very good. In C for example that's about the only thing you have, except a global error code and that's even worse.

6) A better way is to use Option/Result types, but not all languages will have them and if they do they might not be idiomatic, and some of your users will suffer since you force upon them an unfamiliar paradigm and/or external dependencies. Also even Option/Result type often make the calling code devolve into if/else chains, unless the language has support for monadic chaining, and while you can find it, it's not idiomatic in many languages (yet?). Also few languages will force your callers to handle those returned errors.

7) That said, exceptions are your friends during development and also after - throw them when you reach a point in the code that you know you should never reach. This is if you can't make the error situation somehow fail compilation.

8) As for logging - yes you should log each error. My take on logging - if it's a lower level API code, it should never do logging by itself, unless it will always be a part of larger framework that already has an integrated logging. What I usually do is accept an optional logging callback to let the caller implement the logging.


👤 faangiq
Totally situational. Good question to be thinking about though.

👤 theflyingelvis
Meh, just do it the old VB6 way..

On Error Resume Next

Who needs errors anyway?


👤 glouwbug
Neither. Just write infallible code