I would like to back to issue that Tomcat servlet logs unhandled exceptions without trace/span id.

When unhandled exception reaches Tomcat servlet, standard log entry with level ERROR is logged.

I would like to customize the log message. Typical demand is trace/span id, let say I want to see also request path. Let's name it context. It is sensible: when I see ERROR log entry, I would like to see collected all clues in one place instead of start scan logs and collect myself.

What could I do: 1. I could add

@ExceptionHandler void handleException(Exception ex, HttpServletResponse response) throws IOException {
  log(context);
  response.sendError(500);
}

Problem: Handler takes ResponseStatusException because ExceptionHandlerExceptionResolver is before ResponseStatusExceptionResolver and does not honor ResponseStatusException code

1a. I could add dedicated implementation for ResponseStatusException Problem: It does not have a sense to reimplement Spring code in custom code

1b. I could extend ResponseEntityExceptionHandler, part of Problem Details implementation. The handler is facing similar problem and has dedicated implementation for ResponseStatusException Problem: It adds Problem Details format for ResponseStatusException.

  1. I could implements HandlerExceptionResolver with
@Override public ModelAndView resolveException(
  HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
  try {
    log(context);
    response.sendError(500, ex.getMessage());
    return new ModelAndView();
  } catch (IOException e) {
    return null;
  }
}

It works. Problem: Overloaded, unclerar, old-fashion API

Conclusion: When Spring code catches unhandled exception, it should not buble it up to Tomcat Servlet. It is known that Tomcat Servlet will log meager error and pass to error controller with code 500. It is typical expectancy that error log message is rich with plenty details. For that reason I think that Spring log should: 1. Log exception itself and call response.sendRedirect() to make Tomcat NOT log the exception 2. Open API to easy customizing exception logging by developers.

However the expectancy can be satisfied with custom HandlerExceptionResolver, I think it should be more straightforward. Althought I'm very experienced with Spring MVC error handling, it took me few hours to invent solution (2).

I propose the following: a) Add a dedicated, customizable HandlerExceptionResolver as last exception resolver in chain b) Additionally ExceptionHandlerExceptionResolver should have same policy for standard Spring exceptions like ResponseEntityExceptionHandler

Of course I can't exclude there is much simpler solution 3...

Comment From: bclozel

I would like to back to issue that Tomcat servlet logs unhandled exceptions without trace/span id.

When unhandled exception reaches Tomcat servlet, standard log entry with level ERROR is logged.

I would like to customize the log message. Typical demand is trace/span id, let say I want to see also request path. Let's name it context. It is sensible: when I see ERROR log entry, I would like to see collected all clues in one place instead of start scan logs and collect myself.

I think you are referring to #29398. This approach and its limitations are well explained in the MVC section of the observability instrumentation.

If I understand correctly, the main problem you would like to solve is that exceptions that were not caught by Spring should be logged by Tomcat in a different fashion and should contain the observation information.

This log statement is typically logged by Tomcat's org.apache.catalina.core.StandardWrapperValve. If the message doesn't fit your needs, I think you can customize it by configuring Tomcat message properties. Of course this wouldn't solve the fact that the trace id is not present. Because this would be Tomcat-specific, I believe disabling the Observation Servlet filter and using a custom Tomcat Valve would be the best option. This is out of scope for Spring Framework, see https://github.com/micrometer-metrics/micrometer/pull/3989 for a potential solution.

Design 1) and 2) won't solve your problem completely as they'll only apply to Spring MVC controller handlers, so this won't apply to resource handling errors or filter errors.

Conclusion: When Spring code catches unhandled exception, it should not buble it up to Tomcat Servlet. It is known that Tomcat Servlet will log meager error and pass to error controller with code 500. It is typical expectancy that error log message is rich with plenty details. For that reason I think that Spring log should:

  1. Log exception itself and call response.sendRedirect() to make Tomcat NOT log the exception
  2. Open API to easy customizing exception logging by developers.

If we response.sendRedirect(), this will send an HTTP redirect response to the client, targeting a different page. Redirecting clients to the error page won't work as it won't be an ERROR dispatch anymore and error details will be lost.

However the expectancy can be satisfied with custom HandlerExceptionResolver, I think it should be more straightforward. Althought I'm very experienced with Spring MVC error handling, it took me few hours to invent solution (2).

I propose the following: a) Add a dedicated, customizable HandlerExceptionResolver as last exception resolver in chain b) Additionally ExceptionHandlerExceptionResolver should have same policy for standard Spring exceptions like ResponseEntityExceptionHandler

Again, this will only work for controller handlers and won't globally solve your problem. If another exception handler resolves and handles the actual exception, this would log the exception as an error whereas the application gracefully dealt with it and this won't even appear as an error in the observations. This is also pointed out in the reference docs.

Another way to solve this problem would be to configure a custom Servlet filter that would do just that, as long as it's ordered right after the observation filter. In a Spring Boot application, this would look like this:

@Configuration
public class WebConfiguration {

    @Bean
    FilterRegistrationBean<MyLoggingFilter> myLoggingFilter() {
        FilterRegistrationBean<MyLoggingFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new MyLoggingFilter());
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
        registration.addUrlPatterns("/*");
        return registration;
    }

    static class MyLoggingFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            try {
                filterChain.doFilter(servletRequest, servletResponse);
            }
            catch (Throwable ex) {
                LOGGER.info("from logging filter", ex);
                throw ex;
            }
        }
    }

}

We could consider adding a protected method void onError(Throwable error) on the org.springframework.web.filter.ServerHttpObservationFilter, but I'm not sure overriding this class and configuring it in the application is easier than a custom independent filter.

I'm closing this issue as I don't think there's much to be done in Spring Framework to improve this. Thanks for your report!

Comment From: michaldo

Let me clarify the issue

1. What is a problem and what is expectation?

Any unhandled exception raised by controller is bubbled to tomcat/jetty/undertow. Each server logs is according own rules (tomcat, undertow: level ERROR, jetty: level WARN). There rules are not easy to customize or enriched.

For error management it is vital to have straight access to trace id or anything developer decides: headers, request params, MDC etc.

My expectation is that a) unhandled exceptions are logged by Spring b) logging is configurable c) unhandled exceptions are not logged by webserver (i want unhandled exception from controller be "handled" and logged once not twice)

You noticed that my expectation is modest, because filters and resource exceptions are ignored. Indeed, I should be brave and ask for consistent processing all unhandled exceptions. But I'm modest and I ask only for controller exceptions. There are 2 reasons: a) controller exceptions are majority of cases b) I know that solution for controllers is at fingerprint, but must be done wisely, to not break existing pattern

2. Implementation sketch

DispatcherServlet has 2 handlerExceptionResolvers - DefaultErrorAttributes, which remembers exception in request attributes - composite resolvers which attempts to handle exception -- ExceptionHandlerExceptionResolver for @ExceptionHandler methods -- ResponseStatusExceptionResolver for ResponseStatusException or exception annotated by @ResponseStatus -- DefaultHandlerExceptionResolver

My proposal id add to composite 4th resolver, which logs exception in customizable way and calls response.sendError(500) (not ~sendRedirect~ - it was mistake)

That code will reproduce webserver behavior. Webserver will not longer log error message, because exception became "handled"

Comment From: bclozel

Thanks for clarifying. Unfortunately we cannot apply this behavior broadly because this would break most developers' expectations when it comes to deploying an application in a Servlet container.

What you are describing can easily be implemented by a Servlet filter as suggested before.

Comment From: michaldo

Filter like MyLoggingFilter is not a solution. I need a solution for exception thrown by controller and catch by org.apache.catalina.core.StandardWrapperValve, thus something what does not leave filterChain.doFilter(servletRequest, servletResponse); Then 1. The exception is already logged by StandardWrapperValve, I don't want log same exception twice 2. I could search for exception in request attribute, but the exception could be not unhandled exception but something like ResponseStatusException, which is already handled and does not need a special attention.

I don't know what are most developers expectations about unhandled exceptions. But I know these expectations: https://github.com/spring-projects/spring-framework/issues/33667 https://github.com/spring-projects/spring-framework/issues/30675 (and more linked in https://github.com/spring-projects/spring-framework/issues/29398)

With 4th resolver, developers experience would be very similar to current: unhandled exception will be logged like by StandardWrapperValve but by 4thResolver and it would be customizable

If you don't want modify current behavior, maybe 4thResolver should be implemented in Spring Framework and opt-in in Spring Boot?

Comment From: bclozel

Thanks for the feedback. If I understand correctly, you don't want unhandled exceptions to reach the servlet container at all and you would like to log a custom message containing the trace id. I agree that in this case, this filter would not solve the problem as the exception would still bubble up to the Servlet container.

As for https://github.com/spring-projects/spring-framework/issues/33667 and https://github.com/spring-projects/spring-framework/issues/30675, I understand that developers expect to get the trace id information for all errors, but they would also expect Spring Framework to provide a consistent support and experience (including for all Servlet containers that we support). This is why we can only instrument at the Servlet filter level as far as I understand. It is possible to instrument at a lower level with server-specific instrumentations and I believe this could be covered by Micrometer instrumentations.

With 4th resolver, developers experience would be very similar to current: unhandled exception will be logged like by StandardWrapperValve but by 4thResolver and it would be customizable If you don't want modify current behavior, maybe 4thResolver should be implemented in Spring Framework and opt-in in Spring Boot?

I have tried this approach by first extending DispatcherServlet and implementing the behavior that you are requesting. Unfortunately, this breaks the Spring Framework test suite and Spring Boot test suite. This breaks large parts of error handling features.

But of course, we could just introduce this 4th resolver and make this behavior up to the developer implementing that resolver. Maybe a contract quite similar to HandlerExceptionResolver. And developers would always catch the exception, log the message, calling sendError and returning a fully resolved error view. This of course would break further Spring Boot error handling like shown in the test suite results, but that would be completely opt-in.

Enter: ✨Servlet Filters✨ Servlet Filters are well supported by all Servlet containers and have low overhead when well implemented. They can be used right away in all existing Spring versions!

The key is that it's even easier to implement the behavior you are requesting because you wouldn't need to return an error ModelAndView to signal that the error is properly handled, unlike exception resolvers.

Throwing an exception from a controller and not handling it yields the following right now in Spring Boot:

2025-06-09T12:54:33.103+02:00 ERROR 28360 --- [gh-34975] [nio-8080-exec-1] [                                                 ] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalStateException: test error] with root cause

java.lang.IllegalStateException: test error

With a slightly improved Servlet filter, we get:

2025-06-09T12:55:13.561+02:00 ERROR 28480 --- [gh-34975] [nio-8080-exec-1] [6846bd9157258219cfc2634c4221d4a2-cfc2634c4221d4a2] com.example.gh34975.MyLoggingFilter      : CUSTOM LOGGING MESSAGE

jakarta.servlet.ServletException: Request processing failed: java.lang.IllegalStateException: test error
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022) ~[spring-webmvc-6.2.7.jar:6.2.7]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.7.jar:6.2.7]

This is exactly the behavior you are looking for!

public class MyLoggingFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(MyLoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        }
        catch (Throwable exception) {
            logger.error("CUSTOM LOGGING MESSAGE", exception);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}

So considering the two solutions, I am leaning towards the Servlet Filter one because:

  1. adding another exception resolver would make the design more complex
  2. this new resolver might solve your problem, but also would push developers towards a "catch-all" solution like this that would break other well-needed features
  3. because this happens at the Servlet Filter level, this not only catches Spring MVC errors, but possibly errors throwh by other Servlets or other Filters. 100% of errors are caught, which would not be the case with another resolver.
  4. This is available and usable TODAY!
  5. For your case, the implementation is simpler than a custom resolver implementation. And easier to test!

Thanks for the discussion, I think we're reaching a place where this gives better guidance for developers looking for similar behavior.

Comment From: michaldo

Thank you for your engagement, the servlet filter is exactly something I want. I was impacted by previous version, which thrown exception in catch section and I blindly decided filter is not a solution. But filter which calls response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); in catch section is something I want And event better, because handles also other filter errors