java développer - Comment dois-je enregistrer les exceptions non interceptées dans mon service Web RESTful JAX-RS?



des services (5)

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?


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

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


Jersey (et JAX-RS 2.0) fournit ContainerResponseFilter (et ContainerResponseFilter dans JAX-RS 2.0 ).

L'utilisation du filtre de réponse Jersey version 1.x ressemblerait à

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

}

Le filtre doit être enregistré sur Jersey. Dans web.xml, le paramètre suivant doit être défini sur servlet Jersey:

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

De plus, l'entité devrait être tamponnée. Cela peut se faire de différentes manières: en utilisant la mise en mémoire tampon au niveau des servlets (comme l'a souligné Ashley Ross sur https://.com/a/17129256/356408 ) ou en utilisant ContainerRequestFilter .


import com.google.common.collect.Lists;

...


ArrayList<String> getSymbolsPresent = Lists.newArrayList("item 1", "item 2");

...




java rest jersey jackson glassfish-3