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_sqliteest 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 :
ou, si l'application est servie par FrankenPHP (voir la doc « L'application dans Docker ») :
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 :
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 :
PdoFactoryfabrique les connexions : en mode dev (avec l'extensionpdo_sqlite, comme pour la journalisation des requêtes HTTP), la connexion applicative est uneTraceablePDO— qui reste unPDOclassique (DatabaseService::getConnect()est inchangé pour votre code) ;TraceablePDOmesure le temps d'exécution dequery()etexec(), etTraceablePDOStatementcelui de chaqueexecute()de statement préparé (viaPDO::ATTR_STATEMENT_CLASS) ;SqlQueryLoggermet 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 tableserv_sql_logsde 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 :
| 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ànullsi le pilote ne fournit pas l'information, Exemple :-1pour unSELECTsous 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 :
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.
- 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.
- 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.
- 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) |
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 :
TwigouRepository).
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 ? ».
- Profilez deux fois la page : par exemple avant et après une modification, ou au premier chargement puis au suivant.
- Ouvrez l'onglet Code de l'une des deux requêtes, puis l'onglet Comparer.
- 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.
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.
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 |
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¶
| 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(colonnesid,event_date,event_desc) - table :
serv_sql_logs(colonnesid,request_id(token de la requête HTTP),query,duration_ms,rows_count,event_date) — requêtes SQL collectées parSqlQueryLogger, indexée surrequest_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 :
getetpost: les variables$_GETet$_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é__classindiquant 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_mbreflè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
PDOnatif, 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¶
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.