Información general

Para enviar tus logs de PHP a Datadog, loguea un archivo y luego supervisa ese archivo con tu Datadog Agent. En esta página, se detallan ejemplos de configuración para las bibliotecas de registro Monolog, Zend-Log y Symfony.

Configuración

Instalación

Ejecuta este comando para utilizar Composer para añadir Monolog como una dependencia:

composer require "monolog/monolog"

Alternativamente, instala Monolog manualmente de la siguiente manera:

  1. Descarga Monolog del repositorio e inclúyelo en las bibliotecas.

  2. Añade lo siguiente en el arranque de la aplicación para inicializar la instancia:

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

Zend-Log forma parte del marco Zend. Ejecuta este comando para utilizar Composer para añadir Zend-Log:

composer require "zendframework/zend-log"

Alternativamente, instala Zend-Log manualmente de la siguiente manera:

  1. Descarga la fuente del repositorio e inclúyela en las bibliotecas.
  2. Añade lo siguiente al arranque de la aplicación para inicializar la instancia:
<?php
  require __DIR__ . '/vendor/autoload.php';

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

Añade lo siguiente para declarar un formateador JSON de Monolog como un servicio:

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

Configurar tu logger

La siguiente configuración habilita el formato JSON y redacta los logs y eventos en el archivo application-json.log. En tu código, añade un nuevo manejador después de la inicialización de la instancia de Monolog:

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

  // cargar biblioteca Monolog
  use Monolog\Logger;
  use Monolog\Handler\StreamHandler;
  use Monolog\Formatter\JsonFormatter;

  // crear un canal de log
  $log = new Logger('channel_name');

  // crear un formateador de Json
  $formatter = new JsonFormatter();

  // crear un manejador
  $stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
  $stream->setFormatter($formatter);

  // vincular
  $log->pushHandler($stream);

  // un ejemplo
  $log->info('Adding a new user', array('username' => 'Seldaek'));

La siguiente configuración habilita el formato JSON y redacta los logs y eventos en el archivo application-json.log. En tu código, añade un nuevo manejador después de la inicialización de la instancia de Zend-Log:

<?php
  use Zend\Log\Logger;
  use Zend\Log\Writer\Stream;
  use Zend\Log\Formatter\JsonFormatter;

  // crear un registrador
  $logger = new Logger();

  // crear un escritor
  $writer = new Stream('file://' . __DIR__ . '/application-json.log');

  // crear un formateador Json
  $formatter = new JsonFormatter();
  $writer->setFormatter($formatter);

  // vincular
  $logger->addWriter($writer);
  Zend\Log\Logger::registerErrorHandler($logger);

Para configurar el formateador en tu configuración de Monolog, declara el campo de formateador de la siguiente manera:

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

Configurar el Datadog Agent

Cuando tengas la recopilación de logs activada, haz la siguiente configuración de la recopilación de logs personalizada para supervisar tus archivos de log y enviar logs nuevos a Datadog.

  1. Crea una carpeta php.d/ en el directorio de configuración del Agent conf.d/.
  2. Crea un archivo conf.yaml en php.d/ con el siguiente contenido:
init_config:

instances:

## Sección de log
logs:

  - type: file
    path: "<path_to_your_php_application_json>.log"
    service: "<service_name>"
    source: php
    sourcecategory: sourcecode

Conectar tus servicios entre logs y trazas (traces)

Si APM está habilitado para esta aplicación, la correlación entre los logs de aplicación y trazas puede mejorarse siguiendo las instrucciones de registro de APM PHP para añadir automáticamente trazas e IDs de tramos en tus logs.

Añadir más contexto a los logs

Puede ser útil añadir contexto adicional a tus logs y eventos. Monolog proporciona métodos para establecer el contexto local del subproceso que luego se envía automáticamente con todos los eventos. Por ejemplo, para loguear un evento con datos contextuales:

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

El preprocesador de Monolog tiene una función que es una devolución de llamada simple y enriquece tus eventos con metadatos que puedes establecer (por ejemplo, el ID de sesión, o el ID de solicitud):

 <?php
  $log->pushProcessor(function ($record) {

      // registrat el usuario actual
      $user = Acme::getCurrentUser();
      $record['context']['user'] = array(
          'name' => $user->getName(),
          'username' => $user->getUsername(),
          'email' => $user->getEmail(),
      );

      // Añadir varias etiquetas
      $record['ddtags'] = array('key' => 'value');

      // Añadir varios contextos genéricos
      $record['extra']['key'] = 'value';

      return $record;
  });

Puede ser útil añadir contexto adicional a tus logs y eventos. Zend-Log proporciona métodos para establecer el contexto local del subproceso que luego se envía automáticamente con todos los eventos. Por ejemplo, para loguear un evento con datos contextuales:

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

Consulta la documentación del procesador de Zend para obtener más información sobre cómo proporcionar información adicional a tus logs.

Sigue estos pasos para añadir contexto variable en tus logs utilizando un procesador de sesión.

  1. Implementar tu procesador de sesiones: En el siguiente ejemplo, el procesador conoce la sesión actual y mejora el contenido del registro de log con información como 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;
        }
      }
    
  2. Integrar el procesador con Symfony añadiendo lo siguiente:

      services:
          monolog.processor.session_request:
              class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
              arguments:  [ @session ]
              tags:
                  - { name: monolog.processor, method: processRecord }
    
  3. Envía en un flujo el archivo JSON generado a Datadog.

Integración del marco de Monolog

Monolog puede utilizarse con los siguientes marcos:

Para integrar Monolog con tu marco, añade lo siguiente:

 <?php
  // Comprueba si la biblioteca de Monolog está bien cargada
  //use Monolog\Logger;
  //use Monolog\Handler\StreamHandler;
  //use Monolog\Formatter\JsonFormatter;

  // con la instancia de monolog
  $monolog = ...

  ///// Configuración de envío de log

  $formatter = new JsonFormatter();
  $stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
  $stream->setFormatter($formatter);

  $monolog->pushHandler($stream);
  return $r;

A continuación, configura tu registrador para Monolog.

En tu directorio de configuración /path/to/config/directory/, añade lo siguiente a config_dev.yml y config_prod.yml. Modifica el ejemplo para configurarlo para tus entornos de desarrollo y producción.

# app/config/config.yml
monolog:

# Deja sin comentarios esta sección si deseas usar un procesador
#       Processor :
#           session_processor:
#               class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
#            arguments:  [ @session ]
#            tags:
#               - { name: monolog.processor, method: processRecord }

    json_formatter:
        class: Monolog\Formatter\JsonFormatter

    handlers:

        # Configuración de envío de log
        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

En tu directorio de configuración /path/to/config/directory/, añade lo siguiente a config_dev.yml y config_prod.yml. Modifica el ejemplo para configurarlo para tus entornos de desarrollo y producción.

monolog:
    handlers:

        # Configuración de envío de log
        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
La función \DDTrace\current_context() se presentó en la versión 0.61.0.

Añade lo siguiente:

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

        // Opcional: utilice el formato JSON
        $useJson = false;
        foreach ($monolog->getHandlers() as $handler) {
            if (method_exists($handler, 'setFormatter')) {
                $handler->setFormatter(new \Monolog\Formatter\JsonFormatter());
                $useJson = true;
            }
        }

        // Inyecta la traza y el ID de tramo para conectar la entrada de log con la traza de 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 any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

Añade lo siguiente:

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

Añade lo siguiente:

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

  return $app;

Leer más