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