简介
在日常开发里面,找Bug需要经常用到日志,但是Laravel默认日志是存储到服务器本地文件中,每次去查日志都需要进入服务器,使用vi、cat等命令进行查看,而且当服务器水平扩展的时候,日志文件分布在不同容器内,让我们查日志更是难上加难,无法对日志进行集中分析。
今天会把Laravel日志接入到Seq里面,让日志可以统一管理、集中分析,让日志变成我们找Bug的好帮手。
Seq 是用于结构化应用程序日志数据的实时搜索和分析服务器,类似ElasticSearch。
准备Seq
我们需要一台Seq服务器,可以直接在Seq官方下载安装,支持Windows安装包或Docker方式。
因为Seq没有提供PHP SDK,所以我们使用HTTP方式写入日志。
POST https://localhost:5341/api/events/raw?clef
Body格式化如下:
{
"Events": [{
"Timestamp": "2021-12-02T22:00:00.12345+10:00",
"Level": "Warning",
"MessageTemplate": "Disk space is low on {Drive}",
"Properties": {
"Drive": "C:",
"MachineName": "nblumhardt-rmbp"
}
}]
}
使用Postman测试一下,可以成功写入到Seq。
Laravel 日志组件
Laravel的日志组件默认是使用 Seldaek/monolog 去实现,所以我们可以使用更换不同的 Monolog Handler 去改变日志处理的方式,Monolog 官方有提供 RedisHandler、MongoDBHandler、ElasticsearchHandler 等等,当然你也可以去 Packagist 寻找其他第三方的 Handler。
Laravel 日志组件自定义配置
Laravel 日志组件提供自定义 Monolog Handler 的方法:
Laravel 版本 <= 5.5 方法 官方文档
你可以使用 configureMonologUsing 方法来配置应用程序对 Monolog 的完全控制。在 $app 变量返回之前,在 bootstrap/app.php 文件中调用此方法。
$app->configureMonologUsing(function (Monolog\Logger $monolog) {
$redis = new Predis\Client('tcp://127.0.0.1:6379');
$handler = new \Monolog\Handler\RedisHandler($redis, 'logs');
$monolog->pushHandler($handler);
});
return $app;
Laravel 版本 >= 5.6 方法 官方文档
你可以在 config/logging.php 创建一个 channels,配置不同的 MonoLog Handler,with 是配置 Handler 的构建函数。
'redislog' => [
'driver' => 'monolog',
'handler' => \Monolog\Handler\RedisHandler::class,
// with
'with' => [
'redis' => new \Predis\Client('tcp://127.0.0.1:6379'),
'key' => 'logs',
],
],
覆盖 LogServiceProvider 方法
根据 Laravel 服务容器特性,你还可以自己创建一个 LogServiceProvider 去覆盖 Laravel 自身的 LogServiceProvider。
- 在
app/Log下创建LogManager.php,继承Illuminate\Log\LogManager。
<?php
namespace App\Log;
use Illuminate\Log\LogManager as BaseLogManager;
class LogManager extends BaseLogManager
{
// TODO: 重写对应的方法
}
- 在
app/Providers下创建LogServiceProvider.php
<?php
namespace App\Providers;
use App\Log\LogManager;
use Illuminate\Log\LogServiceProvider as BaseLogServiceProvider;
class LogServiceProvider extends BaseLogServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('log', function () {
return new LogManager($this->app);
});
}
}
- 在
config/app.php中的providers数组,追加App\Providers\LogServiceProvider::class。
'providers' => [
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\LogServiceProvider::class, // <-- LogServiceProvider
],
正常情况下,不建议使用覆盖 LogServiceProvider 的方式,因为在 Laravel 5.6 之后日志组件已经比较自由了,很多地方都可以通过 config/logging.php 去配置。
Laravel 日志组件接入Seq
因为这次需要接入的项目是 Laravel 5.5,我们使用官方的自定义 Monolog Handler 方式,通过自定义 Handler ,把Log写入到Seq里面。
- 在
app/Utilities/SeqMonologHandler创建SeqHandler.php
通过自定义 Handler,可以实现不同的接入方式,具体可以参考 Monolog 官方文档
<?php
namespace App\Utilities\SeqMonologHandler;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
class SeqHandler extends AbstractProcessingHandler
{
const SEQ_API_URI = '/api/events/raw';
protected $logLevelMap = [
'100' => 'Debug',
'200' => 'Information',
'250' => 'Information',
'300' => 'Warning',
'400' => 'Error',
'500' => 'Error',
'550' => 'Fatal',
'600' => 'Fatal',
];
protected $client;
protected $serverUrl;
public function __construct(string $serverUrl = '', $level = Logger::DEBUG, $bubble = true)
{
$this->serverUrl = $serverUrl;
$this->client = new Client();
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
$url = $this->serverUrl . SeqHandler::SEQ_API_URI;
$eventBody = [
'Events' => [[
'Timestamp' => Carbon::now()->toIso8601String(),
"Level" => $this->logLevelMap[$record['level']] ?: 'Information',
"MessageTemplate" => $record['formatted'],
]]
];
try {
$this->client->post($url, [
"body" => json_encode($eventBody)
]);
} catch (\Exception $exception) {
return;
}
}
}
- 然后在
bootstrap\app.php的return $app之前,配置一下 Monolog Handler
$app->configureMonologUsing(function (Monolog\Logger $monolog) {
$handler = new App\Utilities\SeqMonologHandler\SeqHandler('http://127.0.0.1:5341');
$monolog->pushHandler($handler);
});
- 测试一下效果