본문 바로가기
WIKI 기술 지식 베이스

PHP 로그 수집

원문 보기 위키 갱신

PHP 로그를 Datadog로 보내려면 파일에 로그를 기록한 다음 Datadog Agent로 그 파일을 tail하세요. 이 페이지는 Monolog, Zend-Log, Symfony 로깅 라이브러리의 설정 예시를 다룹니다.

출처: 문서

본문

설정

설치

{% tab title="PHP Monolog" %} Composer를 사용해 Monolog를 의존성으로 추가하려면 이 명령을 실행하세요:

composer require "monolog/monolog"

또는 Monolog를 수동으로 설치할 수도 있어요:

  1. 저장소에서 Monolog를 다운로드해 라이브러리에 포함하세요.

  2. 인스턴스를 초기화하려면 애플리케이션의 부트스트랩에 다음을 추가하세요:

    <?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는 Zend 프레임워크의 일부예요. Composer를 사용해 Zend-Log를 추가하려면 이 명령을 실행하세요:

composer require "zendframework/zend-log"

또는 Zend-Log를 수동으로 설치할 수도 있어요:

  1. 저장소에서 소스를 다운로드해 라이브러리에 포함하세요.
  2. 인스턴스를 초기화하려면 애플리케이션의 부트스트랩에 다음을 추가하세요:
<?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" %} Monolog JSON 포매터를 서비스로 선언하려면 다음을 추가하세요:

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

{% /tab %}

로거 구성하기

{% tab title="PHP Monolog" %} 다음 구성은 JSON 포매팅을 활성화하고 로그·이벤트를 application-json.log 파일에 기록해요. 코드에서 Monolog 인스턴스 초기화 후 새 핸들러를 추가하세요:

 <?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" %} 다음 구성은 JSON 포매팅을 활성화하고 로그·이벤트를 application-json.log 파일에 기록해요. 코드에서 Zend-Log 인스턴스 초기화 후 새 핸들러를 추가하세요.

<?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" %} Monolog 구성에서 포매터를 구성하려면 formatter 필드를 다음과 같이 선언하세요:

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

{% /tab %}

Datadog Agent 구성하기

log collection이 활성화되면 custom log collection을 설정해 로그 파일을 tail하고 새 로그를 Datadog로 보내세요.

  1. conf.d/ Agent 구성 디렉터리에 php.d/ 폴더를 만드세요.
  2. php.d/에 다음 내용으로 conf.yaml 파일을 만드세요:
init_config:

instances:

## Log section
logs:

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

로그와 트레이스에 걸쳐 서비스 연결하기

이 애플리케이션에서 APM이 활성화되어 있다면 APM PHP 로깅 지침을 따라 로그에 트레이스·스팬 ID를 자동으로 추가해 애플리케이션 로그와 트레이스 간 상관관계를 개선할 수 있어요.

로그에 컨텍스트 더 추가하기

{% tab title="PHP Monolog" %} 로그와 이벤트에 추가 컨텍스트를 더하는 것이 유용할 수 있어요. Monolog는 스레드-로컬 컨텍스트를 설정할 수 있는 메서드를 제공하며, 이 컨텍스트는 모든 이벤트와 함께 자동 제출돼요. 예를 들어 컨텍스트 데이터로 이벤트를 로깅하려면:

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

Monolog의 전처리기는 단순한 콜백 기능으로, 설정할 수 있는 메타데이터(예: 세션 ID, 요청 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;
  });

{% /tab %}

{% tab title="PHP Zend-Log" %} 로그와 이벤트에 추가 컨텍스트를 더하는 것이 유용할 수 있어요. Zend-Log는 스레드-로컬 컨텍스트를 설정할 수 있는 메서드를 제공하며, 이 컨텍스트는 모든 이벤트와 함께 자동 제출돼요. 예를 들어 컨텍스트 데이터로 이벤트를 로깅하려면:

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

로그에 추가 정보를 제공하는 방법에 대한 자세한 내용은 Zend의 Processor 문서를 참고하세요. {% /tab %}

{% tab title="PHP Symfony" %} 세션 프로세서를 사용해 로그에 변수 컨텍스트를 추가하려면 다음 단계를 따르세요.

  1. 세션 프로세서를 구현하세요: 다음 예시에서 프로세서는 현재 세션을 알고 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. 다음을 추가해 프로세서를 Symfony와 통합하세요:

      services:
          monolog.processor.session_request:
              class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
              arguments:  [ @session ]
              tags:
                  - { name: monolog.processor, method: processRecord }
    
  3. 생성된 JSON 파일을 Datadog로 스트리밍하세요.

{% /tab %}

Monolog 프레임워크 통합

Monolog는 다음 프레임워크와 함께 사용할 수 있어요:

Monolog를 프레임워크와 통합하려면 다음을 추가하세요:

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

그런 다음 Monolog용 로거를 구성하세요.

{% tab title="Symfony v2+/v3+" %} 구성 디렉터리 /path/to/config/directory/에서 config_dev.yml과 config_prod.yml에 다음을 추가하세요. 개발·프로덕션 환경에 맞게 예시를 수정하세요.

# 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" %} 구성 디렉터리 /path/to/config/directory/에서 config_dev.yml과 config_prod.yml에 다음을 추가하세요. 개발·프로덕션 환경에 맞게 예시를 수정하세요.

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" %} 함수 \DDTrace\current_context()는 버전 0.61.0에서 도입됐어요. {% /alert %}

다음을 추가하세요:

<?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" %} 다음을 추가하세요:

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

{% /tab %}

{% tab title="Lumen" %} 다음을 추가하세요:

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

  return $app;

{% /tab %}

더 알아보기 (Learn more)