Publié par
Il y a 4 années · 8 minutes · Front

Logs d’audit pour les services REST

Pour une application hébergeant une API REST, il est souvent utile d’obtenir les logs des accès aux services. On pense tout de suite aux access logs qui fournissent facilement l’URL invoquée, la méthode et le code de retour HTTP. Dans certains cas cela suffira amplement ; si toutefois vous souhaitez pouvoir analyser finement la consommation des services par des tiers, les informations disponibles dans les logs du serveur d’application ne seront pas suffisantes.

Je vous propose donc de voir comment obtenir les requêtes complètes (header et body) ainsi que les réponses que votre application aura envoyées, via différentes solutions selon le framework utilisé. Dans cet article, nous nous intéresserons d’abord aux forces en présence sur des stacks assez répandues, puis vous découvrirez une méthode alternative qui peut s’appliquer à tous les frameworks reposant sur l’API Servlet.

Besoin

Nous souhaitons pouvoir dumper l’intégralité de la requête, headers et body compris, ainsi que l’intégralité de la réponse avec ses headers. Notre but est de pouvoir récupérer dans les logs une requête et de permettre de la rejouer facilement. Dernier point, nous voulons pouvoir corréler la requête et la réponse dans les logs.

Spring MVC 3.x

Spring fournit des filtres permettant de logger quelques informations de la requête :org.springframework.web.filter.AbstractRequestLoggingFilter. Les classes filles fournissent des implémentations pour commons-logging, log4j, et pour les logs du ServletContext.
En ajoutant ce filtre vous pourrez récupérer l’URL, les paramètres et le body de la requête entrante. Cette technique a l’avantage d’être fournie Out-Of-the-Box par Spring. Malheureusement ces filtres ne permettent pas de récupérer les headers à moins de faire sa propre implémentation et surtout, ils ne permettent pas de logger la réponse dont nous avons besoin.

CXF

CXF fournit des intercepteurs qui se chargent de logger l’intégralité de la requête et de la réponse (body et headers inclus). Si vous utilisez CXF, nul besoin d’aller chercher plus loin, vous avez ici tout ce que nous cherchons. Une remarque tout de même, l’interception des requêtes a lieu dans CXF ce qui peut causer des soucis si la stack est mal configurée.

La configuration des intercepteurs de log est décrite sur cette page : http://cxf.apache.org/docs/configuration.html, elle a l’avantage de pouvoir être appliquée de façon globale à tous les services exposés ou de façon spécialisée par service. Attention toutefois, cette solution n’est pas très adaptable, la seule configuration possible étant activé ou non. Il n’est, par exemple, pas possible de limiter la taille du body dumpé dans les logs, ni de choisir d’activer l’extraction des headers.

logback-access

Ce projet développé par l’équipe logback fournit une solution de remplacement des access logs Tomcat et Jetty. Avec logback access, nous allons pouvoir réaliser nos dumps directement via la configuration du serveur d’application. Attention, pour répondre précisément à notre besoin, logback-access nécessite en plus de la configuration du serveur, l’ajout dans la webapp d’un filtre : le TeeFilter, créant ainsi une adhérence forte entre le service d’infrastructure et l’applicatif.

Pour configurer logback-access dans Tomcat, commencez par ajouter les jars (logback-core et logback-access) dans le répertoire $TOMCAT_HOME/lib, puis vous devrez ajouter cette valve dans le fichier server.xml :

<Valve className="ch.qos.logback.access.tomcat.LogbackValve"/>

Vous pourrez ensuite configurer le format des access-logs dans un fichier logback-access.xml stocké au même endroit que le server.xml.

<configuration>
  <!-- always a good activate OnConsoleStatusListener -->
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%h %l %u %user %date "%r" %s %b</pattern>
   </encoder>
  </appender>

  <appender-ref ref="STDOUT" />
</configuration>

À ce point, vous pourrez tirer parti de logback-access en remplacement des access-log Tomcat. Toutefois, pour pouvoir obtenir les dumps des requêtes et réponses, il vous faudra ajouter le fameux TeeFilter dans votre web.xml:

<filter>
  <filter-name>TeeFilter</filter-name>
  <filter-class>ch.qos.logback.access.servlet.TeeFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>TeeFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Vous pourrez alors utiliser « %fullRequest %fullResponse dans votre pattern d’access-log pour obtenir les dumps tant désirés.

L’inconvénient de cette solution est son côté intrusif, nous n’avons pas toujours la main sur la configuration des tomcats de production, de plus, la modification à deux niveaux (configuration serveur + application) paraît lourde. Cela revient à déporter des logs applicatifs dans un service d’infrastructure. Personnellement, je ne suis pas favorable à cette démarche, car j’estime que ce type de logs doit dépendre uniquement de la configuration de l’application et non du serveur d’application.
Vous trouverez plus d’information sur le site de logback-access.

Jersey

N’étant personnellement pas coutumier de Jersey, mon avis est ici donné à titre purement informatif. Cher lecteur, n’hésite pas à apporter ton avis sur ce point comme sur les autres… Bref, Jersey fournit out of the box, un LoggingFilter qui peut-être utilisé pour logger les requêtes et les réponses de l’application (https://jersey.java.net/nonav/apidocs/1.8/jersey/com/sun/jersey/api/container/filter/LoggingFilter.html)

Il suffit d’ajouter ces lignes dans le web.xml, pour activer les logs qui sont envoyés sur le logger du JDK en niveau INFO.

     <init-param>
         <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
         <param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
     </init-param>
     <init-param>
         <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
         <param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
     </init-param>

Tout comme pour CXF, cette solution, peu personnalisable, doit être parfaitement adaptée à notre besoin.

xebia-logfilter-extras

Après avoir fait ce petit tour des solutions en présence, j’en suis arrivé à la conclusion que rien ne satisfaisait réellement mes besoins. N’étant jamais mieux servi que par soi-même, j’ai décidé d’écrire ma propre solution peu intrusive et surtout flexible. Son avantage ? Elle ne repose que sur l’API Servlet et peut donc être utilisée sur toutes les applications web en Servlet quel que soit le framework applicatif utilisé. La bibliothèque fournit une corrélation par ID, elle permet de logger indépendamment la requête et la réponse en activant à la demande l’écriture des headers. Ayant aimé le principe de Spring permettant de limiter la taille du dump, je l’ai aussi implémenté dans la librairie.

Le principe est le même que pour Spring, il s’agit d’un ServletFilter que l’on doit activer en matchant la ou les URLs des services que l’on souhaite pouvoir auditer. Pour cette solution, j’ai utilisé l’API SLF4J comme dépendance et une JVM 1.7.

Le projet est disponible sur le github de xebia https://github.com/xebia-france/xebia-logfilter-extras, vous pouvez directement l’utiliser via maven en ajoutant dans votre pom :

<dependency>
  <groupId>fr.xebia.extras</groupId>
  <artifactId>xebia-logfilter-extras</artifactId>
  <version>1.0.1</version>
</dependency>

Pour la configuration, côté web.xml :

<filter>
    <filter-name>requestLoggerFilter</filter-name>
    <filter-class>fr.xebia.extras.filters.logfilters.RequestLoggerFilter</filter-class>
    <init-param>
        <param-name>maxDumpSizeInKB</param-name>
        <param-value>500</param-value>
      </init-param>
  </filter>

  <!-- ... -->

  <filter-mapping>
      <filter-name>requestLoggerFilter</filter-name>
      <url-pattern>/rest/*</url-pattern>
  </filter-mapping>

Pour finir, il vous suffira de configurer les loggers comme suit, pour logback :

 <!--
 Dumping HTTP requests and response with their headers to the console appender
  using the RequestLoggerFilter
-->
    <logger name="RequestLoggerFilter.request" level="debug" additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <logger name="RequestLoggerFilter.response" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <logger name="RequestLoggerFilter.headers" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>

Attention, l’utilisation de ce type de log d’audit a un impact sur les performances, l’activation du niveau de log est donc à utiliser avec parcimonie en production.

Pour conclure, vous l’aurez compris, ma solution de prédilection est le xebia-logfilter-extras, particulièrement quand il s’agit d’une application Spring MVC. J’aurais tendance à l’appliquer dans tous les cas, car elle garantit que les dumps réalisés correspondent exactement à ce qui est échangé entre le client et le serveur, sans pour autant impliquer une modification importante de la configuration du serveur d’application. Je vous invite à tester et à me remonter vos impressions.

6 réflexions au sujet de « Logs d’audit pour les services REST »

  1. Publié par chris, Il y a 4 années

    Avant tout, comme je ne connais pas exactement le besoin auquel tu réponds, mon commentaire est forcément partiel.

    Je comprends l’intérêt de logger la requête (url+body) et les headers en prod (pour faire des stats par des tiers, pour rejouer, etc…).

    Autant la réponse je ne comprends pas : en prod on ne pourra généralemetn pas se permettre cet impact sur les perfs; en dev on a usuellement le debug intégré dans le client.

    D’autant que dans le fichier de log, la réponse et la requête seront potentiellement séparées de plusieurs lignes donc il faut aggréger tout ca avec les ids et ca se complique sérieusement.
    Et je ne parle pas des réponses binaires type fichier xls !

    Pour moi, si le besoin est réellement de tout logger, autant s’orienter sur des solutions type new relic qui vont logger tout ca et bien plus encore !
    Ou sinon, sur des solutions analytiques où on envoie sélectivement les mesures (ex: graphite)

    Comme je le dis en préambule, tout dépend du contexte !

  2. Publié par Rémi, Il y a 4 années

    Merci pour toutes ces pistes, c’est très utile.

  3. Publié par Waddle, Il y a 4 années

    +1 pour new relic s’il s’agit d’avoir des stats, info d’usage, etc.
    Si c’est pour de l’analyse en debug, je passerait tout simplement par wireshark (qui va relié les id, ce qui résout le problème soulevé par Chris) qui apportera en plus des filtres sur les requêtes, des conversations HTTP, etc.

  4. Publié par Séven Le Mesle, Il y a 4 années

    Merci pour ces commentaires, @chris, le contexte est assez simple, sur une application de gestion de commande, les injections de commande se font en REST + XML. J’ai besoin de garder les trames d’injection (aka les commandes) ainsi que les réponse (numéro de la commande / message de validation / …). Il s’agit typiquement des SLA pour lesquels nous devons pouvoir justifier très précisèment du comportement et par commande.
    Attention, je n’ai jamais voulu dumper les fichiers xls, le but est clairement de tracer les services XML et non les pages Web que ce soit avec CXF ou un autre filtre, l’avantage est que l’on peut choisir précisement la ou les urls à auditer.
    Je suis tout à fait d’accord, concernant le point sur les performances, il est important d’utiliser ce type de filtre avec parcimonie et c’est d’ailleurs la raison pour la quelle les log sont en niveau Debug.
    Pour votre point sur les requetes à recouper, c’est effectivement un mal nécessaire qui est d’ailleurs traité par toutes les solutions en place via la génération d’identifiant de corrélation systématique.

    Pour finir, @Waddle, si l’APM peut paraître comme une solution idéale, mais il n’est pas toujours possible d’en profiter sur son infrastructure ne serait-ce que par le coût des licences ou la nécessité d’ouvrir des flux vers l’extérieur pour la supervision. La même réponse s’applique d’ailleurs à Wrireshark ou tcpdump en production.
    Au final trouver la trace de l’appel complet qui a eu lieu en production dans les logs au lieu d’écouter les flux réseaux en attendant que cela se reproduise n’est pas idéal.

    Notez, tout de même que lorsque le niveau de log est inférieur à DEBUG, l’impact sur les performances est nulle. Evidement, ce n’est pas une solution magique mais elle est simple, pérenne et peu couteuse de mon point de vue.

    Bien sûr, si de meilleurs outils sont présent, surtout utilisez les …

  5. Publié par Anas, Il y a 4 années

    La problématique de performance en production peut être résolue par un logging asychrone, par exemple sur une file d’un MOM léger configuré pour une vitesse de réception plus performante que l’écriture sur un fichier de log. La problématique de corrélation est résolue de facto sans impacter les perf en agrégeant les messages de log « tranquillement » en aval de la file.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *