41

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

ただし、実際のプロジェクトはこの例ほど単純ではなく、この同じパターンを何度も実装する必要があり、Response を手動で作成する必要があることは言うまでもありません。

私は何をすべきか?

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

4

5 に答える 5

26

キャッチされていない 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://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();
    }
}
于 2013-10-30T10:46:20.013 に答える
9

アプローチ #1 は 1 つの問題を除いて完璧ですWebApplicationException。デフォルトのロジック (例: )を呼び出すか、特定のエラー状態に対して作成されたリソースの特定のロジックを実行できるため、WebApplicationException通過を妨げられないようにすることが重要です。NotFoundExceptionResponse

幸いなことに、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...
    }
}
于 2014-07-30T00:28:49.890 に答える
6

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

私が見つけた最良の答えは、 ジャージーでの@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");
    }

}
于 2015-11-05T04:54:15.013 に答える
2

それらはおそらくすでにログに記録されており、適切なロガーを見つけて有効にするために必要なすべてです。たとえば、Spring Boot + Jersey の下では、次の行を追加するだけですapplication.properties

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

于 2017-01-05T12:00:32.360 に答える