Coding Style – Should Errors Be Logged Where They Occur or Passed Back?

coding-styleerror handlinggologging

One of the things I love about Go is how they encourage passing errors as return values, but when it comes to logging what is the most maintainable solution: passing the error as far back down the execution stack as possible and logging everything there, or logging it in the function it occurred there and then?

So the former:

func main() {
    err := DoSomething()
    if err != nil {
        log.Printf(err)
    }
}

func DoSomething() error {
    return DoSomethingElse()
}

func DoSomethingElse() error {
    return ThisFunctionMayCauseAnErrorAndIsHorriblyNamed()
}

Or

func main() {
    _ := DoSomething()
}

func DoSomething() error {
    return DoSomethingElse()
}

func DoSomethingElse() error {
    err := ThisFunctionMayCauseAnErrorAndIsHorriblyNamed()
    if err != nil {
        log.Printf(err)
    }
}

Best Answer

The main concern I have found with logging close to the source is that you are in danger of logging the same errors twice. In the second case we might imagine someone calling DoSomethingElse() without being sure whether it would log the error. These cases become increasingly common as the size and complexity of the component increases, and the number of times an error is logged scales with size.

When errors are logged multiple times in multiple places, the density of useful information in the logs decreases and so the value of those logs also decreases.

When programmers instead follow the first example and log from a higher level there is much less danger of logs being repeated. Furthermore, at higher levels logs can include much more system meaning. DoSomethingElse() may only be trying to send a SQL string to a Postgres database, while main is trying to purge all expired user accounts.

The logs from DoSomethingElse can only include very low level information -- the SQL query that was sent, the connection string tried, etc. -- while main can provide all of these (because the error was transmitted upwards) but also provide a high level description, e.g. "Could not connect to the database while attempting to purge stale accounts.".

These high level descriptions make logs more valuable by providing useful glosses on the errors encountered. So in general I would recommend logging from the highest level possible.