Rédigez vos logs PHP dans un fichier, puis utilisez l’Agent pour les transmettre à Datadog. Vous avez la possibilité de choisir parmi les bibliothèques de journalisation suivantes : Monolog, Zend-Log ou Symfony.
Utilisez Composer pour ajouter Monolog en tant que dépendance :
composer require "monolog/monolog"
Vous pouvez également l’installer manuellement :
Téléchargez Monolog depuis le référentiel et ajoutez-le aux bibliothèques.
Lancez l’instance dans le bootstrap de l’application.
<?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. Utilisez Composer pour ajouter Zend-Log :
composer require "zendframework/zend-log"
Vous pouvez également l’installer manuellement :
<?php
require __DIR__ . '/vendor/autoload.php';
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
Déclarez 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
. Juste après avoir lancé l’instance Monolog, modifiez votre code afin d’ajouter 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
. Juste après avoir lancé l’instance Zend-log, modifiez votre code afin d’ajouter 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);
Configurez le formateur dans votre configuration Monolog en déclarant le champ formatter comme suit :
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.json_formatter
Associer vos logs à vos traces
Si l’APM est activé 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 l’APM afin d’ajouter automatiquement des identifiants de trace et de span à vos logs.
Créez un fichier php.d/conf.yaml
dans votre dossier conf.d/
avec le contenu suivant :
init_config:
instances:
## Section logs
logs:
- type: file
path: "/chemin/vers/votre/php/application-json.log"
service: php
source: php
sourcecategory: sourcecode
L’ajout de données de contexte à vos logs et événements est particulièrement utile. Monolog rend cette opération simple en proposant 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. À tout moment, il vous est possible de loguer un événement accompagné de données de contexte :
<?php
$logger->info('Ajout d'un nouvel utilisateur', array('username' => 'Seldaek'));
Monolog intègre un préprocesseur. Il s’agit d’un rappel simple qui enrichit vos événements en ajoutant les métadonnées de votre choix (ID de la session, ID de la requête, etc.) :
<?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;
});
La majorité des informations utiles proviennent des données de contexte supplémentaire que vous pouvez ajouter à vos logs et événements. Zend-Log rend cette opération simple en proposant différents moyens de définir des données de contexte propres à chaque thread, qui sont ensuite automatiquement envoyées avec chaque événement. À tout moment, il vous est possible de loguer un événement accompagné de données de contexte :
<?php
$logger->info('Ajout d'un nouvel utilisateur', array('username' => 'Seldaek'));
Plus utile encore, la bibliothèque intègre également un processeur. Les processeurs vous permettent d’ajouter des informations supplémentaires à vos logs de façon automatisée. Ils sont appelés par le logger avant que l’événement ne soit transmis au service d’écriture ; ils reçoivent alors le tableau des événements, puis renvoient un tableau des événements une fois l’opération terminée.
Exemples de cas d’utilisation :
Vous pouvez utiliser le code suivant si vous le souhaitez :
<?php
$logger->addProcessor(new Zend\Log\Processor\Backtrace());
$logger->addProcessor(new Zend\Log\Processor\PsrPlaceholder());
$logger->addProcessor(new Zend\Log\Processor\ReferenceId());
$logger->addProcessor(new Zend\Log\Processor\RequestId());
Si vous souhaitez rédiger votre propre code, consultez la documentation relative à Zend (en anglais).
Ajoutez un processeur de session pour inclure des données de contexte variables dans vos logs :
Implémentez votre processeur de session :
Voici un exemple de processeur. Ce dernier connaît la session actuelle et enrichit l’entrée de log en y ajoutant des informations utiles 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;
}
}
Connectez le processeur à Symfony :
services:
monolog.processor.session_request:
class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
arguments: [ @session ]
tags:
- { name: monolog.processor, method: processRecord }
Monolog est intégré aux frameworks suivants :
Intégrez Monolog à votre framework, puis configurez votre logger :
<?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;
Dans votre répertoire de configuration /chemin/vers/répertoire/configuration/
, modifiez les fichiers config_dev.yml
et config_prod.yml
afin de configurer la gestion des logs en fonction de vos besoins sur vos environnements de développement et de production.
# app/config/config.yml
monolog:
# Retirez la mise en commentaire de cette section si vous souhaitez utiliser 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"
# Inclure tous les canaux (doctrine, erreurs, etc.)
channels: ~
# Utiliser le formateur JSON
formatter: monolog.json_formatter
# Loguer tous les événements (des données de debugging aux erreurs fatales)
level: debug
Dans votre répertoire de configuration /chemin/vers/répertoire/configuration/
, modifiez les fichiers config_dev.yml
et config_prod.yml
afin de configurer la gestion des logs en fonction de vos besoins sur 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
# Loguer tous les événements (des données de debugging aux erreurs fatales)
level: debug
<?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;
}
// Faculatif : utiliser une mise en forme 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 afin d'associer l'entrée de log à la trace APM
$monolog->pushProcessor(function ($record) use ($useJson) {
$span = \DDTrace\GlobalTracer::get()->getActiveSpan();
if (null === $span) {
return $record;
}
if ($useJson === true) {
$record['dd'] = [
'trace_id' => $span->getTraceId(),
'span_id' => \dd_trace_peek_span_id(),
];
} else {
$record['message'] .= sprintf(
' [dd.trace_id=%d dd.span_id=%d]',
$span->getTraceId(),
\dd_trace_peek_span_id()
);
}
return $record;
});
}
/**
* Bootstrap des services d'application
*
* @return void
*/
public function boot()
{
//
}
}
<?php
// file: bootstrap
$app->extend('monolog', function($monolog, $app) {
$monolog->pushHandler(...);
// Configurer votre logger ci-dessous
return $monolog;
});
<?php
//file: bootstrap/app.php
$app->configureMonologUsing(function($monolog) {
$monolog->pushHandler(...);
// Configurer votre logger ci-dessous
});
return $app;
Commencez par ajouter la dépendance suivante au fichier composer.json
,
puis exécutez composer update
.
{"require": {"cakephp/monolog": "*"}}
Créez ensuite un fichier de configuration de journalisation (p. ex., app/Config/log.php
) et ajoutez-y votre app/Config/bootstrap.php
:
<?php
include 'log.php';
Voici un exemple de configuration basique permettant de reproduire les fonctionnalités de Cake avec Monolog :
CakePlugin::load('Monolog');
Enfin, enregistrez les logs dans un fichier :
CakeLog::config('debug', array(
'engine' => 'Monolog.Monolog',
'channel' => 'app',
'handlers' => array(
'Stream' => array(
LOGS . 'application-json.log',
'formatters' => array(
'Json' => array("")
)
)
)
));
Documentation, liens et articles supplémentaires utiles: