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;
    }

}
171 1 1
1个回答

10bang

怎么发到问答来了,不应该发分享吗?

  • 暂无评论
🔝