当前位置: 首页 > 知识库问答 >
问题:

我应该如何在RESTful JAX-RS web服务中记录未捕获的异常?

蒯宇定
2023-03-14

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

@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;
    }
}

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

我尝试使用Thread.UnaughtExceptionHandler,并且可以确认它在方法主体中应用,但是它的UnaughtException方法从未被调用,因为在未捕获的异常到达我的处理程序之前,其他东西正在处理这些异常。

我看到一些人使用的另一个选项是ExceptionMapper,它捕获所有异常,然后筛选出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的作用,即将某些异常映射到某些响应。

大多数示例JAX-RS代码直接返回response对象。按照这种方法,我可以将代码更改为如下内容:

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();
    }
}

然而,我对这条路持谨慎态度,因为我的实际项目并不像这个例子那么简单,而且我必须一遍又一遍地实现相同的模式,更不用说必须手动构建响应了。

有更好的方法为未捕获的异常添加日志记录吗?有没有一个“正确”的实施方法?

共有1个答案

聂涛
2023-03-14

由于没有更好的方法来实现未捕获的JAX-RS异常的日志记录,因此使用catch-allexceptionmapper就像其他想法一样:#1似乎是添加该功能的最干净、最简单的方法

下面是我的实现:

@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:\n");
        message.append("URL: ").append(getOriginalURL(req)).append("\n");
        message.append("Method: ").append(req.getMethod()).append("\n");
        message.append("Entity: ").append(entity).append("\n");

        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();
    }
}
 类似资料:
  • 我在我们的一个开发环境中得到了以下异常。但是它在另一个环境中工作得很好。无法抓住窍门。有人能帮忙吗?

  • 我用的是spring-boot和JPA。我试图捕捉未检查的异常,如(违反约束)引发的事务。即使我添加了catch块,它也会在超出事务边界时抛出。 我谷歌了一下,发现可以通过事务回调来实现。我尝试了下面的代码:仍然是给出错误 请帮帮我!!

  • 这个问题不是关于Rollbar tho:)

  • 我有一个SQL语句,我希望它返回一行,因为我正在传递主键。所以我的选择是 将queryForObject包装为try/catch,捕获EmptyResultDataAccessException,并返回null 将调用更改为queryForList,然后展开列表,并(希望)返回第一个元素,或null 我在某个地方读到过,将EmptyResultDataAccessException作为cathin

  • 我目前在我的路由中使用dotry/doCatch块,因此我无法使用全局onException块。 然而,如果驼峰路由中断(由于错误代码或意外/未测试的场景),我希望执行一些业务逻辑。希望这永远不会发生,但我仍然想处理更糟糕的情况。 我不能在全局OneException块中有java.lang.Exception,而且,我不想在每个路由上都添加一个额外的捕获。 在抛出未捕获的异常和中断路由之前,是否

  • 我使用的是python 2.7和tornado 4.5 以下代码不起作用:除块不被触发。我不明白为什么? 相反,我可以捕获;但是我没有我如何调用的上下文。在我的例子中,更有意义的是引发一个较低级别的异常,并且调用者根据输入将其转换为人类可读的错误。 我是否只需要重构它来调用较低级别的gen.Task?那会很烦人:/