tutorial - web service rest java+eclipse




Comment dois-je enregistrer les exceptions non interceptées dans mon service Web RESTful JAX-RS? (4)

En l'absence d'un meilleur moyen d'implémenter la journalisation des exceptions JAX-RS non interceptées, l'utilisation d'un ExceptionMapper attrape-tout comme dans Autres idées: # 1 semble être le moyen le plus propre et le plus simple d'ajouter cette fonctionnalité.

Voici ma mise en œuvre:

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

J'ai un service web RESTful fonctionnant sous Glassfish 3.1.2 en utilisant Jersey et Jackson:

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

Pour les exceptions attendues, je lance l'exception WebApplicationException appropriée et je suis satisfait de l'état HTTP 500 renvoyé si une exception inattendue se produit.

Je voudrais maintenant ajouter la journalisation de ces exceptions inattendues, mais malgré la recherche, je ne peux pas trouver comment je devrais aller à ce sujet.

Tentative infructueuse

J'ai essayé d'utiliser un Thread.UncaughtExceptionHandler et peux confirmer qu'il est appliqué dans le corps de la méthode, mais sa méthode uncaughtException n'est jamais appelée, car quelque chose d'autre gère les exceptions non interceptées avant qu'elles n'atteignent mon gestionnaire.

Autres idées: # 1

Une autre option que certains utilisateurs utilisent est un ExceptionMapper , qui récupère toutes les exceptions et filtre les 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();
        }
    }
}

Bien que cette approche puisse fonctionner, elle me semble être une mauvaise utilisation de ce à quoi ExceptionMappers est censé être utilisé, c'est-à-dire mapper certaines exceptions à certaines réponses.

Autres idées: # 2

La plupart des exemples de code JAX-RS renvoie directement l'objet Response . Suivant cette approche, je pourrais changer mon code pour quelque chose comme:

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

Cependant, je me méfie de cette route, car mon projet actuel n'est pas aussi simple que cet exemple, et je devrais implémenter ce même modèle encore et encore, sans parler de devoir construire manuellement les réponses.

Que devrais-je faire?

Existe-t-il de meilleures méthodes pour ajouter la journalisation pour les exceptions non interceptées? Y a-t-il une "bonne" façon de mettre en œuvre cela?


Ils sont probablement déjà connectés, tout ce dont vous avez besoin pour trouver et activer un enregistreur approprié. Par exemple, sous Spring Boot + Jersey, tout ce dont vous avez besoin est d'ajouter une ligne à application.properties :

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


L'approche n ° 1 est parfaite sauf pour un problème: vous WebApplicationException par attraper WebApplicationException . Il est important de laisser passer WebApplicationException sans entrave, car il invoquera la logique par défaut (par exemple, NotFoundException ) ou il pourra transmettre une Response spécifique que la ressource a créée pour une condition d'erreur particulière.

Heureusement, si vous utilisez Jersey, vous pouvez utiliser une approche modifiée # 1 et implémenter ExtendedExceptionMapper . Il s'étend de l' ExceptionMapper standard pour ajouter la possibilité d'ignorer de manière conditionnelle certains types d'exceptions. Vous pouvez ainsi filtrer WebApplicationException comme ceci:

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

La réponse acceptée ne fonctionne pas (ou même compile) dans Jersey 2 parce que ContainerResponseFilter a été totalement changé.

Je pense que la meilleure réponse que j'ai trouvée est la réponse de @ Adrian dans Jersey ... comment enregistrer toutes les exceptions, mais toujours invoquer ExceptionMappers où il a utilisé un RequestEventListener et concentré sur RequestEvent.Type.ON_EXCEPTION.

Cependant, j'ai fourni une autre alternative ci-dessous qui est un spin sur @stevevls réponse ici.

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

}




glassfish-3