Symfony Logger : tout savoir sur Monolog dans Symfony
Les logs sont les yeux et les oreilles de votre application en production. Symfony integre Monolog, la librairie de logging la plus populaire en PHP, et la configure intelligemment selon l'environnement. Dans cet article, je vous montre comment configurer Monolog dans Symfony 7.2 pour couvrir tous les cas - du debug local aux alertes Slack en production.
Monolog dans Symfony : comment ca marche
Symfony utilise le bundle MonologBundle pour integrer la librairie Monolog dans le framework. Ce bundle fournit une configuration declarative en YAML et enregistre automatiquement le logger comme service injectable. L'architecture repose sur trois concepts cles :
- Handlers - determinent ou les logs sont envoyes (fichier, console, Slack, email, etc.).
- Processors - enrichissent chaque record de log avec des informations supplementaires.
- Channels - separent les logs par domaine fonctionnel pour un routage independant.
L'installation est automatique avec Symfony Flex. Si besoin :
composer require symfony/monolog-bundle
Configuration de base par environnement
Symfony fournit des configurations differentes pour le dev et la prod. Voici la configuration de developpement type :
# config/packages/dev/monolog.yaml
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
En dev, tous les logs de niveau debug et au-dessus sont ecrits dans var/log/dev.log. Le channel event est exclu car il genere un volume enorme de logs a chaque requete. Le handler console affiche les logs directement dans le terminal quand vous executez des commandes.
La configuration de production est naturellement plus stricte :
# config/packages/prod/monolog.yaml
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50
nested:
type: rotating_file
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
max_files: 14
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
Le handler fingers_crossed
Le fingers_crossed est le handler le plus important en production. Son fonctionnement est ingenieux : il bufferise tous les logs de la requete en memoire sans les ecrire. Si une erreur de niveau error ou superieur survient, il vide tout le buffer vers le handler cible (nested). Sinon, les logs sont simplement jetes. Cela vous donne le contexte complet en cas d'erreur tout en gardant les fichiers de log propres.
Le parametre excluded_http_codes evite de logger les 404 classiques (pages non trouvees, bots) comme des erreurs. Le buffer_size limite la memoire utilisee. Pour gerer les taches de fond ou les erreurs asynchrones, pensez a configurer aussi Messenger avec ses workers.
Les niveaux de log PSR-3
Monolog implemente les 8 niveaux definis par la norme PSR-3. Le choix du bon niveau est crucial pour que vos logs soient exploitables :
| Niveau | Usage | Exemple |
|---|---|---|
DEBUG | Information de debogage | Requete SQL executee, cache hit/miss |
INFO | Evenement notable | Utilisateur connecte, commande traitee |
NOTICE | Evenement normal mais significatif | Migration executee, cache purge |
WARNING | Situation anormale non bloquante | API tierce lente, deprecation |
ERROR | Erreur d'execution | Paiement echoue, fichier manquant |
CRITICAL | Composant indisponible | Base de donnees injoignable |
ALERT | Action immediate requise | Disque plein, certificat expire |
EMERGENCY | Systeme inutilisable | Corruption de donnees |
Utiliser le logger dans vos services
Injectez LoggerInterface dans vos services via l'autowiring :
<?php
namespace App\Service;
use Psr\Log\LoggerInterface;
class PaymentService
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly PaymentGateway $gateway,
) {}
public function processPayment(Order $order): PaymentResult
{
$this->logger->info('Debut du paiement', [
'order_id' => $order->getId(),
'amount' => $order->getTotal(),
'currency' => 'EUR',
]);
try {
$result = $this->gateway->charge(
$order->getTotal(),
$order->getPaymentMethod()
);
$this->logger->info('Paiement reussi', [
'order_id' => $order->getId(),
'transaction_id' => $result->getTransactionId(),
]);
return $result;
} catch (PaymentException $e) {
$this->logger->error('Echec du paiement', [
'order_id' => $order->getId(),
'error' => $e->getMessage(),
'code' => $e->getCode(),
]);
throw $e;
}
}
}
Le deuxieme parametre de chaque methode de log est un tableau de contexte. Utilisez-le systematiquement pour ajouter des donnees structurees - c'est bien plus exploitable qu'une simple chaine concatenee.
Channels : separer les logs par domaine
Les channels permettent de router differents types de logs vers differents handlers. Symfony cree automatiquement des channels pour ses composants (doctrine, security, request, etc.). Vous pouvez creer les votres :
# config/packages/monolog.yaml
monolog:
channels: ['payment', 'import', 'notification']
handlers:
payment_file:
type: rotating_file
path: "%kernel.logs_dir%/payment.log"
level: info
channels: [payment]
max_files: 30
import_file:
type: rotating_file
path: "%kernel.logs_dir%/import.log"
level: debug
channels: [import]
max_files: 7
Pour injecter un logger avec un channel specifique, utilisez l'attribut #[Target] en PHP 8.3 :
<?php
namespace App\Service;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Target;
class PaymentService
{
public function __construct(
#[Target('paymentLogger')]
private readonly LoggerInterface $logger,
) {}
}
Ou configurez-le dans services.yaml :
# config/services.yaml
services:
App\Service\PaymentService:
arguments:
$logger: '@monolog.logger.payment'
Handlers avances
Rotating file handler
Le handler rotating_file cree un nouveau fichier de log par jour et supprime automatiquement les anciens. Indispensable en production pour eviter un fichier unique de plusieurs Go :
handlers:
main:
type: rotating_file
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
max_files: 14 # garde 14 jours de logs
Handler Slack pour les alertes
Recevez les erreurs critiques directement dans Slack :
handlers:
slack:
type: slack_webhook
webhook_url: "%env(SLACK_WEBHOOK_URL)%"
channel: "#alertes-prod"
bot_name: "MonApp Logger"
icon_emoji: ":warning:"
level: critical
include_extra: true
Handler email avec le deduplication
handlers:
deduplicated:
type: deduplication
handler: mail
deduplication_level: error
time: 60 # secondes de deduplication
mail:
type: symfony_mailer
from_email: "logs@monapp.fr"
to_email: "dev@monapp.fr"
subject: "[PROD] Erreur critique"
level: error
content_type: text/html
Le handler deduplication evite de recevoir 500 emails si la meme erreur se produit en boucle. Il est recommande d'utiliser la CLI Symfony pour tester votre configuration de logs en local.
Processors : enrichir les logs
Les processors ajoutent automatiquement des informations a chaque record de log. Symfony en fournit plusieurs et vous pouvez creer les votres :
# config/packages/monolog.yaml
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
processors:
- introspection # ajoute fichier, ligne, classe, methode
- web # ajoute URL, IP, methode HTTP
- memory_usage # ajoute la memoire utilisee
Voici un processor custom qui ajoute l'utilisateur connecte a chaque log :
<?php
namespace App\Logger;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
use Symfony\Bundle\SecurityBundle\Security;
class UserProcessor implements ProcessorInterface
{
public function __construct(
private readonly Security $security,
) {}
public function __invoke(LogRecord $record): LogRecord
{
$user = $this->security->getUser();
if ($user !== null) {
$record->extra['user_id'] = $user->getId();
$record->extra['user_email'] = $user->getUserIdentifier();
}
return $record;
}
}
# config/services.yaml
services:
App\Logger\UserProcessor:
tags:
- { name: monolog.processor }
Debugging avec les logs en dev
En environnement de developpement, le Web Profiler de Symfony est votre meilleur ami. Cliquez sur l'icone de logs dans la barre de debug pour voir tous les logs de la requete courante, filtres par channel et niveau. Vous pouvez aussi examiner les logs dans la console lors de l'execution de commandes. Pour gerer les processus de fond, consultez notre article sur les machines a etats avec Workflow.
# Voir les logs en temps reel dans le terminal
tail -f var/log/dev.log
# Filtrer par niveau
tail -f var/log/dev.log | grep "ERROR"
# Avec la commande Symfony
symfony server:log
Structured logging avec le JSON formatter
Pour les outils de monitoring (ELK, Datadog, Grafana Loki), formatez vos logs en JSON :
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: info
formatter: monolog.formatter.json
Cela produit des logs exploitables par des outils d'analyse automatique, avec chaque champ (message, level, channel, context, extra) indexable separement.
Bonnes pratiques
- Utilisez le bon niveau - un
ERRORdoit vraiment etre une erreur, pas un cas metier normal. - Contexte structure - passez toujours un tableau de contexte, jamais de string concatenees.
- Ne loggez pas de donnees sensibles - mots de passe, tokens, numeros de CB n'ont rien a faire dans les logs.
- Surveillez la taille des logs - utilisez
rotating_fileet purgez regulierement. - Testez votre configuration - changez le niveau en dev pour verifier que les handlers fonctionnent.
Conclusion
Monolog dans Symfony est un systeme de logging complet et configurable. Le handler fingers_crossed, les channels dedies, les processors d'enrichissement et les alertes Slack forment un arsenal solide pour surveiller votre application. Investissez du temps dans cette configuration - en production, de bons logs font la difference entre un incident resolu en 5 minutes et un debug de 3 heures.
Vous souhaitez un audit de votre configuration Symfony ou un accompagnement technique ? Decouvrez mes services de developpement web, consultez les tarifs ou contactez-moi pour en discuter.
