Symfony 2 : Configuration des logs Monolog

Symfony 2 : Configuration des logs Monolog
05 Janvier 2015


Symfony 2 utilise Monolog pour gérer les logs.

Les logs vous permettent de garder une trace de ce qui se passe sur votre application. Souvent ils se révèlent une source très précieuse d'informations lorsqu'un utilisateur soulève un bug ou un comportement anormal.

De plus, avec Symfony, vous pouvez mettre en place des mécanismes simples pour déclencher des actions en fonction du niveau d'alerte des logs, par exemple pour recevoir un mail lorsqu'un problème survient. N'hésitez pas à logger le plus d'informations possible, Symfony vous permet de gérer simplement la rotation des fichiers de log pour ne pas perdre indéfiniment en espace disque.

 

Niveaux de logs

Comme beaucoup de systèmes de log, Monolog utilise plusieurs niveaux. Par ordre croissant, du moins alertant au plus critique des logs :

DEBUGUtilisé en général pour développer ou débugger une application afin de vérifier une valeur ou un bon déroulement.
INFOInformation sur un événement commun et normal (exemple : un utilisateur qui se connecte).
NOTICEComportement normal signifiant mais pas d'erreur.
WARNINGÉvénement exceptionnel mais sans erreur (exemple : Utilisation d'une fonction dépréciée).
ERRORErreur d'exécution qui ne demande pas d'intervention immédiate mais qui doit être enregistrée. (exemple : une erreur 404, un objet non trouvé en base avec tel identifiant ...).
CRITICALException inattendue soulevée pendant l'exécution de l'application. Cette action est généralement accompagnée d'alerte mail. (exemple : un paramètre manquant dans la configuration d'un module).
ALERT"Alerte rouge", tout le service ou sa base de données est indisponible. Cette action est généralement accompagnée d'alerte sms et / ou d'alerte monitoring sonore. (exemple : le site est inaccessible par votre outil de monitoring).
EMERGENCYLe système est inutilisable, tout est complètement cassé et nécessite une grosse intervention pour tout remettre d'aplomb. Des données sont perdues / corrompues. Bref ... Je vous laisse imaginer la catastrophe que ça peut être. Je vous souhaite de ne jamais voir apparaître ce genre de log ! (exemple : détection d'un hacking bien hard de votre site).

 

Logger avec Symfony

Pour utiliser le système de log de Symfony :

Dans un contrôler :

$logger = $this->get('logger');
$logger->info('Tout va bien, je suis en version 2.3');
$logger->error('Je ne peux pas trouver la voiture n°53');
$logger->critical('Il manque un ; !!');

Dans un service :

votre.service:
    class: ACME\AppBundle\Services\DefaultService
    arguments: [@logger]
namespace ACME\AppBundle\Mailer;

use Monolog\Logger;

class Mailer
{
    protected $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function faireQuelqueChose() {
    
        $this->logger->info('Je fais quelque chose');
        $this->logger->critical('Mais je n\'ai pas réussi ...');
    }
}

 

Configuration du logger

La configuration par défaut que l'on trouve pour un environnement de production est celle-ci :

monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
        nested:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
        console:
            type: console

Ici main, nested et console sont appelés des handlers (des gestionnaires), le nom donné est arbitraire. Pour chaque handler, on définit un type. Nous verrons les différents types de handler possible plus bas.

Chaque handler est ensuite appelé dans l'ordre défini (Attention, on a parfois des handlers imbriqués. Ils ne sont pas appelés par défaut ! C'est le cas ici puisque nested est imbriqué dans main).

Pour expliquer cette configuration :

On a ici un FingersCrossedHandler (handler qui en déclenche un autre, ici il s'agit de main) qui se déclenche seulement lorsque le niveau de log attendu est atteint (ici on attend un log de type error). Ce handler, une fois déclenché, appelle le handler nested. Nested est de type stream (handler qui écrit les logs) qui va écrire les logs dans un fichier à partir d'un level défini (ici tout les logs plus importants ou égaux à debug).

Le handler Nested n'est pas déclenché par défaut car il est imbriqué dans main.

Le handler console est déclenché quant à lui pour tous les logs, nous ne verrons pas ce handler dans cet article, si vous désirez en savoir plus, lisez cet article du blog de Symfony.

 

Différents types de handles

Il existe plusieurs types de handler avec chacun une fonctionnalité précise :

  • finders_crossed : Ce handler stocke dans un buffer tout les logs qui passe. Lorsqu'un des logs dépasse le niveau minimum requis, il appelle un autre handler avec tous les logs contenus dans son buffer.
  • stream : Ce handler écrit le log qu'il reçoit dans un fichier si son niveau dépasse le niveau minimum requis.
  • rotating_file : Ce handler fait la même chose que stream mais fait une rotation des fichiers pour effacer les logs anciens.
  • group : Ce handler envoit le log reçu à plusieurs handles (exemple : pour écrire le log ET l'envoyer par mail)
  • buffer : Ce handler stocke dans un buffer tout les logs qu'il reçoit puis envoit le buffer à un handler à la fin de l'exécution de la requête.
  • swit_mailler : Ce handle envoit par mail les logs (souvent passé par un handler de type buffer)
  • console : Ce handler permet de définir les niveaux d'affichage de log dans la console.

Nous avons vu avec l'exemple par défaut comment marche les handler finder_crossed et stream.

Nous verrons dans les exemples ci-dessous comment sont utilisés les handlers pour faire ce que l'on veut.

 

Envoyer les alertes par mail

monolog:
    handlers:
        mail:
            type:         fingers_crossed
            action_level: critical
            handler:      buffered
        buffered:
            type:    buffer
            handler: swift
        swift:
            type:       swift_mailer
            from_email: contact@domaine.com
            to_email:   error@domaine.com
            subject:    Une erreur critique est survenue
            level:      info

Ici on attend un log de niveau critical pour déclencher le handle buffered. Une fois déclenché, le handler buffered va stocker tout les logs et les passer à la fin de l'éxécution de la requête du client au handler swift. Ce dernier va envoyer un mail en triant les logs reçus et en ne gardant que ceux de niveau minimum info.

Contrairement à un handler de type finger_crossed, un handler de type buffer appelle un handler une seule fois avec le contenu de son buffer alors que finger_crossed appelle un autre handler pour chaque log contenu dans son buffer.

 

Rotation des logs

Pour faire la rotation des logs, on va utiliser simplement le handler rotating_file au lieu de steam :

monolog:
    handlers:
        main:
            type: rotating_file
            max_files: 10
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug

Ici on écrit tous les logs de niveau supérieur à debug dans un fichier en rajoutant la date du jour dans le nom du fichier. Au bout de 10 fichiers créés, le plus vieux est supprimé automatiquement dès qu'un nouveau est créé et ainsi de suite. On a donc au minimum 10 jours de logs derrière nous. Vous pouvez augmenter ce paramètre avec max_files.

 

Les channels

Les logs utilisent des channels pour s'identifier. Par exemple, les logs de doctrine sont sur le channel "doctrine" et ceux sur les authentifications sont sur le channel "security"

Ainsi on peux lancer des handlers différents en fonction du type de channel.swift

monolog:
    handlers:
        main:
            type: stream
            path: /var/log/symfony.log
            channels: [!doctrine, !security]
        doctrine:
            type: stream
            path: /var/log/doctrine.log
            channels: doctrine
        login:
            type: stream
            path: /var/log/auth.log
            channels: security

Ici on écrit tous les logs qui ne viennent pas de doctrine, ni de security dans un fichier symfony.log (car le type de handler est stream). On écrit tous les logs de doctrine dans doctrine.log et tout ceux de security dans auth.log.

Vos propres channels

Vous pouvez bien évidemment créer vous aussi vos propres channel pour logger vos log comme vous le souhaitez. Pour cela, ajouter un tag monolog.logger dans vos services :

votre.service:
    class: ACME\AppBundle\Services\DefaultService
    arguments: [@logger]
    tags:
        - { name: monolog.logger, channel: acme_app }

Le channel utilisé pour tous les logs du service sera ici : acme_app.

 

Appeler plusieurs handler à partir d'un seul

Imaginons que vous souhaitez que lorsqu'un log de type critical est lu, un mail vous soit envoyé et qu'il soit écrit dans un fichier avec rotation.

monolog:
    handlers:
        main_critical:
            type:           fingers_crossed
            action_level:   critical
            handler:        grouped
        grouped:
            type:           group
            members:        [streamed, buffered]
        streamed:
            type:           rotating_file
            max_files:      15
            path:           %kernel.logs_dir%/%kernel.environment%.critical.log
            level:          info
        buffered:
            type:           buffer
            handler:        swift
        swift:
            type:           swift_mailer
            from_email:     %email.from%
            to_email:       %email.super_admin%
            subject:        Critical Error Occurred
            level:          error

Et voilà ! Dès qu'un log critical est lu, le handler main_criticale appelle le handler grouped pour chaque log de son buffer (n'oublions pas qu'il est de type finfer_crossed). Le handler grouped qui est de type group va envoyer chaque log vers le handler streamed et vers le handler buffered en même temps. Ces deux handler vont ensuite remplir leurs fonctions : l'un écrire avec rotation de fichier et l'autre stocker dans un buffer avant de l'envoyer au handler qui envoit un mail.

 

Un exemple complet

Voici un exemple que j'utilise généralement sur mes environnements de prod :

monolog:
    handlers:
        main:
            type:           rotating_file
            max_files:      3
            path:           %kernel.logs_dir%/%kernel.environment%.all.log
            level:          info

        login:
            type:           rotating_file
            max_files:      15
            path:           %kernel.logs_dir%/%kernel.environment%.auth.log
            level:          info
            channels:       security

        main_error:
            type:           fingers_crossed
            action_level:   error
            handler:        streamed_error
        streamed_error:
            type:           rotating_file
            max_files:      15
            path:           %kernel.logs_dir%/%kernel.environment%.error.log
            level:          info

        main_critical:
            type:           fingers_crossed
            action_level:   critical
            handler:        grouped_critical
        grouped_critical:
            type:           group
            members:        [streamed_critical, buffered_critical]
        streamed_critical:
            type:           rotating_file
            max_files:      15
            path:           %kernel.logs_dir%/%kernel.environment%.critical.log
            level:          info
        buffered_critical:
            type:           buffer
            handler:        swift_critical
        swift_critical:
            type:           swift_mailer
            from_email:     contact@domain.com
            to_email:       error@my-domain.com
            subject:        Une erreur critique est survenue !
            level:          info

Vous n'avez pas compris ?? Explications :

Le handler main sera déclenché pour tous les logs de niveau supérieur ou égal à info et il écrira à chaque fois le log dans un fichier dans app/logs/prod.all-2015-01-05.log (avec la date du jour).

Le handler login fera la même chose mais seulement pour le channel security (les authentifications) et stocke le tout dans un fichier prod.auth-2015-01-05.log

Les logs de niveau error déclencheront le handler main_error qui appellera streamed_error qui va écrire tout les logs du buffer dans un fichier prod.error-2015-01-05.log

Les logs de niveau critical déclencherons le handler main_critical qui va à la fois écrire tout les logs du buffer dans un fichier prod.critical-2015-01-05.log et à la fois envoyer un mail avec le buffer pour me prévenir de l'erreur survenue (les deux handlers sont déclenchés par le handler group : grouped_critical).

 

C'est fini ! N'hésitez pas à poser vos questions dans les commentaires et à partager cet article ! Merci.

Commentaires

Harry79
22 Juin 2018 15:32

Trop cool et trop bien expliqué. Pour ma part c'est tellement bien fait.
Merci beaucoup.

aguidis
26 Mai 2018 19:31

Merci pour cet article qui est tjrs aussi utile ;)

Someone
29 Mars 2018 12:42

Je confirme, merci pour la doc'

Jassem
29 Mars 2018 10:50

Plus de 3 ans, et toujours très utile,
Merci beaucoup pour ce tuto !

Thinh
29 Novembre 2017 11:31

2 ans après, toujours très utile, merci !

skp
02 Octobre 2017 21:07

Merci beaucoup pour ce tuto et aussi pour ton exemple que j'ai utilisé comme base pour mon projet.

Outre les petits changements, j'ai remplacé le handler:
buffered_critical:
type: buffer
handler: swift_critical

par:
deduplicated_critical:
type: deduplication
time: 15
handler: swift_critical

du coup j’ai modifié le handler:
grouped_critical:
type: group
members: [streamed_critical, deduplicated_critical]


Merci encore.

Draeli
08 Juillet 2017 19:45

Merci pour ce tuto bien plus clair que la documentation officiel, limite tu devrai proposer ta version dessus :)

Krakott
12 Avril 2017 15:07

Merci pour ce partage, très clair, très sympa.

Marco
22 Février 2017 00:19

Merci beaucoup Remi pour ce tuto !
Très clair, et efficace ! :D

Mapito
17 Février 2017 13:43

Rémi,
Cet article est très clair et très utile.

merci :)

Amine
16 Janvier 2017 12:25

Merci pour ce tuto :)

Rémi POIGNON admin
28 Octobre 2016 17:39

#Hybernatus, il faut définir un channel à vos logs persos ou un niveau de criticité bas (debug par exemple). Puis il faut l'exclure, exemple :
main:
type: rotating_file
max_files: 3
path: %kernel.logs_dir%/%kernel.environment%.log
level: info
channels: !perso

Hybernatus
26 Octobre 2016 17:32

Salut, comment je peux faire pour empecher l ecriture de mes logs persos en prod?

Mcsky
07 Octobre 2016 10:19

Tu détailles les fonctionnalités les plus intéressantes de Monolog, très utile merci !

MathieuM
18 Juillet 2016 17:13

Merci, très bien expliqué !

Rémi POIGNON admin
21 Octobre 2015 19:42

#Pierre, il n'existe pas de handle de base permettant de faire ça. En revanche vous pouvez sûrement créer votre propre handle. Une piste ici : http://stackoverflow.com/questions/12935979/custom-monolog-handler-for-default-monolog-in-symfony-2
J'avoue que je n'ai jamais essayé. N'hésitez pas à en reparler si vous en apprenez plus sur le sujet !

Pierre
21 Octobre 2015 14:28

Comment dois je m'y prendre si je souhaites écrire les logs dans une base de données MySQL ?
Je ne trouve rien de concret sur ce besoin ...

Si quelqu'un voit une façon de le faire je suis preneur.

Dans mon cas j'étais parti sur un service loggator qui se chargerai d'écrire chaque log dans une base mais je ne sais pas trop comment m'y prendre, quel type de handle choisir ?

Merci

Cyril
09 Mai 2015 09:46

Merci pour ce tuto vraiment très clair.

Rémi POIGNON admin
08 Mai 2015 16:07

@sebastien, apparemment, les channels sont vérifiés seulement pour les handles de premier niveau. Un handle de premier niveau, s'il est déclenché, appelle ses enfants "de force" sans vérifier qu'ils correspondent ou non au channel.

C'est vrai qu'il aurait pu être appréciable de pouvoir l'utiliser sur les handles enfants aussi mais si je ne me trompe pas, ce n'est pas prévu comme ça ...

Si tu trouve plus d'infos sur le sujet, je suis intéressé !

sebastien
07 Mai 2015 16:40

par hasard saurais-tu s'il y a un moyen de définir les channels au niveau des handlers imbriqués, exemple :

main:
type: fingers_crossed
action_level: error
handler: grouped
grouped:
type: group
members: [streamed_application, streamed_security, streamed_doctrine]
streamed_application:
type: rotating_file
max_files: 15
path: "%kernel.logs_dir%/%kernel.environment%_application.log"
channels: ['!security', '!doctrine']
level: info
streamed_security:
type: rotating_file
max_files: 15
path: "%kernel.logs_dir%/%kernel.environment%_security.log"
channels: ['security']
level: info
streamed_doctrine:
type: rotating_file
max_files: 15
path: "%kernel.logs_dir%/%kernel.environment%_doctrine.log"
channels: ['doctrine']
level: info

car sinon j'ai l'impression que la seule alternative est de créer 3 "main handler" en finger_crossed avec pour chacun leur channel... je trouve ça bizarre que ça ne soit pas possible ou je ne comprends pas bien la logique.

merci d'avance

ypereirareis
05 Mai 2015 13:28

Merci,

C'est très clair.

greg
05 Mai 2015 00:13

Hello,

Je conseil aussi TRES fortement d'activer les logs PHP dans monolog.
J'en ai parlé lors du dernier SFLive: https://speakerdeck.com/lyrixx/symfony-live-2015-paris-monitorer-sa-prod

zogs
13 Mars 2015 11:39

Merci, très utile et bien expliqué !