Monolog 是目前最流行的php日誌庫,許多流行的php框架都用Monolog作為預設的日誌庫,例如symfony、laravel。
預設情況下,symfony、laravel在列印日誌時都是來一條日誌就列印一行到日誌檔案,這樣就沒有辦法按照請求將日誌聚合起來,更麻煩的是,多個併發的請求列印的日誌是交叉的,這給本地除錯、線上排查bug帶來了許多麻煩。
我希望的結果是:
- 一次請求所產生的日誌是緊密相連的,不會因為高併發而產生日誌交叉的情況。
- 能夠通過Logstash的multiline來聚合一次請求所產生的所有日誌。
- 一些訪問資料(如訪問時間、訪問者IP、訪問的Url、該請求的執行時間等)能夠被Logstash直接解析成欄位,並能夠在Kibana中查詢、篩選。
最終程式碼如下:
namespace App\Extensions\Log;
use Carbon\Carbon;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
class AggregateFileHandler extends StreamHandler
{
public function handleBatch(array $records)
{
$dur = number_format(microtime(true) - LARAVEL_START, 3);
$request = request();
$format = 'Y-m-d H:i:s.u';
// 這一行是我們這個處理器自己加上的日誌,記錄請求時間、響應時間、訪客IP,請求方法、請求Url
$log = sprintf(
"[%s][%s]%s %s %s\n",
Carbon::createFromFormat('U.u', sprintf('%.6F', LARAVEL_START), config('app.timezone'))->format($format),
$dur,
$request->getClientIp(),
$request->getMethod(),
$request->getRequestUri()
);
// 然後將記憶體中的日誌追加到$log這個變數裡
foreach ($records as $record) {
if (!$this->isHandling($record)) {
continue;
}
$record = $this->processRecord($record);
$log .= $this->getFormatter()->format($record);
}
// 呼叫日誌寫入方法
$this->write(['formatted' => $log]);
}
}
需要BufferHandler
來配合使用:
$handler = new AggregateFileHandler($path_to_log_file);
$handler->setFormatter(
new LineFormatter("[%datetime%]%level_name% %message% %context% %extra%\n", 'i:s', true, true)
);
$monolog->pushHandler(new BufferHandler($handler));
AggregateRotatingFileHandler
類只實現了一個handleBatch
方法,BufferHandler
會在請求結束時去呼叫handleBatch
方法,因為我們不考慮單獨使用這個處理器的情況,所以就沒有去重寫handler
方法(實際上重寫了也沒法滿足我們的需求)。
Logstash的配置如下:
input {
file {
type => 'monolog'
path => "/home/vagrant/website/logs/app.log"
}
}
filter {
if [type] == 'monolog' {
multiline {
pattern => "\[[\d\-\: ]+?\]\[[\d\.]+?\]\d+\.\d+\.\d+\.\d+ \S+ \S+"
negate => true
what => "previous"
}
grok {
match => ["message", "\[%{TIMESTAMP_ISO8601:time}\]\[%{NUMBER:duration}\]%{IP:ip} %{WORD:method} %{DATA:url}\n%{GREEDYDATA:data}"]
}
}
}
貼一個從ElasticSearch中查出的日誌
{
"_index" : "monolog",
"_type" : "monolog",
"_id" : "AVkgdHh4eDK8-gwc8OTf",
"_score" : 1.0,
"_source" : {
"message" : "[2016-12-21 08:16:27.373000][2.421]127.0.0.1 GET / \n[16:29]INFO Test Log 1\n[16:29]INFO Test Log 2",
"@version" : "1",
"@timestamp" : "2016-12-21T08:16:55.485Z",
"path" : "/home/vagrant/online/website/logs/monolog.log",
"host" : "0.0.0.0",
"type" : "monolog",
"tags" : [ "multiline" ],
"time" : "2016-12-21 08:16:27.373000",
"duration" : "2.421",
"ip" : "127.0.0.1",
"method" : "GET",
"url" : "/",
"data" : "[16:29]INFO Test Log 1\n[16:29]INFO Test Log 2"
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結