---
title: PHP Log Collection
description: Datadog, the leading service for cloud-scale monitoring.
breadcrumbs: Docs > Log Management > Log Collection and Integrations > PHP Log Collection
---

# PHP Log Collection

## Overview{% #overview %}

To send your PHP logs to Datadog, log to a file and then [tail](https://docs.datadoghq.com/glossary/#tail) that file with your Datadog Agent. This page details setup examples for the [Monolog](https://seldaek.github.io/monolog/), [Zend-Log](https://framework.zend.com/), and [Symfony](https://symfony.com/) logging libraries.

## Setup{% #setup %}

### Installation{% #installation %}

{% tab title="PHP Monolog" %}
Run this command to use [Composer](https://getcomposer.org) to add Monolog as a dependency:

```text
composer require "monolog/monolog"
```

Alternatively, install Monolog manually by doing the following:

1. Download Monolog from the repository and include it in the libraries.

1. Add the following in the application's bootstrap to initialize the instance:

   ```php
   <?php
     require __DIR__ . '/vendor/autoload.php';
   
     // load Monolog library
     use Monolog\Logger;
     use Monolog\Handler\StreamHandler;
     use Monolog\Formatter\JsonFormatter;
   ```

{% /tab %}

{% tab title="PHP Zend-Log" %}
Zend-Log is a part of the Zend framework. Run this command to use [Composer](https://getcomposer.org) to add Zend-Log:

```text
composer require "zendframework/zend-log"
```

Alternatively, install Zend-Log manually by doing the following:

1. Download the source from the repository and include it in the libraries.
1. Add the following the application's bootstrap to initialize the instance:

```php
<?php
  require __DIR__ . '/vendor/autoload.php';

  use Zend\Log\Logger;
  use Zend\Log\Writer\Stream;
  use Zend\Log\Formatter\JsonFormatter;
```

{% /tab %}

{% tab title="PHP Symfony" %}
Add the following to declare a Monolog JSON formatter as a service:

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

{% /tab %}

### Configure your logger{% #configure-your-logger %}

{% tab title="PHP Monolog" %}
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
 <?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'));
```

{% /tab %}

{% tab title="PHP Zend-Log" %}
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
<?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);
```

{% /tab %}

{% tab title="PHP Symfony" %}
To configure the formatter in your Monolog configuration, declare the formatter field as follows:

```yaml
 monolog:
    handlers:
        main:
            type:   stream
            path:   "%kernel.logs_dir%/%kernel.environment%.log"
            level:  debug
            formatter: monolog.json_formatter
```

{% /tab %}

### Configure the Datadog Agent{% #configure-the-datadog-agent %}

Once [log collection is enabled](https://docs.datadoghq.com/agent/logs/?tab=tailfiles#activate-log-collection), do the following to set up [custom log collection](https://docs.datadoghq.com/agent/logs/?tab=tailfiles#custom-log-collection) to tail your log files and send new logs to Datadog.

1. Create a `php.d/` folder in the `conf.d/` [Agent configuration directory](https://docs.datadoghq.com/agent/configuration/agent-configuration-files/?tab=agentv6v7#agent-configuration-directory).
1. Create a `conf.yaml` file in `php.d/` with the following content:

```yaml
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{% #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](https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/php/) to automatically add trace and span IDs in your logs.

## Add more context to logs{% #add-more-context-to-logs %}

{% tab title="PHP Monolog" %}
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
<?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
 <?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;
  });
```

{% /tab %}

{% tab title="PHP Zend-Log" %}
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
<?php
  $logger->info('Adding a new user', array('username' => 'Seldaek'));
```

See [Zend's Processor documentation](https://docs.zendframework.com/zend-log/processors) for more information on providing additional information to your logs.
{% /tab %}

{% tab title="PHP Symfony" %}
Follow these steps to add variable context in your logs using a session processor.

1. 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
   <?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;
       }
     }
   ```

1. Integrate the processor with Symfony by adding the following:

   ```yaml
     services:
         monolog.processor.session_request:
             class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
             arguments:  [ @session ]
             tags:
                 - { name: monolog.processor, method: processRecord }
   ```

1. Stream the generated JSON file to Datadog.

{% /tab %}

## Monolog framework integration{% #monolog-framework-integration %}

Monolog can be used with the following frameworks:

- [Symfony v2+/v3+](https://symfony.com/doc/current/logging.html#monolog)
- [PPI](https://github.com/ppi/ppi-monolog-module)
- [Laravel](https://laravel.com/docs/9.x/logging#introduction)
- [Silex](https://github.com/silexphp/Silex)
- [Lumen](https://lumen.laravel.com/docs/9.x)

To integrate Monolog with your framework, add the following:

```php
 <?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.

{% tab title="Symfony v2+/v3+" %}
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.

```yaml
# 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
```

{% /tab %}

{% tab title="PPI" %}
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.

```yaml
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
```

{% /tab %}

{% tab title="Laravel" %}

{% alert level="danger" %}
The function `\DDTrace\current_context()` has been introduced in version [0.61.0](https://github.com/DataDog/dd-trace-php/releases/tag/0.61.0).
{% /alert %}

Add the following:

```php
<?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()
    {
        //
    }
}
```

{% /tab %}

{% tab title="Silex" %}
Add the following:

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

{% /tab %}

{% tab title="Lumen" %}
Add the following:

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

  return $app;
```

{% /tab %}

## Further Reading{% #further-reading %}

- [How to collect, customize, and analyze PHP logs](https://www.datadoghq.com/blog/php-logging-guide)
- [Learn how to process your logs](https://docs.datadoghq.com/logs/log_configuration/processors)
- [Learn more about parsing](https://docs.datadoghq.com/logs/log_configuration/parsing)
- [Learn how to explore your logs](https://docs.datadoghq.com/logs/explorer/)
- [Perform Log Analytics](https://docs.datadoghq.com/logs/explorer/#visualize)
- [Log Collection Troubleshooting Guide](https://docs.datadoghq.com/logs/faq/log-collection-troubleshooting-guide)
- [Glossary entry for "tail"](https://docs.datadoghq.com/glossary/#tail)
