我应该如何在我的 RESTful JAX-RS Web 服务中记录未捕获的异常? [英] How should I log uncaught exceptions in my RESTful JAX-RS web service?

查看:28
本文介绍了我应该如何在我的 RESTful JAX-RS Web 服务中记录未捕获的异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用 Jersey 和 Jackson 在 Glassfish 3.1.2 下运行的 RESTful Web 服务:

I have a RESTful web service running under Glassfish 3.1.2 using Jersey and Jackson:

@Stateless
@LocalBean
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("users")
public class UserRestService {
    private static final Logger log = ...;

    @GET
    @Path("{userId:[0-9]+}")
    public User getUser(@PathParam("userId") Long userId) {
        User user;

        user = loadUserByIdAndThrowApplicableWebApplicationExceptionIfNotFound(userId);

        return user;
    }
}

对于预期的异常,我抛出适当的WebApplicationException,我对发生意外异常时返回的 HTTP 500 状态感到满意.

For expected exceptions, I throw the appropriate WebApplicationException, and I'm happy with the HTTP 500 status that is returned if an unexpected exception occurs.

我现在想为这些意外的异常添加日志记录,但尽管进行了搜索,还是找不到我应该如何处理这个问题.

I would now like to add logging for these unexpected exceptions, but despite searching, cannot find out how I should be going about this.

我尝试使用 Thread.UncaughtExceptionHandler 并且可以确认它被应用到方法体内,但是它的 uncaughtException 方法永远不会被调用,因为在它们到达我的处理程序.

I have tried using a Thread.UncaughtExceptionHandler and can confirm that it is applied inside the method body, but its uncaughtException method is never called, as something else is handling the uncaught exceptions before they reach my handler.

我见过一些人使用的另一个选项是 ExceptionMapper,它捕获所有异常,然后过滤掉 WebApplicationExceptions:

Another option I've seen some people use is an ExceptionMapper, which catches all exceptions and then filters out WebApplicationExceptions:

@Provider
public class ExampleExceptionMapper implements ExceptionMapper<Throwable> {
    private static final Logger log = ...;

    public Response toResponse(Throwable t) {
        if (t instanceof WebApplicationException) {
            return ((WebApplicationException)t).getResponse();
        } else {
            log.error("Uncaught exception thrown by REST service", t);

            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                   // Add an entity, etc.
                   .build();
        }
    }
}

虽然这种方法可能有效,但我感觉像是滥用了 ExceptionMappers 的用途,即将某些异常映射到某些响应.

While this approach may work, it feels to me like misuse of what ExceptionMappers are supposed to be used for, that is, mapping certain exceptions to certain responses.

大多数示例 JAX-RS 代码返回 Response 对象直接.按照这种方法,我可以将代码更改为:

Most sample JAX-RS code returns the Response object directly. Following this approach, I could change my code to something like:

public Response getUser(@PathParam("userId") Long userId) {
    try {
        User user;

        user = loadUserByIdAndThrowApplicableWebApplicationExceptionIfNotFound(userId);

        return Response.ok().entity(user).build();
    } catch (Throwable t) {
        return processException(t);
    }
}

private Response processException(Throwable t) {
    if (t instanceof WebApplicationException) {
        return ((WebApplicationException)t).getResponse();
    } else {
        log.error("Uncaught exception thrown by REST service", t);

        return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
               // Add an entity, etc.
               .build();
    }
}

然而,我对走这条路持怀疑态度,因为我的实际项目不像这个例子那么简单,我必须一遍又一遍地实现同样的模式,更不用说必须手动构建响应.

However, I'm leery of going this route, as my actual project is not as simple as this example, and I would have to implement this same pattern over and over again, not to mention having to manually build up the Responses.

是否有更好的方法来为未捕获的异常添加日志记录?有没有一种正确"的方式来实现这一点?

Are there better methods for adding logging for uncaught exceptions? Is there a "right" way of implementing this?

推荐答案

由于缺乏更好的方法来实现未捕获的 JAX-RS 异常的日志记录,使用 catch-all ExceptionMapper其他想法:#1 似乎是添加此功能的最简洁、最简单的方法.

For lack of a better way to implement logging for uncaught JAX-RS exceptions, using a catch-all ExceptionMapper as in Other Ideas: #1 seems to be the cleanest, simplest way to add this functionality.

这是我的实现:

@Provider
public class ThrowableExceptionMapper implements ExceptionMapper<Throwable> {

    private static final Logger log = Logger.getLogger(ThrowableExceptionMapper.class);
    @Context
    HttpServletRequest request;

    @Override
    public Response toResponse(Throwable t) {
        if (t instanceof WebApplicationException) {
            return ((WebApplicationException) t).getResponse();
        } else {
            String errorMessage = buildErrorMessage(request);
            log.error(errorMessage, t);
            return Response.serverError().entity("").build();
        }
    }

    private String buildErrorMessage(HttpServletRequest req) {
        StringBuilder message = new StringBuilder();
        String entity = "(empty)";

        try {
            // How to cache getInputStream: http://stackoverflow.com/a/17129256/356408
            InputStream is = req.getInputStream();
            // Read an InputStream elegantly: http://stackoverflow.com/a/5445161/356408
            Scanner s = new Scanner(is, "UTF-8").useDelimiter("\A");
            entity = s.hasNext() ? s.next() : entity;
        } catch (Exception ex) {
            // Ignore exceptions around getting the entity
        }

        message.append("Uncaught REST API exception:
");
        message.append("URL: ").append(getOriginalURL(req)).append("
");
        message.append("Method: ").append(req.getMethod()).append("
");
        message.append("Entity: ").append(entity).append("
");

        return message.toString();
    }

    private String getOriginalURL(HttpServletRequest req) {
        // Rebuild the original request URL: http://stackoverflow.com/a/5212336/356408
        String scheme = req.getScheme();             // http
        String serverName = req.getServerName();     // hostname.com
        int serverPort = req.getServerPort();        // 80
        String contextPath = req.getContextPath();   // /mywebapp
        String servletPath = req.getServletPath();   // /servlet/MyServlet
        String pathInfo = req.getPathInfo();         // /a/b;c=123
        String queryString = req.getQueryString();   // d=789

        // Reconstruct original requesting URL
        StringBuilder url = new StringBuilder();
        url.append(scheme).append("://").append(serverName);

        if (serverPort != 80 && serverPort != 443) {
            url.append(":").append(serverPort);
        }

        url.append(contextPath).append(servletPath);

        if (pathInfo != null) {
            url.append(pathInfo);
        }

        if (queryString != null) {
            url.append("?").append(queryString);
        }

        return url.toString();
    }
}

这篇关于我应该如何在我的 RESTful JAX-RS Web 服务中记录未捕获的异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆