Error Handling and Logging in Web Contexts

Learn how to handle web errors and logging in Clojure applications, including exception mapping, structured logs, request correlation, and avoiding information leakage in HTTP responses.

Web error handling: The practice of turning application and infrastructure failures into predictable HTTP responses without hiding useful operational evidence or leaking sensitive internals.

In a Clojure web application, the real goal is not “never throw.” The goal is to map failures cleanly at the web boundary. Clients should get stable HTTP semantics. Operators should get enough structured logging and correlation data to debug the issue. Those are related goals, but they are not identical.

Separate Client Errors from Internal Errors

Good web error design distinguishes between:

  • invalid client requests
  • expected domain failures
  • unexpected internal faults

These categories should not all collapse into the same 500 response or the same vague log line. A validation failure, a permission denial, and a database outage deserve different response and logging behavior.

That distinction is easier to maintain when the application already has a clear error vocabulary. Domain error values, explicit exception types, or stable error codes make the web boundary far less ad hoc.

Choose One Boundary for Translation

A common failure mode is translating the same error at several layers:

  • handler catches and logs it
  • middleware catches and logs it again
  • outer wrapper maps it again

That creates duplicate noise and contradictory behavior. Usually the web layer should have one clear place where exceptions or domain errors become HTTP responses.

Map Exceptions Deliberately

One useful pattern is a boundary layer that translates exceptions or domain error values into stable HTTP responses:

1(defn error-response [status code message]
2  {:status status
3   :headers {"content-type" "application/json"}
4   :body {:error {:code code
5                  :message message}}})

The goal is not to serialize every stack trace. The goal is to keep the client contract stable while preserving richer evidence in logs and monitoring systems.

Stable error codes help here because they let the team connect:

  • client-visible failure semantics
  • logs and metrics
  • dashboards and alerting
  • support or operational triage

Logs Need Stable Structure

Structured logs matter because web traffic is high-volume and repetitive. Useful fields often include:

  • request ID
  • route or route template
  • status code
  • latency
  • user or subject identifier when appropriate
  • stable error code or exception class

Without those fields, web logs become difficult to aggregate or correlate under production load.

Severity discipline matters too. If every handled validation miss is logged like an outage, the signal collapses. Operationally useful logging distinguishes expected client failures from genuinely abnormal infrastructure or application faults.

Structured Logs Beat Ad Hoc Strings

Web logs become far more useful when they include stable fields such as:

  • request ID
  • route or endpoint
  • user or subject ID when appropriate
  • status code
  • latency
  • error class or error code

This makes it much easier to correlate a failing client request with the corresponding server-side evidence.

Request Correlation Should Be Routine

One of the best web-operability habits is attaching a request ID early and carrying it through:

  • request logs
  • response headers
  • downstream service calls
  • error records

Without correlation IDs, the logging story becomes much noisier as the application grows.

Log Once, at the Right Layer

Many systems log the same failure three or four times because each layer is “being safe.” Usually one authoritative log entry with strong context is better than several noisy ones with weaker context.

The same principle applies to exception handling. Catching at many layers often produces duplicated stack traces with less context each time. One good log event at the right layer usually beats four mediocre ones.

Do Not Leak Internals

A common web mistake is returning too much detail when something goes wrong:

  • stack traces
  • SQL snippets
  • internal hostnames
  • library or framework internals

Those details can be useful in logs, but they should not usually be exposed in client-visible error payloads.

This is especially important in mixed HTML and API systems where a stack trace intended for debugging can leak into a JSON or HTML response unexpectedly.

HTML and JSON boundaries may also need different user-facing error behavior even when the operational evidence is shared. A browser page might redirect or render an error view. An API client usually needs a stable error payload instead.

Common Failure Modes

This area goes bad when:

  • every failure becomes a 500
  • the app logs the same error repeatedly at many layers
  • structured context is missing from logs
  • client responses expose internal details

A clean design usually logs once at the right boundary and keeps the client-visible message appropriately limited.

Using Free-Form Error Strings as the Only Contract

If clients must parse English prose to decide what happened, the boundary is too loose. Stable codes and stable status semantics make the system easier to evolve.

Practical Heuristics

Translate failures at the HTTP boundary. Keep response contracts predictable. Log structured context with request correlation. Expose enough to help clients react correctly, but keep operational and security-sensitive detail in the server-side evidence stream instead of the response body.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026