PHP 로그 수집
PHP 로그를 Datadog로 보내려면 파일에 로그를 기록한 다음 Datadog Agent로 그 파일을 tail하세요. 이 페이지는 Monolog, Zend-Log, Symfony 로깅 라이브러리의 설정 예시를 다룹니다.
출처: 문서
본문
설정
설치
{% tab title="PHP Monolog" %} Composer를 사용해 Monolog를 의존성으로 추가하려면 이 명령을 실행하세요:
composer require "monolog/monolog"
또는 Monolog를 수동으로 설치할 수도 있어요:
-
저장소에서 Monolog를 다운로드해 라이브러리에 포함하세요.
-
인스턴스를 초기화하려면 애플리케이션의 부트스트랩에 다음을 추가하세요:
<?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를 수동으로 설치할 수도 있어요:
- 저장소에서 소스를 다운로드해 라이브러리에 포함하세요.
- 인스턴스를 초기화하려면 애플리케이션의 부트스트랩에 다음을 추가하세요:
<?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로 보내세요.
conf.d/Agent 구성 디렉터리에php.d/폴더를 만드세요.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" %} 세션 프로세서를 사용해 로그에 변수 컨텍스트를 추가하려면 다음 단계를 따르세요.
-
세션 프로세서를 구현하세요: 다음 예시에서 프로세서는 현재 세션을 알고
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; } } -
다음을 추가해 프로세서를 Symfony와 통합하세요:
services: monolog.processor.session_request: class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor arguments: [ @session ] tags: - { name: monolog.processor, method: processRecord } -
생성된 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 %}