java - syncinvoker - response readentity string class




RESTful JAX-RS Webサービスでキャッチされない例外をどのようにログに記録するのですか? (4)

私は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

いくつかの人が使用するのを見たもう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();
    }
}

しかし、私の実際のプロジェクトはこの例ほどシンプルではないので、このルートを検討するのは心配です。このパターンを繰り返し実装する必要があります。手動でレスポンスを構築する必要はありません。

私は何をすべきか?

キャッチされない例外のログを追加するためのより良い方法はありますか? これを実装する「正しい」方法はありますか?


ContainerResponseFilterが完全に変更されたため、受け入れられた回答はJersey 2では機能しません(またはコンパイルされている)。

私が見つけた最高の答えは、 Jerseyの@ Adrianの答えです...すべての例外をログに記録する方法はありますが、依然として RequestEventListenerを使用し、RequestEvent.Type.ON_EXCEPTIONに焦点を当てたExceptionMappersを呼び出します

しかし、私は以下の別の代替案を提供しました。これは、ここでは@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");
    }

}

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

}

フィルターはJerseyに登録する必要があります。 web.xmlでは、次のパラメータをJerseyサーブレットに設定する必要があります。

<init-param>
  <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
  <param-value>my.package.ExceptionsLoggingContainerResponseFilter</param-value>
</init-param>

さらに、エンティティをバッファリングする必要があります。 サーブレットレベルのバッファリング(Ashley Rossがhttps://.com/a/17129256/356408指摘したように)やContainerRequestFilterを使用してさまざまな方法で行うことができます。


キャッチされていないJAX-RS例外のログを実装するためのより良い方法がないため、キャッチオールExceptionMapper他のアイデアのように使用してください:#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://.com/a/17129256/356408
            InputStream is = req.getInputStream();
            // Read an InputStream elegantly: http://.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://.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 + Jerseyの下では、 application.properties行を追加するだけapplication.properties

logging.level.org.glassfish.jersey.server.ServerRuntime$Responder=TRACE





glassfish-3