Collecte de logs avec PHP
Présentation
Pour envoyer vos logs PHP à Datadog, activez la journalisation au sein d’un fichier et suivez ce fichier avec l’Agent Datadog. Les exemples de configuration ci-dessous utilisent les bibliothèques de journalisation Monolog, Zend-Log et Symfony.
Implémentation
Installation
Exécutez la commande suivante pour utiliser Composer afin d’ajouter Monolog en tant que dépendance :
composer require "monolog/monolog"
Sinon, suivez les instructions ci-dessous pour installer manuellement Monolog :
Téléchargez Monolog depuis le référentiel et ajoutez-le aux bibliothèques.
Ajoutez ce qui suit dans le bootstrap de l’application afin d’initialiser l’instance :
<?php
require __DIR__ . '/vendor/autoload.php';
// load Monolog library
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
Zend-Log fait partie du framework Zend. Exécutez la commande suivante pour utiliser Composer afin d’ajouter Zend-Log :
composer require "zendframework/zend-log"
Sinon, suivez les instructions ci-dessous pour installer manuellement Zend-Log :
- Téléchargez la source depuis le référentiel et ajoutez-la aux bibliothèques.
- Ajoutez ce qui suit dans le bootstrap de l’application afin d’initialiser l’instance :
<?php
require __DIR__ . '/vendor/autoload.php';
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
Ajoutez ce qui suit pour déclarer un formateur JSON Monolog en tant que service :
services:
monolog.json_formatter:
class: Monolog\Formatter\JsonFormatter
Utilisez la configuration ci-dessous pour activer le format JSON et enregistrer les logs et les événements dans le fichier application-json.log
. Après avoir lancé l’instance Monolog, ajoutez dans votre code un nouveau gestionnaire :
<?php
require __DIR__ . '/vendor/autoload.php';
// Charger la bibliothèque Monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
// Créer un canal pour l'enregistrement des logs
$log = new Logger('channel_name');
// Créer un formateur JSON
$formatter = new JsonFormatter();
// Créer un gestionnaire
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// Connexion
$log->pushHandler($stream);
// Un exemple
$log->info('Ajout d'un nouvel utilisateur', array('username' => 'Seldaek'));
Utilisez la configuration ci-dessous pour activer le format JSON et enregistrer les logs et les événements dans le fichier application-json.log
. Après avoir lancé l’instance Zend-Log, ajoutez dans votre code un nouveau gestionnaire :
<?php
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
// Créer un logger
$logger = new Logger();
// Créer un service d'écriture
$writer = new Stream('file://' . __DIR__ . '/application-json.log');
// Créer un formateur JSON
$formatter = new JsonFormatter();
$writer->setFormatter($formatter);
// Connexion
$logger->addWriter($writer);
Zend\Log\Logger::registerErrorHandler($logger);
Pour configurer le formateur dans votre configuration Monolog, déclarez le champ formatter comme suit :
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.json_formatter
Une fois la collecte de logs activée, procédez comme suit pour configurer la collecte de logs personnalisée afin de suivre vos fichiers de log et envoyer les nouveaux logs à Datadog.
- Créez un dossier
php.d/
dans le répertoire de configuration de l’Agent conf.d/
. - Créez un fichier
conf.yaml
dans votre dossier php.d/
avec le contenu suivant :
init_config:
instances:
## Log section
logs:
- type: file
path: "<path_to_your_php_application_json>.log"
service: "<service_name>"
source: php
sourcecategory: sourcecode
Associer vos services à l’ensemble des logs et traces
Si la solution APM est activée pour cette application, vous pouvez améliorer la corrélation entre vos logs et vos traces d’application en suivant les instructions de journalisation PHP pour APM. Cela vous permet d’ajouter automatiquement des identifiants de trace et de span à vos logs.
Enrichir le contexte des logs
Il peut être utile d’enrichir le contexte de vos logs et événements. Monolog propose différents moyens de définir des données de contexte propres à chaque thread, qui sont ensuite automatiquement envoyées avec tous les événements. Par exemple, pour loguer un événement accompagné de données de contexte, utilisez ce qui suit :
<?php
$logger->info('Ajout d'un nouvel utilisateur', array('username' => 'Seldaek'));
Le préprocesseur de Monolog comporte une fonctionnalité de rappel simple qui enrichit vos événements en ajoutant les métadonnées de votre choix (par exemple, l’ID de session ou l’ID de requête) :
<?php
$log->pushProcessor(function ($record) {
// Enregistrer l'utilisateur actuel
$user = Acme::getCurrentUser();
$record['context']['user'] = array(
'name' => $user->getName(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
);
// Ajouter différents tags
$record['ddtags'] = array('key' => 'value');
// Ajouter des données de contexte générales
$record['extra']['key'] = 'value';
return $record;
});
Il peut être utile d’enrichir le contexte de vos logs et événements. Zend-Log propose différents moyens de définir des données de contexte propres à chaque thread, qui sont ensuite automatiquement envoyées avec tous les événements. Par exemple, pour loguer un événement accompagné de données de contexte, utilisez ce qui suit :
<?php
$logger->info('Ajout d'un nouvel utilisateur', array('username' => 'Seldaek'));
Consultez la documentation Zend sur le processeur (en anglais) pour en savoir plus sur l’ajout d’informations supplémentaires dans vos logs.
Suivez les étapes ci-dessous pour ajouter un contexte variable à vos logs à l’aide d’un processeur de session.
Implémentez votre processeur de session :
Dans l’exemple ci-dessous, le processeur a accès aux informations de la session actuelle et enrichit l’entrée de log en y ajoutant des données telles que les attributs requestId
, sessionId
, etc.
<?php
namespace Acme\Bundle\MonologBundle\Log;
use Symfony\Component\HttpFoundation\Session\Session;
class SessionRequestProcessor {
private $session;
private $sessionId;
private $requestId;
private $_server;
private $_get;
private $_post;
public function __construct(Session $session) {
$this->session = $session;
}
public function processRecord(array $record) {
if (null === $this->requestId) {
if ('cli' === php_sapi_name()) {
$this->sessionId = getmypid();
} else {
try {
$this->session->start();
$this->sessionId = $this->session->getId();
} catch (\RuntimeException $e) {
$this->sessionId = '????????';
}
}
$this->requestId = substr(uniqid(), -8);
$this->_server = array(
'http.url' => (@$_SERVER['HTTP_HOST']).'/'.(@$_SERVER['REQUEST_URI']),
'http.method' => @$_SERVER['REQUEST_METHOD'],
'http.useragent' => @$_SERVER['HTTP_USER_AGENT'],
'http.referer' => @$_SERVER['HTTP_REFERER'],
'http.x_forwarded_for' => @$_SERVER['HTTP_X_FORWARDED_FOR']
);
$this->_post = $this->clean($_POST);
$this->_get = $this->clean($_GET);
}
$record['http.request_id'] = $this->requestId;
$record['http.session_id'] = $this->sessionId;
$record['http.url'] = $this->_server['http.url'];
$record['http.method'] = $this->_server['http.method'];
$record['http.useragent'] = $this->_server['http.useragent'];
$record['http.referer'] = $this->_server['http.referer'];
$record['http.x_forwarded_for'] = $this->_server['http.x_forwarded_for'];
return $record;
}
protected function clean($array) {
$toReturn = array();
foreach(array_keys($array) as $key) {
if (false !== strpos($key, 'password')) {
// Do not add
} else if (false !== strpos($key, 'csrf_token')) {
// Do not add
} else {
$toReturn[$key] = $array[$key];
}
}
return $toReturn;
}
}
Ajoutez ce qui suit pour intégrer le processeur avec Symfony :
services:
monolog.processor.session_request:
class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
arguments: [ @session ]
tags:
- { name: monolog.processor, method: processRecord }
Diffusez le fichier JSON généré à Datadog.
Intégration de Monolog à un framework
Monolog peut être utilisé avec les frameworks suivants :
Ajoutez ce qui suit pour intégrer Monolog à votre framework :
<?php
// Vérifier que la bibliothèque Monolog est bien chargée
//use Monolog\Logger;
//use Monolog\Handler\StreamHandler;
//use Monolog\Formatter\JsonFormatter;
// Avec l'instance Monolog suivante
$monolog = ...
///// Configuration du log shipper
$formatter = new JsonFormatter();
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
$monolog->pushHandler($stream);
return $r;
Configurez ensuite votre logger pour Monolog.
Dans votre répertoire de configuration /chemin/vers/répertoire/configuration/
, ajoutez ce qui suit aux fichiers config_dev.yml
et config_prod.yml
. Modifiez l’exemple afin d’adapter la configuration à vos environnements de développement et de production.
# app/config/config.yml
monolog:
# Supprimer la mise en commentaire de cette section si vous avez besoin d'un processeur.
# Processor :
# session_processor:
# class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
# arguments: [ @session ]
# tags:
# - { name: monolog.processor, method: processRecord }
json_formatter:
class: Monolog\Formatter\JsonFormatter
handlers:
# Configuration du log shipper
to_json_files:
# Enregistrer les logs dans var/logs/(environment).log
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# Inclut tous les canaux (doctrine, erreurs, etc.)
channels: ~
# Utiliser le formatteur JSON
formatter: monolog.json_formatter
# Définir le niveau de journalisation (par exemple, debug, error ou alert)
level: debug
Dans votre répertoire de configuration /chemin/vers/répertoire/configuration/
, ajoutez ce qui suit aux fichiers config_dev.yml
et config_prod.yml
. Modifiez l’exemple afin d’adapter la configuration à vos environnements de développement et de production.
monolog:
handlers:
# Configuration du log shipper
to_json_files:
# Enregistrer les logs dans var/logs/(environment).log
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# Utiliser le formateur JSON
formatter: monolog.json_formatter
# Définir le niveau de journalisation (par exemple, debug, error ou alert)
level: debug
La fonction
\DDTrace\current_context()
a été ajoutée avec la version
0.61.0.
Ajoutez ce qui suit :
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Enregistrer des services d'application.
*
* @return void
*/
public function register()
{
// Récupérer l'instance Monolog
$monolog = logger()->getLogger();
if (!$monolog instanceof \Monolog\Logger) {
return;
}
// Facultatif : utiliser le format JSON
$useJson = false;
foreach ($monolog->getHandlers() as $handler) {
if (method_exists($handler, 'setFormatter')) {
$handler->setFormatter(new \Monolog\Formatter\JsonFormatter());
$useJson = true;
}
}
// Injecter l'ID de trace et de span pour associer l'entrée de log à la trace APM
$monolog->pushProcessor(function ($record) use ($useJson) {
$context = \DDTrace\current_context();
if ($useJson === true) {
$record['extra']['dd'] = [
'trace_id' => $context['trace_id'],
'span_id' => $context['span_id'],
];
} else {
$record['message'] .= sprintf(
' [dd.trace_id=%d dd.span_id=%d]',
$context['trace_id'],
$context['span_id']
);
}
return $record;
});
}
/**
* Bootstrap des services d'application.
*
* @return void
*/
public function boot()
{
//
}
}
Ajoutez ce qui suit :
<?php
// file: bootstrap
$app->extend('monolog', function($monolog, $app) {
$monolog->pushHandler(...);
// Configurer votre logger ci-dessous
return $monolog;
});
Ajoutez ce qui suit :
<?php
//file: bootstrap/app.php
$app->configureMonologUsing(function($monolog) {
$monolog->pushHandler(...);
// Configurer votre logger ci-dessous
});
return $app;
Pour aller plus loin
Documentation, liens et articles supplémentaires utiles: