ネットワーク パフォーマンス モニタリングの正式提供を開始しました! ネットワーク パフォーマンス モニタリング提供開始!

PHP log collection

このページは英語では対応しておりません。随時翻訳に取り組んでいます。翻訳に関してご質問やご意見ございましたら、お気軽にご連絡ください。

Overview

Write your PHP logs into a file, then use the Agent to forward them to Datadog, choose between the Monolog, Zend-Log, or Symfony logging libraries.

Setup

Installation

Use Composer to add Monolog as a dependency:

composer require "monolog/monolog"

Alternatively, install it manually:

  1. Download Monolog from the repository and include it to the libraries
  2. Initialize the instance in the application’s bootstrap

    <?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. Use Composer to add Zend-Log:

composer require "zendframework/zend-log"

Alternatively, install it manually:

  1. Download the source from the repository and include it to the libraries
  2. Initialize the instance in the application’s bootstrap

    <?php
    require __DIR__ . '/vendor/autoload.php';
    
    use Zend\Log\Logger;
    use Zend\Log\Writer\Stream;
    use Zend\Log\Formatter\JsonFormatter;
    ?>
    

Declare a Monolog JSON formatter as a service:

services:
    monolog.json_formatter:
        class: Monolog\Formatter\JsonFormatter

Logger Configuration

The following configuration enables the JSON formatting and writes the logs and events into the application-json.log file. Edit your code, right after the initialization of the Monolog instance and add a new handler:

 <?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. Edit your code, right after the initialization of the Zend-Log instance and add a new handler.

<?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);
?>

Then Stream your log files to Datadog

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

Agent Configuration

Create a php.d/conf.yaml file in your conf.d/ folder with the following content:

init_config:

instances:

## Log section
logs:

  - type: file
    path: "/path/to/your/php/application-json.log"
    service: php
    source: php
    sourcecategory: sourcecode

Adding more context

It’s useful to add additional context data to your logs and events. Monolog makes this convenient by providing methods for setting thread-local context data that is then submitted automatically with all events. At any moment, log an event with contextual data:

<?php
  $logger->info('Adding a new user', array('username' => 'Seldaek'));
?>

Monolog comes with a pre-processor feature. It’s a simple callback that enriches your events with metadata you can set (e.g., the session id, the request id, etc.):

 <?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;
  });
?>

Much of the useful information comes from additional context data that you can add to your logs and your events. Zend-Log makes this very convenient by providing methods to set thread local context data that is then submitted automatically with all events. At any moment, log an event with contextual data:

<?php
  $logger->info('Adding a new user', array('username' => 'Seldaek'));
?>

But, most importantly, the library comes with a Processor feature. Processors allow you to provide additional information to logs in an automated fashion. They are called from the logger before the event is passed to the writers; they receive the event array, and return an event array on completion.

Use cases include:

  • Providing exception backtrace information.
  • Injecting substitutions into the message.
  • Injecting a request identifier (in order to later inspect logs for a specific identifier).

Take a peek to this code if you want to use it:

<?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());
?>

If you want to develop yours, refer the Zend documentation.

Add a session Processor to add variable context within your logs:

  1. Implement your session Processor: This is an example of such a Processor. It knows the current session and it enriches the content of the log record with valuable information such as the requestId, sessionId, …

    <?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;
        }
      }
    ?>
    
  2. Wire the Processor with Symfony:

    services:
      monolog.processor.session_request:
          class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
          arguments:  [ @session ]
          tags:
              - { name: monolog.processor, method: processRecord }
  3. Stream generated JSON file to Datadog.

Monolog Framework integration

Monolog is a part of the following frameworks:

Integrate Monolog with your framework then configure your logger:

 <?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;
?>

Symfony (v2+, v3+)

In your configuration directory /path/to/config/directory/, edit the config_dev.yml and config_prod.yml to configure log management to suit your needs for 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"
            # inclues all channels (doctrine, errors, etc.)
            channels: ~
            # use json formatter
            formatter: monolog.json_formatter
            # logs alls events (debug trought fatal)
            level: debug

PPI

In your configuration directory /path/to/config/directory/, edit the config_dev.yml and config_prod.yml to configure log management to suit your needs for 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
            # logs alls events (debug trought fatal)
            level: debug

Laravel

<?php
  //file: bootstrap/app.php
  $app->configureMonologUsing(function($monolog) {
      $monolog->pushHandler(...);

    // configure your logger below
  });

  return $app;
?>

Silex

<?php
  // file: bootstrap
  $app->extend('monolog', function($monolog, $app) {
      $monolog->pushHandler(...);
      // configure your logger below
      return $monolog;
  });
?>

Lumen

<?php
  //file: bootstrap/app.php
  $app->configureMonologUsing(function($monolog) {
      $monolog->pushHandler(...);
      // configure your logger below
  });

  return $app;
?>

CakePHP

First, add this dependency to the composer.json file and run composer update.

{"require": {"cakephp/monolog": "*"}}

Then, start by creating a logging configuration file (i.e., app/Config/log.php) that you includes your app/Config/bootstrap.php:

<?php
  include 'log.php';
?>

A basic configuration, to replicate what Cake does but using Monolog would look something like this:

CakePlugin::load('Monolog');

Finally log into a file:

CakeLog::config('debug', array(
  'engine' => 'Monolog.Monolog',
  'channel' => 'app',
  'handlers' => array(
    'Stream' => array(
      LOGS . 'application-json.log',
      'formatters' => array(
        'Json' => array("")
      )
    )
  )
));

Further Reading