- 重要な情報
- アプリ内
- インフラストラクチャー
- アプリケーションパフォーマンス
- 継続的インテグレーション
- ログ管理
- セキュリティ
- UX モニタリング
- 管理
Monolog、Zend-Log、Symfony の各ロギングライブラリから選択して PHP ログをファイルに書き込み、Agent を使用して Datadog に転送します。
Composer を使用して Monolog を依存関係として追加します。
composer require "monolog/monolog"
または、手動でインストールします。
リポジトリから Monolog をダウンロードし、ライブラリに追加します。
アプリケーションのブートストラップでインスタンスを初期化します。
<?php
require __DIR__ . '/vendor/autoload.php';
// load Monolog library
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
Zend-log は Zend フレームワークに含まれます。Composer を使用して Zend-Log を追加します。
composer require "zendframework/zend-log"
または、手動でインストールします。
<?php
require __DIR__ . '/vendor/autoload.php';
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
Monolog JSON フォーマッタをサービスとして宣言します。
services:
monolog.json_formatter:
class: Monolog\Formatter\JsonFormatter
次の構成は JSON フォーマッタを有効にし、ログとイベントを application-json.log
ファイルに書き込みます。Monolog インスタンスの初期化直後に新しいハンドラーを追加するように、コードを編集してください。
<?php
require __DIR__ . '/vendor/autoload.php';
// Monolog ライブラリをロード
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
// ログチャネルを作成
$log = new Logger('channel_name');
// Json フォーマッタを作成
$formatter = new JsonFormatter();
// ハンドラーを作成
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// バインド
$log->pushHandler($stream);
// 例
$log->info('Adding a new user', array('username' => 'Seldaek'));```
次の構成は JSON フォーマッタを有効にし、ログとイベントを application-json.log
ファイルに書き込みます。Zend-Log インスタンスの初期化直後に新しいハンドラーを追加するように、コードを編集してください。
<?php
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
use Zend\Log\Formatter\JsonFormatter;
// ロガーを作成
$logger = new Logger();
// ライターを作成
$writer = new Stream('file://' . __DIR__ . '/application-json.log');
// Json フォーマッタを作成
$formatter = new JsonFormatter();
$writer->setFormatter($formatter);
// バインド
$logger->addWriter($writer); Zend\Log\Logger::registerErrorHandler($logger);```
次に、[ログファイルを Datadog にストリーミングします][1]。
[1]: /ja/logs/log_collection/
Monolog 構成でフォーマッタを構成します。以下のフォーマッタフィールドを宣言します。
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.json_formatter
ログとトレースの接続
このアプリケーションで APM が有効になっている場合、APM PHP ロギングの指示に従ってログにトレース ID とスパン ID を自動的に追加することで、アプリケーションログとトレース間の相関関係を改善できます。
conf.d/
フォルダーに次の内容の php.d/conf.yaml
ファイルを作成します。
init_config:
instances:
## Log セクション
logs:
- type: file
path: "/path/to/your/php/application-json.log"
service: "<SERVICE_NAME>"
source: php
sourcecategory: sourcecode
コンテキストデータをログやイベントに追加すると便利です。Monolog では、これを簡単に行えるよう、スレッドローカルなコンテキストデータを設定すると、すべてのイベントと一緒に自動的に送信されるメソッドが提供されています。いつでもコンテキストデータを含むイベントを記録できます。
<?php
$logger->info('Adding a new user', array('username' => 'Seldaek'));
Monolog にはプリプロセッサー機能が付属しています。これは、イベントにメタデータ (例: セッション ID、リクエスト ID) を設定して情報を補完できる簡単なコールバックです。
<?php
$log->pushProcessor(function ($record) {
// 現在のユーザーを記録
$user = Acme::getCurrentUser();
$record['context']['user'] = array(
'name' => $user->getName(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
);
// さまざまなタグを追加
$record['ddtags'] = array('key' => 'value');
// さまざまな汎用コンテキストを追加
$record['extra']['key'] = 'value';
return $record; });```
有益なログ情報は、ログやイベントに追加できるコンテキストデータから得られます。Zend-Log では、これを簡単に行えるよう、スレッドローカルなコンテキストデータを設定すると、すべてのイベントと一緒に自動的に送信されるメソッドが提供されています。コンテキストデータを含むイベントをいつでも記録できます。
<?php
$logger->info('Adding a new user', array('username' => 'Seldaek'));
ただし、このライブラリにはプロセッサー機能が付属しています。プロセッサーを使用すると、ログに自動的に追加情報を提供することができます。プロセッサーは、イベントがライターに渡される前にロガーから呼び出され、イベント配列を受け取り、完了時にイベント配列を返します。
次のような使用例があります。
そのために、以下のようなコードを使用できます。
<?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());
独自にコードを開発される場合は、Zend ドキュメントをご参照ください。
セッションプロセッサーを追加してさまざまなコンテキストをログに追加します。
セッションプロセッサーを実装します。
下記にプロセッサーの例を示しました。プロセッサーは現在のセッションを認識し、ログレコードの内容を 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 }
Monolog は次のフレームワークに含まれます。
Monolog をフレームワークに統合し、次にロガーを構成します。
<?php
// Monolog ライブラリのロードが正常か確認
//use Monolog\Logger;
//use Monolog\Handler\StreamHandler;
//use Monolog\Formatter\JsonFormatter;
// monolog インスタンスを含む
$monolog = ...
///// Log shipper configuration
$formatter = new JsonFormatter();
$stream = new StreamHandler(__DIR__.'/application-json.log', Logger::DEBUG);
$stream->setFormatter($formatter);
$monolog->pushHandler($stream);
return $r;
構成ディレクトリ /path/to/config/directory/
にある config_dev.yml
と config_prod.yml
を編集して、それぞれ開発環境と生産環境のニーズに合わせてログ管理を構成します。
# app/config/config.yml
monolog:
# プロセッサーを使用する場合はこのセクションのコメントを解除
# Processor :
# session_processor:
# class: Acme\Bundle\MonologBundle\Log\SessionRequestProcessor
# arguments: [ @session ]
# tags:
# - { name: monolog.processor, method: processRecord }
json_formatter:
class: Monolog\Formatter\JsonFormatter
handlers:
# ログシッパーのコンフィギュレーション
to_json_files:
# lvar/logs/(environment).log に記録
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# 全チャネル (ドクトリン、エラーなど) を含む
channels: ~
# json フォーマッタを使用
formatter: monolog.json_formatter
# 全イベント (致命的なエラーをデバッグ) を記録
level: debug
構成ディレクトリ /path/to/config/directory/
にある config_dev.yml
と config_prod.yml
を編集して、それぞれ開発環境と生産環境のニーズに合わせてログ管理を構成します。
monolog:
handlers:
# ログシッパー構成
to_json_files:
# var/logs/(environment).log にログを記録します
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
# JSON フォーマッタを使用します
formatter: monolog.json_formatter
# すべてのイベント (debug から fatal まで) をログに記録します
level: debug
\DDTrace\current_context()
は、バージョン 0.61.0 で導入されています。<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
// Monolog インスタンスを取得
$monolog = logger()->getLogger();
if (!$monolog instanceof \Monolog\Logger) {
return;
}
// オプション: JSON 形式を使用
$useJson = false;
foreach ($monolog->getHandlers() as $handler) {
if (method_exists($handler, 'setFormatter')) {
$handler->setFormatter(new \Monolog\Formatter\JsonFormatter());
$useJson = true;
}
}
// トレースおよびスパン ID を挿入してログエントリを APM トレースと接続
$monolog->pushProcessor(function ($record) use ($useJson) {
$context = \DDTrace\current_context();
if ($useJson === true) {
$record['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()
{
//
}
}
<?php
// file: bootstrap
$app->extend('monolog', function($monolog, $app) {
$monolog->pushHandler(...);
// 下記にロガーを構成
return $monolog;
});
<?php
//file: bootstrap/app.php
$app->configureMonologUsing(function($monolog) {
$monolog->pushHandler(...);
// 下記にロガーを構成
});
return $app;