web-dev-qa-db-fra.com

Comment enregistrer correctement les requêtes http avec Spring MVC

Bonjour, j'ai essayé de trouver un moyen générique de consigner les demandes http dans mon application, jusqu'à présent, pas de chance, voici comment je gère la journalisation en ce moment, à savoir:

@RequestMapping(value="register", method = RequestMethod.POST)
    @ResponseBody
    public String register(@RequestParam(value="param1",required=false) String param1, @RequestParam("param2") String param2, @RequestParam("param3") String param3, HttpServletRequest request){
        long start = System.currentTimeMillis();
        logger.info("!--REQUEST START--!");

        logger.info("Request URL: " + request.getRequestURL().toString());

        List<String> requestParameterNames = Collections.list((Enumeration<String>)request.getParameterNames());
        logger.info("Parameter number: " + requestParameterNames.size()); 

 for (String parameterName : requestParameterNames){
           logger.info("Parameter name: " + parameterName + " - Parameter value: " + request.getParameter(parameterName));
        }
                  //Some processing logic, call to the various services/methods with different parameters, response is always String(Json)
        String response = service.callSomeServiceMethods(param1,param2,param3);

logger.info("Response is: " + response);

        long end = System.currentTimeMillis();
        logger.info("Requested completed in: " + (end-start) + "ms");
        logger.info("!--REQUEST END--!");   

        return response;
    }

Donc, ce que je fais en ce moment pour différents contrôleurs/méthodes, c'est tout copier du début de l'intérieur de la méthode jusqu'à la logique de traitement qui diffère d'une méthode à l'autre, puis copier tout ce qui est en dessous de celui-ci comme indiqué dans le modèle ci-dessus.

C'est un peu désordonné et il y a beaucoup de répétition de code (que je n'aime pas). Mais je dois tout enregistrer.

Quelqu'un a-t-il plus d'expérience avec ce type de journalisation, quelqu'un peut-il nous éclairer à ce sujet?

38
London

Utilisez un intercepteur :

  • étendre HandlerInterceptorAdapter et remplacer preHandle
  • le définir avec <mvc:interceptors> dans dispatcher-servlet.xml

Il s'exécutera pour chaque demande.

26
Bozho

EDIT: Voir également le commentaire de @ membersound sur cette réponse, ce qui améliore cette réponse.

Le printemps soutient cela. Voir CommonsRequestLoggingFilter . Si vous utilisez Spring Boot, enregistrez simplement un bean de ce type et Boot l'appliquera à la chaîne de filtrage. Comme:

@Bean
public Filter logFilter() {
    CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
    filter.setIncludeQueryString(true);
    filter.setIncludePayload(true);
    filter.setMaxPayloadLength(5120);
    return filter;
}

En outre, ce filtre de journalisation nécessite que le niveau de journalisation soit défini sur DEBUG. Par exemple. faites ceci dans un logback.xml avec:

<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="DEBUG"/>
44
dgtc

Le principal problème avec la demande de lecture est que dès que le flux d'entrée est consommé, son disparu ... et ne peut plus être lu. Le flux d'entrée doit donc être mis en cache. Au lieu d'écrire vos propres classes pour la mise en cache (qui peuvent être trouvées à plusieurs endroits sur le Web), Spring fournit quelques classes utiles, à savoir ContentCachingRequestWrapper et ContentCachingResponseWrapper . Ces classes peuvent être utilisées très efficacement, par exemple, dans des filtres à des fins de journalisation.

Définissez un filtre dans web.xml:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Étant donné que le filtre est déclaré en tant que DelegatingFilterProxy, il peut être déclaré en tant que bean à l'aide d'annotations @Component ou @Bean. Dans la méthode doFilter de loggingFilter, encapsulez la demande et la réponse avec les classes fournies par spring avant de les transmettre à la chaîne de filtrage:

HttpServletRequest requestToCache = new ContentCachingRequestWrapper(request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper(response);
chain.doFilter(requestToCache, responseToCache);
String requestData = getRequestData(requestToCache);
String responseData = getResponseData(responseToCache);

Le flux d'entrée sera mis en cache dans la demande encapsulée dès que le flux d'entrée sera consommé après chain.doFilter (). Ensuite, il peut être consulté comme ci-dessous:

public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
    String payload = null;
    ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
        }
    }
    return payload;
}

Cependant, les choses sont un peu différentes pour la réponse. Comme la réponse a également été encapsulée avant de la transmettre à la chaîne de filtrage, elle sera également mise en cache dans le flux de sortie dès qu'elle est écrite sur le chemin du retour. Mais comme le flux de sortie sera également consommé, vous devez donc recopier la réponse dans le flux de sortie à l'aide de wrapper.copyBodyToResponse (). Voir ci-dessous:

public static String getResponseData(final HttpServletResponse response) throws IOException {
    String payload = null;
    ContentCachingResponseWrapper wrapper =
        WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
            wrapper.copyBodyToResponse();
        }
    }
    return payload;
}

J'espère que cela aide!

7
B. Ali

Voici une petite bibliothèque que j'ai écrite que vous pouvez utiliser: spring-mvc-logger

Je l'ai rendu disponible via maven central:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>
6
Israel Zalmanov

Ajout à ce que @ B.ALi a répondu. Si vous l'utilisez dans un scénario de gestion de demande asynchrone à ressort (serlvet 3.0 ou supérieur), le code suivant est ce qui a fonctionné pour moi.

public class OncePerRequestLoggingFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    boolean isFirstRequest = !isAsyncDispatch(request);
    HttpServletRequest requestToUse = request;
    HttpServletResponse responseToUse = response;

    // The below check is critical and if not there, then the request/response gets corrupted.
    // Probably because in async case the filter is invoked multiple times.
    if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
        requestToUse = new ContentCachingRequestWrapper(request);
    }

    if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
        responseToUse = new ContentCachingResponseWrapper(response);
    }

    filterChain.doFilter(requestToUse, responseToUse);

    if (!isAsyncStarted(request)) {
        ContentCachingResponseWrapper responseWrapper =
                WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        responseWrapper.copyBodyToResponse(); // IMPORTANT to copy it back to response
    }
}

@Override
protected boolean shouldNotFilterAsyncDispatch() {
    return false; // IMPORTANT this is true by default and wont work in async scenario.
}

}

2
Abe

Comme toute réponse technique ... cela dépend ... de la pile technologique que vous utilisez et de vos besoins.

par exemple, plus vous voulez rendre votre journalisation générique, plus vous voudrez le faire d'avance. dans votre cas, vous enregistrez uniquement les demandes dont la journalisation est activée et traitées dans le contexte de Spring. Vous pourriez donc "manquer" d'autres demandes.

Je regarderais le conteneur ou le serveur Web que vous utilisez pour exécuter votre application. Cela supprimera cette dépendance au printemps. De plus, les conteneurs vous offrent la possibilité de connecter un fournisseur de journalisation, puis de configurer le format du code en dehors du journal. Par exemple, si vous utilisez le serveur Web Apache, utilisez la journalisation du serveur Web Apache pour consigner toutes les demandes HTTP dans la couche de journalisation des accès. Mais soyez prudent, certaines des options de journalisation comportent des pénalités de performances. Enregistrez uniquement ce dont vous avez vraiment besoin pour une perspective de surveillance des modèles d'accès.

Si vous utilisez Tomcat, Tomcat vous permettra également d'enregistrer des éléments. Recherchez Access Valve dans la documentation Tomcat pour le Tomcat que vous utilisez. Cela ouvrira un monde de possibilités.

Une journalisation plus étendue devrait être le domaine de la stratégie d'exception, c'est-à-dire le type de détails que vous souhaitez voir lorsqu'un problème se produit dans le système.

1
Master Chief