Aller au contenu

La barre de debug et le profiler

En mode développement (APP_ENV=dev), Edu Framework affiche une barre de debug en bas de chaque page (inspirée de la Web Debug Toolbar de Symfony) et met à disposition une page de profiler pour naviguer dans l'historique des requêtes HTTP reçues par le serveur.

Ces outils sont disponibles à partir de la version v2.3.0.

Prérequis

La barre de debug est active lorsque APP_ENV=dev. Le profiler et l'enregistrement des requêtes nécessitent en plus l'extension PHP pdo_sqlite.

Pour activer le mode dev :

## << Config application
## pour activer le mode dev, il faut mettre APP_ENV=dev
APP_ENV=dev
## >> Config application
  • l'extension PHP pdo_sqlite est installée (elle est utilisée pour stocker les logs des requêtes).

Mode production

Les routes /_debug/profiler, /edu-logs et /edu-sql-logs ne sont enregistrées qu'en mode dev. Elles ne sont donc pas accessibles lorsque APP_ENV=prod.

La barre de debug

La barre s'affiche automatiquement en bas de chaque page. Chaque bloc affiche une information au survol :

Bloc Information
Statut HTTP Code et libellé de la réponse (200 OK, 404 Not Found, ...) - le fond est coloré selon la famille du statut (2xx vert, 4xx orange, 5xx rouge)
Route Chemin appelé, controller exécuté et paramètres de la route
Temps d'exécution Durée totale de la requête en millisecondes
Mémoire Pic d'utilisation mémoire et limite PHP (memory_limit)
SQL Nombre de requêtes SQL exécutées pendant la requête et temps SQL cumulé ; au survol : requête la plus lente et lien vers la vue SQL du profiler (#sql/{token}). Le bloc est masqué s'il n'y a aucune requête
DevTool Token de la requête en cours et lien vers son descriptif dans le profiler (#request/{token}) ; si la requête n'est pas journalisée, lien vers la liste des requêtes
DEV Version de PHP et SAPI utilisée; type, hôte et nom de la base de données au survol (visible uniquement si DB_HOST_STATUS=true) ; services de développement au survol : application (HTTP/HTTPS), MariaDB, Mailpit (interface web et SMTP), DbGate et phpMyAdmin — les URLs des interfaces web sont cliquables (ouverture dans un nouvel onglet), les ports sont lus dans le .env (HTTP_PORT, HTTPS_PORT, DB_HOST/DB_SOCKET, MAILER_UI_PORT, MAILER_SMTP_PORT, DBGATE_PORT, PMA_PORT) avec les valeurs par défaut du compose.yaml en cas d'absence. Les colonnes contenant des liens sont correctement alignées avec les autres informations.
Edu Framework Version du framework, date de livraison et lien vers la documentation

Accès aux services Docker depuis la barre

Le panneau du bloc DEV liste les services de développement publiés sur l'hôte avec leurs URLs : l'application (http://localhost:80 et https://localhost:443), MariaDB, Mailpit (http://localhost:8025, SMTP localhost:1025), DbGate (http://localhost:8082) et phpMyAdmin (http://localhost:8081). Les ports personnalisés dans le .env sont automatiquement pris en compte (voir « L'application dans Docker »).

Réduire la barre

Le bouton × à droite de la barre permet de la réduire. Un bouton avec le logo Edu Framework apparaît alors en bas à droite de l'écran pour la réafficher. Cet état est mémorisé (localStorage) d'une page à l'autre.

Le profiler

Le profiler est une page (style Symfony Profiler) qui permet de naviguer dans les requêtes HTTP journalisées par le serveur (serveur PHP intégré ou FrankenPHP).

Elle est disponible à l'adresse :

http://localhost:8042/_debug/profiler

ou, si l'application est servie par FrankenPHP (voir la doc « L'application dans Docker ») :

https://localhost/_debug/profiler

La base des logs étant partagée (le conteneur Docker monte le dossier du projet), le profiler affiche toutes les requêtes reçues, quel que soit le serveur qui les a traitées.

Fonctionnalités

  • liste des requêtes reçues, de la plus récente à la plus ancienne ;
  • compteur du nombre de requêtes par famille de statut (2xx, 3xx, 4xx, 5xx) : onglets de filtrage en tête des vues Logs et Recherche (un clic filtre les résultats sur la famille, l'onglet Tous affiche toutes les requêtes ; dans la vue Recherche, le champ Statut du formulaire est synchronisé) ;
  • descriptif d'une requête HTTP : la page /_debug/profiler#request/{token} décrit la requête sélectionnée (statut, méthode, URL, IP, SAPI, durée, mémoire, ligne brute) — c'est la page ouverte par le bloc DevTool de la barre de debug ; l'URL d'une requête dans la liste est également cliquable ;
  • vue SQL d'une requête HTTP : la page /_debug/profiler#sql/{token} liste les requêtes SQL exécutées pendant la requête sélectionnée (voir « Les requêtes SQL ») — accessible depuis la ligne Requêtes SQL du descriptif et le bloc SQL de la barre de debug ;
  • filtres combinables :
Filtre Description Exemple
Méthode Méthode HTTP (GET, POST, PUT, DELETE, ...) GET
Statut Code complet ou famille de code (2, 3, 4 ou 5 pour 2xx à 5xx) 404 ou 4
Recherche Recherche textuelle dans l'événement favicon
IP Adresse IP du client 127.0.0.1
SAPI Serveur ayant traité la requête cli-server ou frankenphp
URL Fragment du chemin de la requête /ville
Token Identifiant du log 42
From / Until Bornes de date (format YYYY-MM-DD, heure locale) 2026-10-01

L'API des logs

Le profiler s'appuie sur un endpoint interne qui retourne les logs au format JSON :

GET /edu-logs

Paramètres de la requête :

Paramètre Description Défaut
page Numéro de la page 1
limit Nombre de logs par page (maximum 100) 20
method Filtre sur la méthode HTTP —
status Filtre sur le statut complet (500) ou sa famille (5) —
search Recherche textuelle —
ip Filtre sur l'adresse IP du client —
sapi Filtre sur le SAPI ayant servi la requête (cli-server, frankenphp, ...) —
url Filtre sur le chemin de la requête —
token Filtre sur l'identifiant du log —
from Date de début (YYYY-MM-DD) —
until Date de fin (YYYY-MM-DD) —

Exemple de réponse :

{
    "total": 42,
    "page": 1,
    "pages": 3,
    "limit": 20,
    "counts": {
        "all": 42,
        "2": 40,
        "3": 0,
        "4": 2,
        "5": 0
    },
    "logs": [
        {
            "id": 42,
            "event_date": "2026-10-05 12:34:56",
            "event": {
                "raw": "[Mon Oct  5 12:34:56 2026] 127.0.0.1:65229 [200]: GET /",
                "timestamp": "Mon Oct  5 12:34:56 2026",
                "ip_port": "127.0.0.1:65229",
                "status_code": "200",
                "method": "GET",
                "path": "/",
                "sapi": "cli-server",
                "execution_time_ms": 183.4,
                "memory_mb": 14.0,
                "error_message": "ParseError : syntax error, unexpected token \"return\" (fichier app/Controller/HomeController.php, ligne 17)"
            }
        }
    ]
}

Les requêtes SQL

En mode dev, chaque requête SQL exécutée pendant une requête HTTP est mesurée et rattachée au token de la requête (comme le Doctrine panel de Symfony). La vue SQL du profiler (/_debug/profiler#sql/{token}) liste pour une requête HTTP :

  • le tableau chronologique des requêtes SQL : numéro d'ordre, requête, durée (colorée au-delà de 20 ms et 100 ms), nombre de lignes retournées ou affectées, heure ;
  • un badge sur les requêtes identiques exécutées plusieurs fois : nombre d'exécutions et temps cumulé du groupe (×3 requêtes identiques — 4,5 ms au total) ;
  • le temps SQL cumulé et sa part du temps de la requête HTTP (Exemple : 12,3 ms — 34 % du temps de la requête HTTP (36,1 ms)).

Comment fonctionne la collecte

La collecte est assurée par trois classes du framework, uniquement en mode dev :

  • PdoFactory fabrique les connexions : en mode dev (avec l'extension pdo_sqlite, comme pour la journalisation des requêtes HTTP), la connexion applicative est une TraceablePDO — qui reste un PDO classique (DatabaseService::getConnect() est inchangé pour votre code) ;
  • TraceablePDO mesure le temps d'exécution de query() et exec(), et TraceablePDOStatement celui de chaque execute() de statement préparé (via PDO::ATTR_STATEMENT_CLASS) ;
  • SqlQueryLogger met les requêtes en mémoire tampon avec le token de la requête HTTP en cours, puis les écrit groupées en une transaction en fin de requête dans la table serv_sql_logs de la base des logs.

En production (APP_ENV=prod), la fabrique retourne une connexion PDO native : les classes de tracing ne sont ni instanciées ni chargées, aucune mesure ni collecte n'a lieu.

Les requêtes exécutées hors d'une requête HTTP journalisée ne sont jamais collectées : commandes CLI, routes internes du profiler (/_debug/..., /edu-logs, /edu-sql-logs) et requêtes de la base des logs elle-même — le rattachement se fait par le token, absent dans ces contextes.

L'API des requêtes SQL

Le profiler s'appuie sur un endpoint interne qui retourne les requêtes SQL d'une requête HTTP au format JSON :

GET /edu-sql-logs?token=42
Paramètre Description Défaut
token Identifiant du log de la requête HTTP (le # du bloc DevTool est accepté : #42) requis

Exemple de réponse :

{
    "token": 42,
    "total": 3,
    "total_duration_ms": 2.6,
    "groups": [
        { "query": "SELECT * FROM ville WHERE nom = :nom", "count": 2, "duration_ms": 1.6 },
        { "query": "SELECT 1", "count": 1, "duration_ms": 1.0 }
    ],
    "queries": [
        {
            "id": 1,
            "query": "SELECT 1",
            "duration_ms": 1.0,
            "rows_count": 1,
            "event_date": "2026-10-17 15:04:25"
        }
    ]
}
  • queries : liste chronologique des exécutions (rows_count à null si le pilote ne fournit pas l'information, Exemple : -1 pour un SELECT sous PostgreSQL) ;
  • groups : requêtes identiques regroupées avec leur nombre d'exécutions et leur temps cumulé.

Le profiler de code

Le profiler de code mesure le temps passé dans chaque fonction PHP pendant une requête HTTP, pour repérer ce qui ralentit une page. Il s'appuie sur l'extension Excimer (Wikimedia), un profiler par échantillonnage : toutes les 0,1 ms, Excimer relève la pile d'appels en cours. Une fonction présente dans 30 % des relevés a consommé environ 30 % du temps de la requête. Le coût reste faible (quelques %) et fonctionne avec FrankenPHP, y compris en mode worker.

Déjà disponible avec FrankenPHP dans Docker

Si votre application tourne sur le serveur d'exécution FrankenPHP dans Docker (service frankenphp, voir « L'application dans Docker »), le profiler de code est déjà prêt : l'extension Excimer est installée dans l'image et le profilage à la demande est activé par défaut (EDU_PROFILER=trigger). Il n'y a rien à installer ni à configurer : ouvrez l'application (https://localhost) et cliquez sur le bloc Code de la barre de debug.

L'installation décrite ci-dessous ne concerne que l'exécution hors Docker (serveur PHP intégré, par exemple http://localhost:8042).

Un résultat statistique

Une fonction très courte (moins de 0,1 ms) peut ne jamais apparaître dans le profil. Les temps affichés sont des estimations : nombre de relevés × 0,1 ms.

Installer l'extension

L'image Docker du framework installe Excimer par défaut (argument de build EDU_PROFILER=1). Pour construire une image sans profiler :

EDU_PROFILER_BUILD=0 docker compose build

Hors Docker, installez l'extension avec PECL (pecl install excimer) puis activez-la dans le php.ini (extension=excimer). La commande php bin/edu check:config indique si l'extension est chargée.

Activer le profilage

Le profilage se règle avec la variable EDU_PROFILER du .env :

## << Config profiler de code
EDU_PROFILER=trigger
EDU_PROFILER_KEEP=50
## >> Config profiler de code
Valeur Comportement
off Profiler désactivé, le bloc Code est masqué
trigger (défaut) Profilage à la demande : bouton Code de la barre de debug, ou paramètre ?_profile=1 dans l'URL
on Chaque requête est profilée

EDU_PROFILER_KEEP fixe le nombre de profils conservés (les plus anciens sont supprimés, 50 par défaut).

En mode trigger, un clic sur le bloc Code pose le cookie edu_profile=1 et recharge la page : cette page et les suivantes sont profilées, jusqu'au clic sur Désactiver dans le panneau du bloc.

Lire le profil

  • Le bloc Code de la barre de debug affiche le temps profilé et la fonction la plus coûteuse ; son panneau liste le top 3 des fonctions.
Panneau du bloc Code de la barre de debug
Le panneau du bloc Code : temps profilé, top 3 des fonctions et lien Désactiver
  • L'onglet Code du profiler (/_debug/profiler#code/{token}) affiche le tableau de toutes les fonctions, triable par temps propre (temps passé dans la fonction elle-même) ou temps inclusif (fonction et fonctions appelées), filtrable par origine (app, framework, vendor). Un clic sur une fonction affiche ses appelants et ses appelés.
Onglet Code du profiler
L'onglet Code du profiler, toutes les origines cochées, trié par temps propre
Appelants et appelés d'une fonction
Après un clic sur une fonction : ses appelants (Appelée par) et ses appelés (Appelle)
  • Les onglets Flamegraph, Chronologique et Comparer de l'onglet Code affichent le flamegraph de la requête directement dans le profiler (voir ci-dessous).
  • Le panneau du bloc Code de la barre de debug affiche un mini-flamegraph de la requête : un clic l'ouvre en grand dans le profiler.
Mini-flamegraph du bloc Code
Le mini-flamegraph du panneau du bloc Code : les appels de plus de 1 % du temps, colorés selon leur origine
  • Le bouton Télécharger le flamegraph (Speedscope) exporte les piles d'appels, à ouvrir sur speedscope.app pour une analyse plus poussée (voir plus bas).

Le flamegraph intégré

Le tableau de l'onglet Code additionne les temps par fonction, mais il ne montre pas l'enchaînement des appels. Le flamegraph le montre : on voit d'un coup d'œil quel chemin dans le code consomme le temps.

Le profiler dessine le flamegraph sans quitter la page et sans télécharger de fichier. Choisissez une vue avec les onglets placés au-dessus du tableau :

Onglet URL Ce qu'il montre
Tableau /_debug/profiler#code/{token} Le tableau des fonctions (temps propre et temps inclusif)
Flamegraph /_debug/profiler#code/{token}/flame Les appels identiques regroupés et triés, le plus coûteux à gauche (la vue Left Heavy de Speedscope)
Chronologique /_debug/profiler#code/{token}/time Les appels dans l'ordre où ils ont eu lieu : l'axe horizontal est le temps écoulé depuis le début de la requête (la vue Time Order de Speedscope)
Comparer /_debug/profiler#code/{token}/diff/{autre} Le flamegraph différentiel de deux requêtes profilées (voir ci-dessous)
Flamegraph intégré au profiler
Le flamegraph d'une requête vers /medecin/new : sous Twig\Environment::compileSource, la compilation du template occupe la plus grande partie de la largeur

Lire le graphe. Chaque rectangle est une fonction :

  • la largeur est le temps passé dans la fonction et dans tout ce qu'elle appelle (le temps inclusif) ;
  • de haut en bas, on descend dans la pile d'appels : la première ligne est la requête entière, puis le point d'entrée (public/index.php), puis les fonctions qu'il appelle, et ainsi de suite ;
  • la couleur indique l'origine du code : jaune pour votre code (app/), bleu pour le framework, violet pour les dépendances (vendor/), gris pour le reste. La nuance varie d'une fonction à l'autre pour distinguer deux rectangles voisins.

Cherchez les rectangles jaunes larges : c'est le temps consommé par votre propre code.

Naviguer.

  • Le survol d'un rectangle affiche le nom de la fonction, son fichier, son temps inclusif et son temps propre.
  • Un clic zoome sur la fonction : elle prend toute la largeur, ses parents restent affichés en atténué au-dessus. Le chemin d'appels au-dessus du graphe permet de remonter d'un clic, la touche Esc remonte d'un niveau et le bouton Réinitialiser le zoom revient à la requête entière.
  • Sous le graphe, le détail de la fonction zoomée donne son fichier, son origine, ses temps et le lien Voir les appelants et les appelés, qui affiche les mêmes tableaux que l'onglet Tableau.
  • Le champ Rechercher une fonction surligne les fonctions dont le nom contient le texte saisi et indique le temps total qu'elles représentent (Exemple : Twig ou Repository).

Appels regroupés dans « … (autres) »

Pour que le graphe reste lisible et léger, les appels qui représentent moins de 0,1 % du temps de la requête sont regroupés dans un rectangle … (autres) (vue Flamegraph) ou ne sont pas dessinés (vue Chronologique). Le fichier Speedscope, lui, contient toutes les piles d'appels.

Comparer deux requêtes

L'onglet Comparer répond à la question : « Pourquoi cette page est-elle plus lente que tout à l'heure ? ».

  1. Profilez deux fois la page : par exemple avant et après une modification, ou au premier chargement puis au suivant.
  2. Ouvrez l'onglet Code de l'une des deux requêtes, puis l'onglet Comparer.
  3. Choisissez la requête de référence dans la liste Comparer avec. Les requêtes profilées sur la même URL sont proposées en premier, et la précédente est choisie par défaut.

La forme du graphe est celle de la requête affichée. La couleur indique l'écart avec la référence pour le même chemin d'appels : rouge si la requête est plus lente, bleu si elle est plus rapide, gris si le temps est identique. Plus la couleur est foncée, plus l'écart est grand. Le survol et le détail affichent le temps de la référence et l'écart en ms.

Flamegraph différentiel
Premier chargement de /medecin/new comparé au suivant : la compilation du template Twig (en rouge) n'existe pas dans la référence

Un chemin qui a disparu

Un appel présent seulement dans la référence n'est pas dessiné, puisque la forme est celle de la requête affichée : son temps apparaît dans l'écart de la fonction qui l'appelait. Pour le voir, inversez la comparaison en ouvrant l'onglet Comparer de l'autre requête.

Lire le flamegraph avec Speedscope

Le flamegraph intégré suffit dans la plupart des cas. Pour aller plus loin (toutes les piles d'appels sans regroupement, zoom à la molette, vue Sandwich), exportez le profil vers Speedscope.

Speedscope est un visualiseur de flamegraph open source qui fonctionne dans le navigateur.

1. Exporter le profil. Dans l'onglet Code d'une requête, cliquez sur Télécharger le flamegraph (Speedscope). Le navigateur enregistre un fichier edu-profile-{token}.speedscope.json.

2. Ouvrir le fichier. Cliquez sur Ouvrir speedscope.app, puis sur Browse pour choisir le fichier, ou glissez-déposez-le directement dans la fenêtre.

Écran d'accueil de speedscope.app
L'écran d'accueil de speedscope.app : bouton Browse ou glisser-déposer du fichier

Le fichier reste sur votre poste

speedscope.app lit le fichier dans le navigateur : le profil n'est envoyé à aucun serveur. Il contient néanmoins les noms des fonctions et les chemins des fichiers de votre application.

3. Lire le graphe. Chaque rectangle est un appel de fonction :

  • la largeur d'un rectangle est le temps passé dans la fonction et dans tout ce qu'elle appelle (le temps inclusif) ;
  • de haut en bas, on descend dans la pile d'appels : la première ligne est le point d'entrée (public/index.php), la ligne en dessous contient les fonctions qu'il appelle, et ainsi de suite ;
  • une couleur correspond à une fonction, ce qui aide à repérer une même fonction à plusieurs endroits.

Pour trouver ce qui ralentit une page, cherchez les rectangles larges sur les lignes du bas : c'est là que le temps est réellement consommé.

4. Choisir une vue. Les trois onglets en haut à gauche (ou les touches 1, 2, 3) changent la présentation :

Vue Ce qu'elle montre Quand l'utiliser
Time Order Les piles d'appels dans l'ordre chronologique de la requête (l'axe horizontal est le temps écoulé) Comprendre le déroulement de la requête : routage, controller, rendu du template, ...
Left Heavy Les appels identiques sont regroupés et triés, le plus coûteux à gauche Trouver rapidement le chemin d'appels qui coûte le plus cher
Sandwich Un tableau des fonctions (temps propre et temps total). Une fonction sélectionnée affiche ses appelants au-dessus et ses appelés en dessous Analyser une fonction précise appelée depuis plusieurs endroits
Vue Left Heavy de Speedscope
La vue Left Heavy d'une requête vers /medecin/new : sous Twig\Environment::compileSource, la compilation du template occupe presque toute la largeur

5. Naviguer. La molette (ou le pincement sur un pavé tactile) zoome, un glisser déplace la vue, et un clic sur un rectangle affiche en bas de l'écran le détail de la fonction (temps propre, temps total, fichier).

Exemple : la compilation d'un template Twig

Sur la première requête vers /medecin/new après la modification de son template, la vue Left Heavy (capture ci-dessus) montre que presque toute la requête (environ 11 ms sur 12) se trouve sous Twig\Environment::compileSource : Twig compile le template en PHP (Lexer::tokenize puis Parser::parse) avant de l'afficher. Le résultat est mis en cache dans var/cache/twig : les requêtes suivantes sur cette page n'ont plus ce coût. Avant d'optimiser une page, profilez donc plusieurs requêtes pour ne pas confondre un coût de premier chargement avec un vrai problème de performance.

L'API du profil de code

GET /edu-code-profile?token=42
Paramètre Description Défaut
token Identifiant du log de la requête HTTP (#42 accepté) requis
full 1 : ajoute toutes les fonctions (functions) et le graphe d'appels (edges) —
format speedscope : retourne le fichier Speedscope de la requête ; flame : retourne l'arbre du flamegraph intégré (clé tree) —
order Avec format=flame : heavy (appels regroupés) ou time (ordre chronologique) heavy
compare Avec format=flame : token d'une autre requête profilée, pour le flamegraph différentiel (clés compare, compare_summary, et base_ms / delta_ms dans chaque nœud) —
min Avec format=flame : part minimale du temps total pour garder un appel (de 0 à 0.1) 0.001
depth Avec format=flame : profondeur maximale de l'arbre (0 : illimitée) 0

La réponse contient status (ready, ou pending tant que le profil n'est pas enregistré), summary (driver, period_ms, samples, wall_ms, peak_mb, functions_count) et top (fonctions les plus coûteuses). Avec format=flame, chaque nœud de tree contient name, origin, file, line, start et value (position et largeur en échantillons), self, ms, self_ms, pct et children. Les profils sont stockés, compressés, dans la table serv_code_profiles de la base des logs.

Le stockage des logs

Les requêtes HTTP traitées par le framework sont enregistrées dans une base SQLite dédiée :

  • fichier : var/logs/core_logs.sqlite
  • table : serv_logs (colonnes id, event_date, event_desc)
  • table : serv_sql_logs (colonnes id, request_id (token de la requête HTTP), query, duration_ms, rows_count, event_date) — requêtes SQL collectées par SqlQueryLogger, indexée sur request_id

La collecte est assurée par la classe HttpRequestLogger, en deux temps comme le profiler de Symfony : la ligne de log est réservée en début de requête — son identifiant (le token) est alors disponible pour le bloc DevTool de la barre de debug, qui fait le lien vers le descriptif de la requête — puis complétée en fin de requête avec le statut final, le temps d'exécution et la mémoire. C'est ce qui permet de mesurer par requête le temps d'exécution et la mémoire utilisée — deux indicateurs également affichés par la barre de debug — quel que soit le serveur qui la sert (serveur PHP intégré via php bin/edu start, ou FrankenPHP dans Docker). La commande php bin/edu start analyse la sortie du serveur uniquement pour l'afficher dans la console : elle n'écrit plus dans la base des logs.

Si une erreur survient (exception attrapée par le framework : erreur 500, ErrorHttpStatusException levée par un controller, ...), le framework la transmet à HttpRequestLogger::setError() et le log de la requête porte une clé error_message décrivant la cause de l'erreur — message de l'exception, fichier et ligne où elle s'est produite. La description est visible dans le contexte du log (elle est filtrable par le champ Recherche) et sur la page descriptif de la requête, afin d'identifier et corriger l'erreur directement depuis les logs. Une requête qui s'est déroulée sans erreur ne porte pas la clé.

Pour faciliter le débogage, le log de la requête porte aussi les variables de la requête, affichées sous forme de tableaux sur la page descriptif de la requête :

  • get et post : les variables $_GET et $_POST, capturées en début de requête telles qu'envoyées par le client (avant d'éventuelles modifications par le controller) ;
  • session : les variables $_SESSION, capturées en fin de requête (après l'exécution du controller), uniquement si l'application a démarré une session. Les objets stockés en session sont enregistrés avec toutes leurs propriétés (y compris privées et protégées) et une clé __class indiquant leur classe.

Chaque clé n'est présente que si elle contient des variables. Les variables sensibles — dont le nom contient pass, pwd, secret, mot_de_passe ou mdp (insensible à la casse) — sont masquées (******), y compris dans les tableaux imbriqués : un mot de passe n'est jamais enregistré en clair dans la base des logs. Le corps brut d'une requête JSON (php://input) n'est pas capturé.

Chaque événement porte les clés :

Clé Description
raw, timestamp, ip_port, status_code, method, path même format que la sortie du serveur PHP intégré
sapi serveur à l'origine de la requête (cli-server, frankenphp, ...) : affiché dans le profiler et filtrable (champ SAPI)
execution_time_ms temps d'exécution total de la requête en millisecondes
memory_mb pic de mémoire utilisée par la requête en mégaoctets
error_message description de l'erreur à l'origine de la réponse (classe, message, fichier et ligne de l'exception) : uniquement présente si une erreur a été interceptée

Les événements étant au même format pour tous les serveurs, le profiler affiche l'historique complet, même si les deux serveurs tournent simultanément. Pour éviter les erreurs de verrou SQLite entre le poste et le conteneur, la connexion attend jusqu'à 5 secondes la disponibilité de la base (PRAGMA busy_timeout).

flowchart LR
    S["Requête HTTP<br/>(serveur PHP intégré ou FrankenPHP)"] --> HR["HttpRequestLogger<br/>(journalise la requête en fin d'exécution<br/>avec SAPI, temps et mémoire)"]
    S --> SQL["Requêtes SQL de l'application<br/>(TraceablePDO, uniquement en dev)"]
    SQL --> SQLG["SqlQueryLogger<br/>(tampon + écriture groupée<br/>rattachée au token de la requête)"]
    HR --> DB[("SQLite<br/>var/logs/core_logs.sqlite<br/>tables serv_logs et serv_sql_logs")]
    SQLG --> DB
    DB --> API["endpoints /edu-logs et /edu-sql-logs<br/>(JSON)"]
    API --> P["Profiler<br/>(filtres, recherche, vue SQL par requête)"]

Info

Les requêtes du profiler et de la barre de debug (/_debug/..., /edu-logs et /edu-sql-logs) ne sont pas journalisées, afin d'éviter le remplissage de la base à chaque affichage d'une page d'outils.

Limitations

  • seules les requêtes exécutées par PHP sont journalisées : les fichiers statiques servis directement par le serveur web (assets de public/upload/, ...) ne le sont pas, que ce soit par le serveur intégré ou par Caddy ;
  • dans le conteneur Docker, l'IP du client est celle vue par FrankenPHP (réseau Docker) : le filtre IP du profiler donne l'IP du pont Docker, pas 127.0.0.1 ;
  • en mode worker de FrankenPHP (non activé par défaut), le pic de mémoire est cumulé sur la durée de vie du worker : memory_mb reflète alors le worker et non la seule requête ;
  • le bloc SQL de la barre de debug compte les requêtes exécutées jusqu'au rendu de la page : les requêtes exécutées après (si elles existent) ne sont pas comptées dans le bloc, mais le sont dans le profiler ;
  • les requêtes SQL ne sont collectées qu'en mode dev : en production, la connexion est un PDO natif, sans mesure ni écriture dans la base des logs.

Ajouter ses propres logs

La classe LogsService peut être utilisée directement dans votre application (controller, commande CLI, ...) pour enregistrer et lire vos propres événements.

Enregistrer un log

use Studoo\EduFramework\Core\Logs\LogsService;

LogsService::addLog('Mon événement personnalisé');

Lire les logs

use Studoo\EduFramework\Core\Logs\LogsService;

// Récupère les 20 derniers logs
$logs = LogsService::getLogs(20, 0);

// Récupère les logs filtrés (toutes les requêtes en 404)
$logs = LogsService::getLogs(20, 0, ['status' => '404']);

// Compte le nombre total de logs
$nbLogs = LogsService::countLogs();

// Compte les logs par famille de statut (2xx à 5xx)
$counts = LogsService::countByStatusFamily();

Filtres disponibles

Les méthodes getLogs(), countLogs() et countByStatusFamily() acceptent les mêmes filtres que l'API : method, status, search, ip, url, token, from et until.