Overview
To send your PHP logs to Datadog, log to a file and then tail that file with your Datadog Agent. This page details setup examples for the Monolog, Zend-Log, and Symfony logging libraries.
Setup
Installation
Run this command to use Composer to add Monolog as a dependency:
composer require "monolog/monolog"
Alternatively, install Monolog manually by doing the following:
Download Monolog from the repository and include it in the libraries.
Add the following in the application’s bootstrap to initialize the instance:
<?php
require __DIR__ . '/vendor/autoload.php';
// load Monolog library
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
Zend-Log is a part of the Zend framework. Run this command to use Composer to add Zend-Log:
composer require "zendframework/zend-log"
Alternatively, install Zend-Log manually by doing the following:
- Download the source from the repository and include it in the libraries.
- Add the following the application’s bootstrap to initialize the instance:
<?php
require __DIR__ . '/vendor/autoload.php';
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
Add the following to declare a Monolog JSON formatter as a service:
services:
monolog.json_formatter:
class: Monolog\Formatter\JsonFormatter
The following configuration enables JSON formatting and writes the logs and events into the application-json.log
file. In your code, add a new handler after the initialization of the Monolog instance:
<?php
require __DIR__ . '/vendor/autoload.php';
// load Monolog library
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
// create a log channel
$log = new Logger('channel_name');
// create a Json formatter
$formatter = new JsonFormatter();
// create a handler
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind
$log->pushHandler($stream);
// an example
$log->info('Adding a new user', array('username' => 'Seldaek'));
The following configuration enables the JSON formatting and writes the logs and events into the application-json.log
file. In your code, add a new handler after the initialization of the Zend-Log instance.
<?php
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
// create a logger
$logger = new Logger();
// create a writer
$writer = new Stream('file://' . __DIR__ . '/application-json.log');
// create a Json formatter
$formatter = new JsonFormatter();
$writer->setFormatter($formatter);
// bind
$logger->addWriter($writer);
Zend\Log\Logger::registerErrorHandler($logger);
To configure the formatter in your Monolog configuration, declare the formatter field as follows:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.json_formatter
Once log collection is enabled, do the following to set up custom log collection to tail your log files and send new logs to Datadog.
- Create a
php.d/
folder in the conf.d/
Agent configuration directory. - Create a
conf.yaml
file in php.d/
with the following content:
init_config:
instances:
## Log section
logs:
- type: file
path: "<path_to_your_php_application_json>.log"
service: "<service_name>"
source: php
sourcecategory: sourcecode
Connect your services across logs and traces
If APM is enabled for this application, the correlation between application logs and traces can be improved by following the APM PHP logging instructions to automatically add trace and span IDs in your logs.
Add more context to logs
It can be useful to add additional context to your logs and events. Monolog provides methods for setting thread-local context that is then submitted automatically with all events. For example, to log an event with contextual data:
<?php
$logger->info('Adding a new user', array('username' => 'Seldaek'));
Monolog’s pre-processor has a feature that is a simple callback and enriches your events with metadata you can set (for example, the session ID, or the request id):
<?php
$log->pushProcessor(function ($record) {
// record the current user
$user = Acme::getCurrentUser();
$record['context']['user'] = array(
'name' => $user->getName(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
);
// Add various tags
$record['ddtags'] = array('key' => 'value');
// Add various generic context
$record['extra']['key'] = 'value';
return $record;
});
It can be useful to add additional context to your logs and events. Zend-Log provides methods to set thread-local context that is then submitted automatically with all events. For example, to log an event with contextual data:
<?php
$logger->info('Adding a new user', array('username' => 'Seldaek'));
See Zend’s Processor documentation for more information on providing additional information to your logs.
Follow these steps to add variable context in your logs using a session processor.
Implement your session processor:
In the following example, the processor knows the current session and enriches the content of the log record with information such as the requestId
, sessionId
, and so on.
<?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;
}
}
Integrate the processor with Symfony by adding the following:
services:
monolog.processor.session_request:
class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
arguments: [ @session ]
tags:
- { name: monolog.processor, method: processRecord }
Stream the generated JSON file to Datadog.
Monolog framework integration
Monolog can be used with the following frameworks:
To integrate Monolog with your framework, add the following:
<?php
// Check if the Monolog library is well loaded
//use Monolog\Logger;
//use Monolog\Handler\StreamHandler;
//use Monolog\Formatter\JsonFormatter;
// with the monolog instance
$monolog = ...
///// Log shipper configuration
$formatter = new JsonFormatter();
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
$monolog->pushHandler($stream);
return $r;
Then, configure your logger for Monolog.
In your configuration directory /path/to/config/directory/
, add the following to the config_dev.yml
and config_prod.yml
. Modify the example to configure it for your development and production environments.
# app/config/config.yml
monolog:
# Uncomment this section, if you want to use a Processor
# Processor :
# session_processor:
# class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
# arguments: [ @session ]
# tags:
# - { name: monolog.processor, method: processRecord }
json_formatter:
class: Monolog\Formatter\JsonFormatter
handlers:
# Log shipper configuration
to_json_files:
# log to var/logs/(environment).log
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# includes all channels (doctrine, errors, and so on)
channels: ~
# use json formatter
formatter: monolog.json_formatter
# set the log level (for example: debug, error, or alert)
level: debug
In your configuration directory /path/to/config/directory/
, add the following to the config_dev.yml
and config_prod.yml
. Modify the example to configure it for your development and production environments.
monolog:
handlers:
# Log shipper configuration
to_json_files:
# log to var/logs/(environment).log
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# use json formatter
formatter: monolog.json_formatter
# set the log level (for example: debug, error, or alert)
level: debug
The function
\DDTrace\current_context()
has been introduced in version
0.61.0.
Add the following:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
// Get the Monolog instance
$monolog = logger()->getLogger();
if (!$monolog instanceof \Monolog\Logger) {
return;
}
// Optional: Use JSON formatting
$useJson = false;
foreach ($monolog->getHandlers() as $handler) {
if (method_exists($handler, 'setFormatter')) {
$handler->setFormatter(new \Monolog\Formatter\JsonFormatter());
$useJson = true;
}
}
// Inject the trace and span ID to connect the log entry with the APM trace
$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 any application services.
*
* @return void
*/
public function boot()
{
//
}
}
Add the following:
<?php
// file: bootstrap
$app->extend('monolog', function($monolog, $app) {
$monolog->pushHandler(...);
// configure your logger below
return $monolog;
});
Add the following:
<?php
//file: bootstrap/app.php
$app->configureMonologUsing(function($monolog) {
$monolog->pushHandler(...);
// configure your logger below
});
return $app;
Further Reading
Additional helpful documentation, links, and articles: