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

标签 java rest jersey jackson glassfish-3

我有一个使用 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;
    }
}

对于预期的异常,我抛出相应的 WebApplicationException ,如果发生意外异常,我对返回的 HTTP 500 状态感到满意。

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

徒劳的尝试

我尝试过使用 Thread.UncaughtExceptionHandler并且可以确认它是在方法体内部应用的,但它的 uncaughtException 方法永远不会被调用,因为在未捕获的异常到达我的处理程序之前有其他东西正在处理它们。

其他想法:#1

我看到有些人使用的另一个选项是 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 的用途,即将某些异常映射到某些响应。

其他想法:#2

大多数示例 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();
    }
}

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

我该怎么办?

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

最佳答案

由于缺乏更好的方法来实现未捕获的 JAX-RS 异常的日志记录,请使用包罗万象的 ExceptionMapperOther Ideas: #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();
    }
}

关于java - 我应该如何在我的 RESTful JAX-RS Web 服务中记录未捕获的异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19621653/

相关文章:

java - 将大列表转换为 json 字符串

api - ZF2 with PhlyRestfully The requested controller was unable to dispatch the request

java - Jersey POST 参数值始终为 null,但与 curl 命令配合使用效果良好

java - Intellij 生成的 WSDL 客户端代码

java - Clickatell SOAP wsdl 到 JAXB java 类

java - 使用 IntelliJ 将 .class 文件导入 .jsp 文件

python - 如何为 REST API 实现身份验证?

java - Jersey Rest Get 具有特殊特征

java - 创建类似于 WCF 的 JSON/XML REST Web 服务的最佳 Java 方式是什么?

java - 如何从 angularjs 和 CORS 支持将文件上传到 Jersey