ai改造webman/log独立单文件
<?php
namespace app\pkg\middleware;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Events\Dispatcher;
use Illuminate\Redis\Events\CommandExecuted;
use RuntimeException;
use support\Db;
use support\exception\BusinessException;
use support\Context;
use support\Log;
use support\Redis;
use Throwable;
use Webman\Database\Manager;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use Illuminate\Container\Container;
use Illuminate\Database\Connection;
class MonitorMiddleware implements MiddlewareInterface
{
/**
* 完全写死的配置(替代外部配置文件)
* @var array
*/
private $config = [
'enable' => true,
'exception' => [
'enable' => true,
'dontReport' => [
BusinessException::class
]
],
'dontReport' => [
'app' => [],
'controller' => [],
'action' => [],
'path' => []
],
'channel' => 'default'
];
/**
* @param Request $request
* @param callable $handler
* @return Response
*/
public function process(Request $request, callable $handler): Response
{
// 如果全局禁用日志,直接跳过
if (!$this->config['enable']) {
return $handler($request);
}
static $initialized_db;
// 跳过配置的模块
if (!empty($this->config['dontReport']['app']) && is_array($this->config['dontReport']['app']) && in_array($request->app, $this->config['dontReport']['app'], true)) {
return $handler($request);
}
// 跳过配置的path
if (!empty($this->config['dontReport']['path']) && is_array($this->config['dontReport']['path'])) {
$requestPath = $request->path();
foreach ($this->config['dontReport']['path'] as $_path) {
if (str_starts_with($requestPath, $_path)) {
return $handler($request);
}
}
}
// 跳过配置的控制器日志记录
if (!empty($this->config['dontReport']['controller']) && is_array($this->config['dontReport']['controller']) && in_array($request->controller, $this->config['dontReport']['controller'], true)) {
return $handler($request);
}
// 跳过配置的方法
if (!empty($this->config['dontReport']['action']) && is_array($this->config['dontReport']['action'])) {
foreach ($this->config['dontReport']['action'] as $_action) {
if ($_action[0] === $request->controller && $_action[1] === $request->action) {
return $handler($request);
}
}
}
// 请求开始时间
$start_time = microtime(true);
// 记录ip 请求等信息
$logs = $request->getRealIp() . ' ' . $request->method() . ' ' . trim($request->fullUrl(), '/');
Context::get()->webmanLogs = '';
// 初始化数据库监听
if (!$initialized_db) {
$initialized_db = true;
$this->initDbListen();
}
// 得到响应
$response = $handler($request);
$time_diff = substr((microtime(true) - $start_time) * 1000, 0, 7);
$logs .= " [{$time_diff}ms] [webman/log]" . PHP_EOL;
if ($request->method() === 'POST') {
$logs .= "[POST]\t" . var_export($request->post(), true) . PHP_EOL;
}
$logs = $logs . (Context::get()->webmanLogs ?? '');
// 判断业务是否出现异常
$exception = null;
if (method_exists($response, 'exception')) {
$exception = $response->exception();
}
// 尝试记录异常(使用写死的配置)
$method = 'info';
if ($exception && $this->config['exception']['enable'] && !$this->shouldntReport($exception)) {
$logs .= $exception . PHP_EOL;
$method = 'error';
}
// 判断Db是否有未提交的事务
$has_uncommitted_transaction = false;
if (class_exists(Connection::class, false)) {
if ($log = $this->checkDbUncommittedTransaction()) {
$has_uncommitted_transaction = true;
$method = 'error';
$logs .= $log;
}
}
/**
* 初始化redis监听
*/
$new_names = $this->tryInitRedisListen();
foreach ($new_names as $name) {
$logs .= "[Redis]\t[connection:{$name}] ..." . PHP_EOL;
}
// 使用写死的日志通道
call_user_func([Log::channel($this->config['channel']), $method], $logs);
if ($has_uncommitted_transaction) {
throw new RuntimeException('Uncommitted transactions found');
}
return $response;
}
/**
* 初始化数据库日志监听
*
* @return void
*/
protected function initDbListen()
{
if (!class_exists(QueryExecuted::class) || !class_exists(Db::class)) {
return;
}
try {
$capsule = $this->getCapsule();
if (!$capsule) {
return;
}
$dispatcher = $capsule->getEventDispatcher();
if (!$dispatcher) {
if (!class_exists(Dispatcher::class)) {
return;
}
$dispatcher = new Dispatcher(new Container);
}
$dispatcher->listen(QueryExecuted::class, function (QueryExecuted $query) {
$sql = trim($query->sql);
if (strtolower($sql) === 'select 1') {
return;
}
$sql = str_replace("?", "%s", $sql);
foreach ($query->bindings as $i => $binding) {
if ($binding instanceof \DateTime) {
$query->bindings[$i] = $binding->format("'Y-m-d H:i:s'");
} else {
if (is_string($binding)) {
$query->bindings[$i] = "'$binding'";
}
}
}
$log = $sql;
try {
$log = vsprintf($sql, $query->bindings);
} catch (\Throwable $e) {}
Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[SQL]\t[connection:{$query->connectionName}] $log [{$query->time} ms]" . PHP_EOL;
});
$capsule->setEventDispatcher($dispatcher);
} catch (\Throwable $e) {
echo $e;
}
}
/**
* 尝试初始化redis日志监听
*
* @return array
*/
protected function tryInitRedisListen(): array
{
static $listened;
if (!class_exists(CommandExecuted::class) || !class_exists(Redis::class)) {
return [];
}
$new_names = [];
$listened ??= new \WeakMap();
try {
foreach (Redis::instance()->connections() ?: [] as $connection) {
/* @var \Illuminate\Redis\Connections\Connection $connection */
$name = $connection->getName();
if (isset($listened[$connection])) {
continue;
}
$connection->listen(function (CommandExecuted $command) {
foreach ($command->parameters as &$item) {
if (is_array($item)) {
$item = implode('\', \'', $item);
}
}
Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[Redis]\t[connection:{$command->connectionName}] Redis::{$command->command}('" . implode('\', \'', $command->parameters) . "') ({$command->time} ms)" . PHP_EOL;
});
$listened[$connection] = $name;
$new_names[] = $name;
}
} catch (Throwable $e) {
}
return $new_names;
}
/**
* 获得Db的Manager
*
* @return Manager
*/
protected function getCapsule()
{
static $capsule;
if (!$capsule) {
$reflect = new \ReflectionClass(Db::class);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$capsule = $property->getValue();
}
return $capsule;
}
/**
* 检查Db是否有未提交的事务
*
* @return string
* @throws Throwable
*/
protected function checkDbUncommittedTransaction(): string
{
$logs = '';
$context = Context::get();
foreach ($context as $item) {
if ($item instanceof Connection) {
if ($item->transactionLevel() > 0) {
$item->rollBack();
$logs .= "[ERROR]\tUncommitted transaction found and try to rollback" . PHP_EOL;
}
}
}
return $logs;
}
/**
* 判断是否需要记录异常(使用写死的配置)
*
* @param Throwable $e
* @return bool
*/
protected function shouldntReport($e): bool
{
foreach ($this->config['exception']['dontReport'] as $type) {
if ($e instanceof $type) {
return true;
}
}
return false;
}
}
1个回答
怎么发到问答来了,不应该发分享吗?