## Laravel 日志
### 简介
> laravel 日志系统使用了 Monolog 作用日志系统
- 参考文档
- 开源地址 https://learnku.com/articles/8108/the-log-system-of-laravel-monolog
- Laravel/Lumen 自定义错误日志格式过滤堆栈信息 https://learnku.com/articles/41305
- Laravel 的日志系统 - monolog https://learnku.com/articles/8108/the-log-system-of-laravel-monolog
- 文档8.5: https://learnku.com/docs/laravel/8.5/logging/10380
### 基本使用方法
- channel 通道|管道|渠道
> channel 是用于将 laravel 日志输出到不同的通道中的, 例如 single 单个日志文件通道默认所有的日志信息将记录到 single 中
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
> 再比如 daily 通道是按照天数进行记录, 如果安装 daily 通道则会每天生成的日志 laravel-xx-xx-xx.log
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
> Slack 通道 是一款即时通信软件,类似于 QQ,它提供开放的 API,可以调用它向自己团队中指定的个人或者频道(Channel)发送消息,因此用它来进行异常通知是再合适不过的
> 如果需要在 Laravel 中使用则必须安装扩展包
https://github.com/maknz/slack-laravel
- stack 堆栈
> Monlog 中所有的日志管道都通过 stack 来压入后后遍历整个通道的堆栈。
### 配置分别对日志写入两个文件,一个为数据的形式一个为 JSON 格式
- 使用 tap 自定义重新写入日志文件中
'alilogo' => [
'driver' => 'daily', // 记录到每天日志方便日志进行采集
'path' => storage_path('logs/alilogo.log'), // 前缀名称
'level' => 'debug', // 错误级别
'days' => 1, // 保存的日志的天数
'tap' => [\App\Logging\LogstashJsonFormatter::class], // 自定义模式
],
- 接下来编写 LogstashJsonFormatter 文件
> 原理就是使用 __invoke() 函数每次实例化这个类的时候调用 __invoke()
> processors 的概念: processors 标识一个单独的处理器, 可以对每个处理器进行设置数据
> 原理:push 使用 php 的 array_unshift(), 第二个参数是传入的闭包函数,入栈后依次遍历处理
use Monolog\Formatter\LogstashFormatter as CustomerLogstashFormatter;
public function __invoke($logger) // 这里传入的是一个 Monolog\Logger 对象
{
foreach ($logger->getHandlers() as $handler) { // 遍历 handlers, handlers 是一个数组包含了写入日志的基本信息
$handler->setFormatter(new CustomerLogstashFormatter(env("APP_NAME"))); // 这里的 CustomerLogstashFormatter是别名
$handler->pushProcessor(new WebProcessor());
$handler->pushProcessor(new MemoryUsageProcessor());
$handler->pushProcessor(new ProcessIdProcessor());
$handler->pushProcessor( // 自定义管理参数通过 record
function ($record) {
$record['extra']['env'] = env('APP_ENV'); // 自定义扩展数据
return $record;
}
);
}
}
> 说明: LogstashFormatter 中的 format() 最后输出的是一个 json 的数据
> 最后我们输入一串 json 到日志文件中
{"@timestamp":"2022-02-21T15:16:03.741202+08:00","@version":1,"host":"workbench","message":"debug","type":"Laravel","channel":"local","level":"DEBUG","monolog_level":100,"extra":{"env":"local","process_id":4087,"memory_usage":"22 MB"},"context":{"data":[{"id":184,"group_id":130,"name":"默认店铺","mobile":"13594755555","contacts":"广东","description":null,"address":null,"email":null,"member_group_id":null,"created_at":"2022-01-12 17:38:10","updated_at":"2022-01-12 17:38:10","deleted_at":null,"instance_id":1},{"id":190,"group_id":130,"name":"门店04","mobile":"13594666622","contacts":"复制人","description":null,"address":"地址","email":null,"member_group_id":null,"created_at":"2022-01-18 14:46:14","updated_at":"2022-01-18 14:46:14","deleted_at":null,"instance_id":1},{"id":191,"group_id":130,"name":"门店名称","mobile":"13594754466","contacts":"啊啊啊","description":null,"address":"地址","email":null,"member_group_id":null,"created_at":"2022-01-18 14:48:36","updated_at":"2022-01-18 14:48:36","deleted_at":null,"instance_id":1},{"id":194,"group_id":130,"name":"荷花","mobile":"17788882345","contacts":"荷花","description":null,"address":"123","email":null,"member_group_id":null,"created_at":"2022-01-19 15:25:13","updated_at":"2022-01-19 15:25:13","deleted_at":null,"instance_id":1}]}}
### 优化输出参数
- 时间: 默认的 timestamp 不能直观的查看
- url: 记录 url 路径方便排查文件
- ip: 记录是那个客户端发起的请求
## 如何在代码中按需打印
> 针对不同的情况我们进行打印,首先规定好一些常用的说明, stack 打印可将 alilogo 和 single 共压入栈中打分别打印到日志中
\Log::stack(['alilogo', 'single'])->debug('debug', $log3);
> 通过通道的方式进行按需打印
\Log::channel('alilogo')->debug('debug', $log2); // 打印到 alilogo 通道
### 解决如何不同环境下按需记录日志
> 目前想通过直接或者 env , 但是应该有更简单方法
### 配置 logging.php 使得不用更改代码就达到记录的作用
'stack' => [
'driver' => 'stack',
'channels' => ['single', LoggerChannel::ALIYUN_JSON], // 只打印这个两种
'ignore_exceptions' => false,
],
'aliyun' => [
'driver' => 'daily',
'path' => storage_path('logs/aliyun.log'),
'level' => 'debug', // 只在 debug 级别下生效
'days' => 14,
'tap' => [\App\Logging\LogstashJsonFormatter::class],
],
> 现在只需要在代码中直接输出就可以达到效果
\Log::debug('debug', $log3)
### 更进一步优化输出日志文件
> 通过将不同级别的日志分别放在通过的文件中方便根据优先级进行修复代码, monolog 中的优先级分别有
- DEBUG (100): 详细调试信息,经常使用
- INFO (200): 一些常规的信息例如用户登录、注册、SQL 日志
- NOTICE (250): 正常但是又重要事件信息
- WARNING (300): 例如一些过期的函数的警告不影响程序正常执行完毕, 可能需要换个 api
- ERROR (400): 运行时错误,不需要立即采取行动,但通常应记录和监控
- CRITICAL (500): 程序不可用意外情况,例如突然性的中断
- ALERT (550): 一些提示
- EMERGENCY (600): 例如类没有引入类没有实例化等等
### 输出 error 错误的日志到 JSON
> 安装上面的方法建立一个 LogErrorFormatter
public function __invoke($logger)
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter());
}
}
> 继承 LineFormatter 重写 format
const NEW_SIMPLE_FORMAT = "[%datetime%] [%uuid%] %channel%.%level_name%: %message% %context% %extra%\n";
public function format(array $record):String
{
$output = self::NEW_SIMPLE_FORMAT; // 定义输出格式
$vars = (new NormalizerFormatter())->format($record); // 获取需要格式化的数据
$vars['uuid'] = 'uuid:' . Str::uuid(); // 生成一个唯一id
foreach ($vars['extra'] as $var => $val) { // 替换自定义变量格式
if (false !== strpos($output, '%extra.' . $var . '%')) {
$output = str_replace('%extra.' . $var . '%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
// 卸载异常、错误的堆栈信息
if (isset($vars['context']['exception']) && !empty($vars['context']['exception'])) {
$vars['message'] = '';
$vars['context'] = $vars['context']['exception'];
if (isset($vars['context']['trace'])) {
unset($vars['context']['trace']);
}
if (isset($vars['context']['previous'])) {
unset($vars['context']['previous']);
}
}
// 替换异常堆栈信息
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%' . $var . '%')) {
$output = str_replace('%' . $var . '%', $this->stringify($val), $output);
}
}
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
return $output;
}
### 调试代码到输出为 JSON
- 把默认输出到文件中,
### 如何在线上环境屏蔽 BusinessException 这种错误不让输出到日志中