Ash*_*oss 39 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;
}
}
Run Code Online (Sandbox Code Playgroud)
对于预期的异常,我抛出适当的WebApplicationException,我对发生意外异常时返回的HTTP 500状态感到满意.
我现在想为这些意外的异常添加日志记录,但是尽管搜索,但是无法找到我应该如何处理这个问题.
我已经尝试使用a Thread.UncaughtExceptionHandler并且可以确认它是在方法体内应用的,但是它的uncaughtException方法永远不会被调用,因为其他东西在它们到达我的处理程序之前处理未捕获的异常.
我见过一些人使用的另一个选项是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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这种方法可行,但我觉得滥用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();
}
}
Run Code Online (Sandbox Code Playgroud)
但是,我对走这条路线很谨慎,因为我的实际项目并不像这个例子那么简单,我必须一遍又一遍地实现这个相同的模式,更不用说必须手动建立响应.
是否有更好的方法为未捕获的异常添加日志记录?有没有"正确"的方式来实现这个?
Ash*_*oss 26
由于缺乏更好的方法来实现未捕获的JAX-RS异常的日志记录,使用ExceptionMapper像其他思想中的catch-all :#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();
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*nas 12
Jersey(和JAX-RS 2.0)提供ContainerResponseFilter(以及JAX-RS 2.0中的ContainerResponseFilter).
使用Jersey版本1.x响应过滤器看起来像
public class ExceptionsLoggingContainerResponseFilter implements ContainerResponseFilter {
private final static Logger LOGGER = LoggerFactory.getLogger(ExceptionsLoggingContainerResponseFilter.class);
@Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
Throwable throwable = response.getMappedThrowable();
if (throwable != null) {
LOGGER.info(buildErrorMessage(request), throwable);
}
return response;
}
private String buildErrorMessage(ContainerRequest request) {
StringBuilder message = new StringBuilder();
message.append("Uncaught REST API exception:\n");
message.append("URL: ").append(request.getRequestUri()).append("\n");
message.append("Method: ").append(request.getMethod()).append("\n");
message.append("Entity: ").append(extractDisplayableEntity(request)).append("\n");
return message.toString();
}
private String extractDisplayableEntity(ContainerRequest request) {
String entity = request.getEntity(String.class);
return entity.equals("") ? "(blank)" : entity;
}
}
Run Code Online (Sandbox Code Playgroud)
过滤器应该注册到泽西岛.在web.xml中,以下参数应设置为Jersey servlet:
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>my.package.ExceptionsLoggingContainerResponseFilter</param-value>
</init-param>
Run Code Online (Sandbox Code Playgroud)
此外,实体应该被缓冲.它可以通过各种方式完成:使用servlet级缓冲(如Ashley Ross指出/sf/answers/1199047951/)或使用ContainerRequestFilter.
方法#1是完美的,除了一个问题:你最终捕捉WebApplicationException.让WebApplicationException通过不受阻碍是很重要的,因为它将调用默认逻辑(例如NotFoundException),或者它可以携带Response为特定错误条件设计的特定资源.
幸运的是,如果您使用的是Jersey,则可以使用修改后的方法#1并实现ExtendedExceptionMapper.它从标准扩展ExceptionMapper到添加有条件地忽略某些类型的异常的能力.你可以WebApplicationException这样过滤掉:
@Provider
public class UncaughtThrowableExceptionMapper implements ExtendedExceptionMapper<Throwable> {
@Override
public boolean isMappable(Throwable throwable) {
// ignore these guys and let jersey handle them
return !(throwable instanceof WebApplicationException);
}
@Override
public Response toResponse(Throwable throwable) {
// your uncaught exception handling logic here...
}
}
Run Code Online (Sandbox Code Playgroud)
接受的答案在泽西岛2中不起作用(甚至编译),因为ContainerResponseFilter完全改变了.
我认为我找到的最佳答案是@Adrian在泽西岛的答案 ......如何记录所有异常,但仍调用ExceptionMappers,他使用RequestEventListener并专注于RequestEvent.Type.ON_EXCEPTION.
但是,我在下面提供了另一个替代方案,这是对@stevevls的回答.
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.ext.Provider;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.glassfish.jersey.spi.ExtendedExceptionMapper;
/**
* The purpose of this exception mapper is to log any exception that occurs.
* Contrary to the purpose of the interface it implements, it does not change or determine
* the response that is returned to the client.
* It does this by logging all exceptions passed to the isMappable and then always returning false.
*
*/
@Provider
public class LogAllExceptions implements ExtendedExceptionMapper<Throwable> {
private static final Logger logger = Logger.getLogger(LogAllExceptions.class);
@Override
public boolean isMappable(Throwable thro) {
/* Primarily, we don't want to log client errors (i.e. 400's) as an error. */
Level level = isServerError(thro) ? Level.ERROR : Level.INFO;
/* TODO add information about the request (using @Context). */
logger.log(level, "ThrowableLogger_ExceptionMapper logging error.", thro);
return false;
}
private boolean isServerError(Throwable thro) {
/* Note: We consider anything that is not an instance of WebApplicationException a server error. */
return thro instanceof WebApplicationException
&& isServerError((WebApplicationException)thro);
}
private boolean isServerError(WebApplicationException exc) {
return exc.getResponse().getStatusInfo().getFamily().equals(Family.SERVER_ERROR);
}
@Override
public Response toResponse(Throwable throwable) {
//assert false;
logger.fatal("ThrowableLogger_ExceptionMapper.toResponse: This should not have been called.");
throw new RuntimeException("This should not have been called");
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
23372 次 |
| 最近记录: |