Hyperf框架-自定义日志处理的实现

1. 新建自定义日志协程处理程序

<?php

namespace App\Kernel\Log;

use Hyperf\Context\Context;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;

class LogHandler
{

    private static $instance;

    /**
     * @param mixed ...$args
     * @return static
     */
    static function getInstance(...$args)
    {
        if (!isset(static::$instance)) {
            static::$instance = new static(...$args);
        }
        return static::$instance;
    }

    /**
     * 请求唯一标识
     * @var string
     */
    protected const PROCESS_SIGN = 'process_sign';

    /**
     * 请求开始时间
     */
    protected const PROCESS_SIGN_START_TIME = 'process_sign_start_time';


    /**
     * 日志工厂
     * @var LoggerInterface
     */
    protected LoggerInterface $logger;

    public function __construct()
    {
        // 获取容器
        $container = \Hyperf\Context\ApplicationContext::getContainer();
        // 获取日志工厂
        $loggerFactory = $container->get(LoggerFactory::class);
        // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
        $this->logger = $loggerFactory->get('log', 'default');
    }

    /**
     * 获取请求开始时间
     * @return float
     */
    protected function getProcessSignStartTime()
    {
        $startTime = Context::get(self::PROCESS_SIGN_START_TIME);
        if (!$startTime) {
            $startTime = microtime(true);
            Context::set(self::PROCESS_SIGN_START_TIME, $startTime);
        }
        return $startTime;
    }

    /**
     * 生成请求唯一标识
     * @return string
     */
    public function getUniqueId(): string
    {
        mt_srand();
        $random = mt_rand(10000000, 99999999);
        $uniqueId = uniqid($random, true);
        return md5($uniqueId);
    }

    /**
     * 获取请求唯一标识
     * @return string
     */
    protected function getSign(): string
    {
        $container = \Hyperf\Context\ApplicationContext::getContainer();
        // 获取/设置请求的时间
        self::getInstance()->getProcessSignStartTime();
        $info = Context::get(self::PROCESS_SIGN);
        if (!$info) {
            $sign = self::getInstance()->getUniqueId();
            $path = '';
            try {
                $request = $container->get(RequestInterface::class);
                $path = $request->path();
            } catch (\Throwable $e) {
            }

            $info = '[' . $sign . '|' . trim($path, '/') . ']';
            Context::set(self::PROCESS_SIGN, $info);
        }
        return $info;
    }

    /**
     * 记录日志
     * @param $tag
     * @param array $data
     * @return void
     */
    public function record($tag, array $data = []): void
    {
        $sign = $this->getSign();
        $process = [
            'tag' => $tag,
            'data' => $data,
        ];
        $this->logger->info($sign, $process);
    }

    /**
     * 错误日志记录
     * @param $tag
     * @param $data
     * @return void
     */
    public function error($tag, $data): void
    {
        $sign = $this->getSign();
        $process = [
            'tag' => $tag,
            'data' => $data,
        ];
        $this->logger->error($sign, $process);
    }

    /**
     * 结束记录日志
     * @param string $tag
     * @return void
     */
    public function end(string $tag = ''): void
    {
        $startTime = $this->getProcessSignStartTime();
        $endTime = microtime(true);
        $timeConsumed = number_format($endTime - $startTime, 4);
        $timeConsumedText = sprintf("%s ms", $timeConsumed * 1000);
        $tag = sprintf('%s------------------end--------------------', $tag);
        self::getInstance()->record($tag, ['timeConsumed' => $timeConsumedText]);
    }
}

2. 在 env 中新增 Log 日志的路径

// 这里是将日志与项目文件分开,避免因日志原因导致项目文件夹体积过大的问题
APP_LOG_PATH = '/mnt/log/hyperf-xx/log'

3. config 中的 log 修改或新增配置

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
use function Hyperf\Support\env;
return [
    'default' => [
        'handler' => [
            'class' => Monolog\Handler\RotatingFileHandler::class,
            'constructor' => [
//                'filename' => BASE_PATH . '/runtime/logs/hyperf.log',
                'filename' => sprintf('%s/log.log',env('APP_LOG_PATH')), // 这里 env函数是读取自定义的日志路径
                'level' => Monolog\Logger::DEBUG,
            ],
        ],
        'formatter' => [
            'class' => Monolog\Formatter\LineFormatter::class,
            'constructor' => [
                'format' => "[%datetime%] %message% [%level_name%] %context%\n",
                'dateFormat' => 'Y-m-d H:i:s.u',
                'allowInlineLineBreaks' => true,
                'includeStacktraces' => true,
            ],
        ],
    ],
];

上面为修改default 默认配置,如果新增key的话,需要修改第一点的 $loggerFactory->get 第二个参数保持一致

4.1 中间件中使用

全局前置中间件

<?php

namespace App\Middleware;

use App\Kernel\Log\LogHandler;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class BeforeMiddleware implements MiddlewareInterface
{
    public function __construct(protected ContainerInterface $container)
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // TODO: 前置操作
        LogHandler::getInstance()->record('------------------start--------------------');
        LogHandler::getInstance()->record('header', $request->getHeaders());
        $queryParams = $request->getQueryParams(); // 获取GET请求参数
        $bodyParams = $request->getParsedBody(); // 获取POST请求体参数
        $allParams = array_merge($queryParams, $bodyParams);
        LogHandler::getInstance()->record('body', $allParams);
        try {
            $result = $handler->handle($request);
        } catch (\Throwable $e) {
            LogHandler::getInstance()->error('前置中间件执行抛出异常', [
                'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(),
            ]);
            return $handler->handle($request);
        }
        return $result;
    }
}

全局后置中间件

<?php

namespace App\Middleware;

use App\Kernel\Log\LogHandler;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AfterMiddleware implements MiddlewareInterface
{
    public function __construct(protected ContainerInterface $container)
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $result = $handler->handle($request);
        // TODO: 后置操作
        try {
            $getContents = $result->getBody()->getContents();

            LogHandler::getInstance()->record('后置中间件-返回数据', json_decode($getContents, true));
            LogHandler::getInstance()->end();
        } catch (\Throwable $e) {
            LogHandler::getInstance()->error('后置中间件执行抛出异常', [
                'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(),
            ]);
        }
        return $result;
    }
}

定义全局中间件

全局中间件只可通过配置文件的方式来配置,配置文件位于 config/autoload/middlewares.php ,配置如下:

<?php

declare(strict_types=1);

return [
'http' => [
\App\Middleware\BeforeMiddleware::class,
\App\Middleware\AfterMiddleware::class,
],
];

以上,在普通的请求中,就可以看到请求时,返回结果的日志数据了

4.2 控制器/service 中使用

// 在需要打日志的位置添加即可
LogHandler::getInstance()->record('记录用户信息',[
    'user' => $user,
]);

4.3 定时器或队列异步任务中使用

/**
* 原定时器/队列任务执行方法
* @return void
*/
public function execute()
{
try {
LogHandler::getInstance()->record('FooTask-start');
self::runService();
LogHandler::getInstance()->end('FooTask-end');
}catch (\Throwable $e){
LogHandler::getInstance()->error('FooTask-异常日志',[
'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(),
]);
}
}

/**
* 运行服务
* @return void
*/
public static function runService()
{
LogHandler::getInstance()->record('FooTask-日志1',[
date('Y-m-d H:i:s.u', time()),
]);
LogHandler::getInstance()->record('FooTask-日志2',[
date('Y-m-d H:i:s.u', time()),
]);
LogHandler::getInstance()->record('FooTask-日志3',[
date('Y-m-d H:i:s.u', time()),
]);
}

5.查看效果

因为在第 2 点的时间将日志文件放到了其他目录下,所以查看日志路径为:/mnt/log/hyperf-xx/log

[2025-02-13 15:17:24.138286] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"------------------start--------------------","data":[]}
[2025-02-13 15:17:24.138560] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"header","data":{"host":["hyperf-swow.top"],"x-real-ip":["192.168.97.1"],"x-forwarded-for":["192.168.97.1"],"connection":["close"],"content-length":["0"],"token":["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNDMyMDU5MzYsInN1YiI6InRva2VuIiwibmJmIjoxNzMyMTY1OTM2LCJhdWQiOjEsImlhdCI6MTczMjE2NTkzNiwianRpIjoiMzYxNzI4NTQ5IiwiaXNzIjoiZWFzeXN3b29sZSIsInN0YXR1cyI6MSwiZGF0YSI6eyJtZW1iZXJfaWQiOiIzNjE3Mjg1NDkiLCJzYWx0IjoiemhiQWR3SmkifX0.vV_gIza6iVrJgfaag9u_aEJUZs8nfe0KHwdqZxr-B1Q"],"nnappversion":["1.2.0"],"user-agent":["Apifox/1.0.0 (https://apifox.com)"],"accept":["*/*"],"accept-encoding":["gzip, deflate, br"]}}
[2025-02-13 15:17:24.138889] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"body","data":[]}
[2025-02-13 15:17:24.139093] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"记录日志","data":{"user":"Hyperf","method":"POST"}}
[2025-02-13 15:17:24.139200] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [ERROR] {"tag":"记录抛出错误","data":{"da":1}}
[2025-02-13 15:17:24.139390] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"后置中间件-返回数据","data":{"id":14033,"live_id":14033,"member_id":5975,"im_groupId":"LIVEROOM_5975_02c6ae7e1282_pro","create_time":"2024-12-26 13:37:12","resolution_ratio":"1080","member_uuid":"696511077","im_online_status":{"To_Account":"696511077","Status":"PushOnline","Detail":[{"Platform":"iPhone","Status":"PushOnline","IsBackground":1}]},"online_judge":0,"exec_time":1735196085}}
[2025-02-13 15:17:24.139577] [ad769636090cf1a6ad2c9a7de92660f2|index/getUserInfo] [INFO] {"tag":"------------------end--------------------","data":{"timeConsumed":"1.3 ms"}}

上面是接口请求时记录的效果,该请求关键字为:ad769636090cf1a6ad2c9a7de92660f2,请求接口:index/getUserInfo

[2025-02-13 15:18:00.083566] [4feea8ed00163811897c4cdd60e38a8a|] [INFO] {"tag":"FooTask-start","data":[]}
[2025-02-13 15:18:00.093588] [4feea8ed00163811897c4cdd60e38a8a|] [INFO] {"tag":"FooTask-日志1","data":["2025-02-13 15:18:00.000000"]}
[2025-02-13 15:18:00.093738] [4feea8ed00163811897c4cdd60e38a8a|] [INFO] {"tag":"FooTask-日志2","data":["2025-02-13 15:18:00.000000"]}
[2025-02-13 15:18:00.093877] [4feea8ed00163811897c4cdd60e38a8a|] [INFO] {"tag":"FooTask-日志3","data":["2025-02-13 15:18:00.000000"]}
[2025-02-13 15:18:00.094005] [4feea8ed00163811897c4cdd60e38a8a|] [INFO] {"tag":"FooTask-end------------------end--------------------","data":{"timeConsumed":"13 ms"}}

上面是定时脚本时日志记录的效果,该日志关键字为:4feea8ed00163811897c4cdd60e38a8a,执行的任务为:FooTask

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇