Dans les architectures serverless l’utilisation conjointe des services Lambda et API Gateway permet d’exposer, de gérer et de sécuriser des API Rest. Dans un précédent article j’ai expliqué pourquoi écrire des logs au format json pour les fonctions Lambda. Aujourd’hui on explore ce même sujet mais sous l’angle des access logs d’API Gateway.

Le jour où j’ai eu besoin de ces logs

Il y a quelque temps j’ai été confronté à un problème avec une API déployée sur AWS. Elle fonctionnait parfaitement, avec plusieurs milliers de requêtes par jour. Pourtant, il arrivait parfois qu’une ou deux requêtes échouent. Pas tous les jours. Pas à la meme heure. Sans raison. Nous avons donc enfilé nos costumes de spéléologues et avons tenté de trouver une explication. Nous avions des logs de Lambda bien configuré mais le problème ne venait pas de là.

Nous manquions cruellement d’observabilité sur l’API en elle-même. Je suis rapidement tombé sur un article de l’excellent blog de Alex Debrie expliquant comment configurer les access logs pour API Gateway. Entre-temps j’avais aussi sollicité le support technique d’AWS, qui lui aussi m’a recommandé d’ajouter la configuration des access logs.

C’est quoi les access logs ?

Un log d’accès, ou access log en anglais, est un fichier de log contenant une ligne pour chaque requête reçue par une instance d’API Gateway. Cette ligne unique est en quelque sorte un résumé de la requête, permettant de répondre aux principales questions que l’on va se poser en débuggant. Qu’est-ce qui a été demandé ? À quelle heure ? Quelle a été la latence ? Quel verbe HTTP a été utilisé ?

Il ne faut pas confondre les logs d’accès avec les logs d’exécution. Les logs d’accès contiennent une ligne unique par requête. Les logs d’exécution sont des logs détaillés sur le fonctionnement interne d’API Gateway. Jusqu’à 30 lignes peuvent être écrites dans ces logs pour chaque requête. Comme je l’indiquais dans mon article sur le coût des logs Cloudwatch, cela peut vite s’avérer onéreux !

Comment ça se configure ?

La première possibilité est de passer par la console AWS. Une fois rendu dans la page concernant API Gateway il faut cliquer sur Stages puis sélectionner la stage que l’on souhaite configurer.

Configuration via la console AWS

Configuration via la console AWS

La première zone de l’écran concerne les logs d’exécution. La zone qui nous intéresse, Custom Access Logging, est située juste en dessous. Il est ainsi possible d’activer les access logs, de choisir la destination de ces logs et enfin de définir le format des logs. Plusieurs formats prédéfinis sont disponibles : CLF, XML ou encore CSV. Mais comme je l’expliquais dans mon article sur les logs au format JSON, il est préférable de choisir JSON.

Quels champs inclure ? Cela va dépendre de l’architecture choisie, par exemple si un custom authorizer a été configuré. Le plus simple est de se référer au très bon article de Alex Debrie, le lien se trouve plus bas.

La console peut-être pratique à utiliser, mais j’essaie de l’utiliser le moins possible. Je me sers plutôt d’un outil d’Infrastructure as Code, généralement SAM quand il s’agit d’une application serverless. Comment configurer les access logs dans un template ? Il faudra déclarer un log group, configurer la section AccessLogSetting pour l’API et enfin référencer le RestApiId dans la déclaration de la lambda fonction.

  AccessLoggedApi: 
    Type: AWS::Serverless::Api 
    Properties: 
      StageName: Staging
      AccessLogSetting:
        DestinationArn: !GetAtt AccessLogGroup.Arn
        Format: '{"requestTime":"$context.requestTime","requestId":"$context.requestId","httpMethod":"$context.httpMethod","path":"$context.path","resourcePath":"$context.resourcePath","status":$context.status,"responseLatency":$context.responseLatency,"xrayTraceId":"$context.xrayTraceId","integrationRequestId":"$context.integration.requestId","functionResponseStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent"}'

  AccessLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
          RetentionInDays: 3
          LogGroupName: !Join ['', ['/aws/apigateway/', !Ref AWS::StackName]]

Comment ça se consulte ?

Comme pour les autres logs gérés par CloudWatch, il existe plusieurs moyens de les consulter. Il est ainsi possible d’utiliser la CLI ou la console pour soit lire un log group en particulier soit utiliser Log Insights pour exécuter des requêtes plus complexes.

Avec la CLI vous pouvez par exemple lancer cette commande :

aws logs get-log-events --log-group-name "/aws/apigateway/helloworld-golang" \
  --log-stream-name "aa5e2f586e1912599848e7a90f92e359" --region eu-west-3 

Ce qui retournera un résultat similaire à cela :

{
  "events": [
    {
      "timestamp": 1646388907951,
      "message": "{\"requestTime\":\"04/Mar/2022:10:15:07 +0000\",\"requestId\":\"8bdba9f9-0076-4a4c-bfcb-4ae15355d3e2\",\"httpMethod\":\"GET\",\"path\":\"/Staging/hello\",\"resourcePath\":\"/hello\",\"status\":200,\"responseLatency\":42,\"xrayTraceId\":\"-\",\"integrationRequestId\":\"b6c1e2b5-6f43-459d-86fb-4996dd36ac63\",\"functionResponseStatus\":\"200\",\"integrationLatency\":\"38\",\"integrationServiceStatus\":\"200\",\"ip\":\"123.214.133.72\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36\"}",
      "ingestionTime": 1646388928058
    }
  ],
  "nextForwardToken": "f/36715699533902339325814882200223149555622177707366219776/s",
  "nextBackwardToken": "b/36715699533902339325814882200223149555622177707366219776/s"
}

En passant par la console, vous pouvez obtenir un résultat similaire :

Access logs via la console AWS

Access logs via la console AWS

Il est intéressant de noter la présence de l’attribut integrationRequestId, ceci correspond au request ID qu’il faut logger pour la lambda associée à cette API Gateway.

Enfin, il est possible d’utiliser Cloudwatch Log Insights pour recherche et analyser des informations à travers plusieurs log groups. Prenons par exemple cette requête :

filter status >= 200
| stats count(*) as total by httpMethod, path, status, errorResponseType, errorMessage
| sort total desc

Elle permettra d’obtenir le résultat suivant :

Résultat dans Cloudwatch Log Insights

Résultat dans Cloudwatch Log Insights

Liens Utiles

Pour approfondir le sujet je ne peux que conseiller l’excellent article de Alex Debrie : https://www.alexdebrie.com/posts/api-gateway-access-logs/


Si tu es arrivé jusqu’ici, merci beaucoup d’avoir lu cet article !
Pense à t’abonner à la mailing list pour ne rater aucun article, le formulaire se trouve en bas de page.
Photo de couverture par Denys Nevozhai.