Safeguard
Developer Tools•

Log an error once: stop duplicate stack traces at every layer

Catch, log and rethrow at every layer floods logs with copies of one failure. Log at the boundary where you handle it, and add context as you pass it up.

Safeguard Security Team
3 min read

Log an error once, at the place that finally handles it, and add context as the error travels up instead of logging at each layer. Catch, log and rethrow in every function produces five copies of the same stack trace, inflates costs and makes error counts wrong.

The pattern to avoid

A request fails in the database layer. The repository logs the error and rethrows. The service logs it and rethrows. The controller logs it and returns a 500. One failure now has three log events, probably three stack traces, and an error count of three.

Two bad outcomes follow. Dashboards that count error lines overstate the problem, so alerts fire on noise. And the volume makes logs costly to store and slow to search.

The rule

Each error should be logged by whoever decides what to do about it. Lower layers either handle the error fully, or add context and pass it up without logging.

A simple test: if the code rethrows, it should not log. If it logs, it should not rethrow.

Add context on the way up

Before passing an error up, wrap it with details that only this layer knows:

  • which operation was attempted
  • relevant identifiers, such as order_id, not whole objects
  • the input category, not raw sensitive values

Most languages support wrapping with a cause chain. The final handler logs once, with the whole chain and a single stack trace.

Pick the right level

  • Error: the operation failed and a person may need to act, or the user got a failure.
  • Warn: something unexpected happened but the operation succeeded, such as a retry that worked.
  • Info: normal milestones worth recording.
  • Debug: detail for developers, off in production by default.

If an error is expected and handled, such as a user entering a bad value, it is not an error level event. Log it as info or as a validation event, and count it with a metric.

Avoid logging sensitive input with errors

Error messages often include the failing input. OWASP says to remove, mask or encrypt sensitive items such as passwords, tokens and keys. Check exception messages from libraries too, since some include connection strings or query text.

Count errors with metrics, not log lines

Increment a counter once at the boundary, with a small label such as error type. Alert on the counter ratio, not on a log search. That also protects you if someone changes a log message.

Retrofit plan

  1. Search for catch blocks that log and then throw.
  2. Remove the logging from inner layers, keeping the context wrapping.
  3. Make sure a top level handler logs every unhandled error exactly once.
  4. Compare daily error line counts before and after. A large drop with no change in user impact is a good sign.

Quick check

What about background jobs?

The job runner is the boundary. Let it log the failure once, with job ID and attempt number.

Is it ever fine to log and rethrow?

Rarely, such as logging a security relevant event that the caller cannot know about. Keep it deliberate.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.