Traquer un utilisateur dans les logs

Publié le 2 mai 2022 par Mathieu De Gracia
Couverture de l'article Traquer un utilisateur dans les logs

Votre application est utilisée quotidiennement par des centaines de personnes, tout semble se dérouler normalement quand subitement l'un de vos utilisateurs vous signale une erreur.

Ni une ni deux vous plongez dans votre fichier de log pour comprendre ce qu'il vient de se passer... et vous vous retrouvez face à plusieurs milliers de lignes.

Difficile de retrouver l'erreur incriminée dans ces conditions.

Depuis Laravel 8.49, le framework offre la possibilité d'ajouter un context unique à chaque ligne de log pouvant vous aider à identifier les erreurs d'un utilisateur précis.

Voyons comment utiliser cette feature pour traquer les erreurs d'un utilisateur dans une application Laravel 9 !

Basic usage

Le contexte doit être configuré en amont de l'utilisation des méthodes de logging (info, success, warning ...) depuis la méthode withContext() de la class Illuminate\Log\Logger.

1app(Logger::class)->withContext([
2 'id' => '123456',
3]);

Dès lors, quand vous utiliserez une méthode de logging cet identifiant sera automatiquement ajouté en suffixe à chaque ligne de votre fichier de logs.

1app(Logger::class)->error('Operations error');

Si vous utilisez le disk par défaut, cette ligne de log arrivera dans le fichier laravel.log :

1// storage/logs/laravel.log
2[2022-04-30 20:02:02] local.ERROR: Operations error {"id":"123456"}

Voyons désormais comment utiliser ce contexte pour traquer les logs d'un utilisateur !

tips, Il est possible d'annuler tous les contextes en cours grace à la méthode withoutContext du Logger

Un cas concret

Imaginez que vous développez une API ouverte.

Quand l'un de vos utilisateurs lève une exception vous voulez être en mesure de lui fournir l'identifiant unique de sa requête — cela lui permettra de vous le transmettre ultérieurement pour simplifier l'identification de son anomalie.

C'est un use-case relativement commun pour les API tiers permettant de faciliter l'exploitation des erreurs.

Le principe est assez simple, créer un middleware pour configurer le contexte tout en conservant l'identifiant unique dans les headers de la request, puis afficher l'identifiant à l'utilisateur au besoin.

Le middleware

Pour commencer il est nécessaire de créer un nouveau middleware que vous pouvez nommer AssignRequestId.

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Log\Logger;
7use Illuminate\Support\Str;
8 
9class AssignRequestId
10{
11 public function handle($request, Closure $next)
12 {
13 $requestId = (string) Str::uuid();
14 
15 app(Logger::class)->withContext([
16 'request-id' => $requestId,
17 ]);
18 
19 $request->headers->set('Request-Id', $requestId);
20 
21 return $next($request);
22 }
23}

La variable $requestId provient d'un uuid pour garantir son unicité puis est transmise au Logger.

Pour finir nous ajoutons dans les headers de la request la valeur du $requestId pour pouvoir le récupérer plus tard.

Dorénavant, chaque requête entrant dans votre Laravel aura un identifiant unique dans ses headers et tous les logs que vous effectuerez auront un suffixe request-id avec la valeur de l'uuid !

N'oubliez pas, un middleware doit être renseigné dans le fichier app\Http\Kernel.php !

Logger et afficher le Request-Id

L'utilisateur génère une erreur, par exemple en ne répondant pas aux rules d'une FormRequest.

1// @Illuminate\Contracts\Validation\Validator
2throw new WhateverException($validator->errors());

Profitons des méthodes report et render d'une exception pour factoriser ça proprement.

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6use Illuminate\Log\Logger;
7use Illuminate\Support\MessageBag;
8 
9class WhateverException extends Exception
10{
11 public function __construct(
12 protected MessageBag $messageBag,
13 ){}
14 
15 public function report()
16 {
17 app(Logger::class)
18 ->error([
19 request()->getRequestUri(),
20 $this->messageBag->toJson(),
21 ]);
22 }
23 
24 public function render()
25 {
26 return response()->json([
27 'message' => 'It looks broken !',
28 'request-id' => request()->header('Request-Id'),
29 ], 400);
30 }
31}

L'utilisateur voit apparaitre sur son écran son request-id ainsi qu'un court message et vos logs contiennent désormais une nouvelle ligne associée à son identifiant !

1[2022-04-30 20:02:02] local.ERROR: array (
2 0 => '/api/clips/1/show?relations=qsdsqdsqdsqdsq',
3 1 => '{"relations":["The selected relations is invalid."]}',
4) {"request-id":"397c2091-4275-455d-9bb8-05f942420cbf"}

Félicitations, vous pouvez désormais traquer vos utilisateurs ! 🧐

Mathieu De Gracia avatar
Mathieu De Gracia
Des fois, mon chat code à ma place 🐱

A lire

Autres articles de la même catégorie