multi-store/vendor/webman/log/src/Middleware.php

347 lines
12 KiB
PHP
Raw Normal View History

2024-05-31 09:27:37 +08:00
<?php
namespace Webman\Log;
use Illuminate\Container\Container;
use Illuminate\Database\Connection;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Events\Dispatcher;
use Illuminate\Redis\Events\CommandExecuted;
use support\Context;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use Throwable;
use RuntimeException;
use support\Db;
use support\Log;
use support\Redis;
use think\db\connector\Mysql;
use think\DbManager;
use think\facade\Db as ThinkDb;
use think\Container as ThinkContainer;
class Middleware implements MiddlewareInterface
{
/**
* @param Request $request
* @param callable $next
* @return Response
*/
public function process(Request $request, callable $next): Response
{
static $initialized_db, $initialized_think_orm;
$conf=config('plugin.webman.log.app');
//跳过配置的模块
if(!empty($conf['dontReport']['app']) && is_array($conf['dontReport']['app']) && in_array($request->app,$conf['dontReport']['app'],true)){
return $next($request);
}
//跳过配置的path
if(!empty($conf['dontReport']['path']) && is_array($conf['dontReport']['path'])){
$requestPath=$request->path();
foreach ($conf['dontReport']['path'] as $_path){
if(strpos($requestPath,$_path)===0){
return $next($request);
}
}
}
//跳过配置的控制器日志记录
if(!empty($conf['dontReport']['controller']) && is_array($conf['dontReport']['controller']) && in_array($request->controller,$conf['dontReport']['controller'],true)){
return $next($request);
}
//跳过配置的方法
if(!empty($conf['dontReport']['action']) && is_array($conf['dontReport']['action'])){
foreach ($conf['dontReport']['action'] as $_action){
if($_action[0]===$request->controller && $_action[1]===$request->action){
return $next($request);
}
}
}
// 请求开始时间
$start_time = microtime(true);
// 记录ip 请求等信息
$logs = $request->getRealIp() . ' ' . $request->method() . ' ' . trim($request->fullUrl(), '/');
Context::get()->webmanLogs = '';
// 清理think-orm的日志
if (class_exists(ThinkDb::class, false) && class_exists(Mysql::class, false)) {
ThinkDb::getDbLog(true);
}
// 初始化数据库监听
if (!$initialized_db) {
$initialized_db = true;
$this->initDbListen();
}
// 初始化think-orm日志监听
if (!$initialized_think_orm) {
try {
ThinkDb::setLog(function ($type, $log) {
Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[SQL]\t" . trim($log) . PHP_EOL;
});
} catch (Throwable $e) {}
$initialized_think_orm = true;
}
// 得到响应
$response = $next($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 ?? '');
// think-orm如果被使用则记录think-orm的日志
if ($loaded_think_db = (class_exists(ThinkDb::class, false) && class_exists(Mysql::class, false))) {
$sql_logs = ThinkDb::getDbLog(true);
if (!empty($sql_logs['sql'])) {
foreach ($sql_logs['sql'] as $sql) {
$logs .= "[SQL]\t" . trim($sql) . PHP_EOL;
}
}
}
// 判断业务是否出现异常
$exception = null;
if (method_exists($response, 'exception')) {
$exception = $response->exception();
}
// 尝试记录异常
$method = 'info';
if ($exception && config('plugin.webman.log.app.exception.enable', true) && !$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;
}
}
// 判断think-orm是否有未提交的事务
if ($loaded_think_db) {
if ($log = $this->checkTpUncommittedTransaction()) {
$has_uncommitted_transaction = true;
$method = 'error';
$logs .= $log;
}
}
/**
* 初始化redis监听
* 注意由于redis是延迟监听所以第一个请求不会记录redis具体日志
*/
$new_names = $this->tryInitRedisListen();
foreach ($new_names as $name) {
$logs .= "[Redis]\t[connection:{$name}] ..." . PHP_EOL;
}
call_user_func([Log::class, $method], $logs);
if ($has_uncommitted_transaction) {
throw new RuntimeException('Uncommitted transactions found');
}
return $response;
}
/**
* 初始化数据库日志监听
*
* @return void
*/
protected function initDbListen()
{
if (!class_exists(QueryExecuted::class)) {
return;
}
try {
$capsule = $this->getCapsule();
if (!$capsule) {
return;
}
$dispatcher = $capsule->getEventDispatcher();
if (!$dispatcher) {
$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_names = [];
if (!class_exists(CommandExecuted::class)) {
return [];
}
$new_names = [];
try {
foreach (Redis::instance()->connections() ?: [] as $connection) {
/* @var \Illuminate\Redis\Connections\Connection $connection */
$name = $connection->getName();
if (isset($listened_names[$name])) {
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_names[$name] = $name;
$new_names[] = $name;
}
} catch (Throwable $e) {
echo $e;
}
return $new_names;
}
/**
* 获得Db的Manager
*
* @return mixed
*/
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
*/
protected function checkDbUncommittedTransaction(): string
{
$logs = '';
try {
foreach ($this->getCapsule()->getDatabaseManager()->getConnections() as $connection) {
/* @var \Illuminate\Database\MySqlConnection $connection * */
if (\in_array($connection->getConfig('driver'), ['mysql', 'pgsql', 'sqlite', 'sqlsrv'])) {
$pdo = $connection->getPdo();
if ($pdo && $pdo->inTransaction()) {
$connection->rollBack();
$logs .= "[ERROR]\tUncommitted transaction found and try to rollback" . PHP_EOL;
}
}
}
} catch (Throwable $e) {
echo $e;
}
return $logs;
}
/**
* 检查think-orm是否有未提交的事务
*
* @return string
*/
protected function checkTpUncommittedTransaction(): string
{
static $property, $manager_instance;
$logs = '';
try {
if (!$property) {
if (class_exists(ThinkContainer::class, false)) {
$manager_instance = ThinkContainer::getInstance()->make(DbManager::class);
} else {
$reflect = new \ReflectionClass(ThinkDb::class);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$manager_instance = $property->getValue();
}
$reflect = new \ReflectionClass($manager_instance);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
}
$instances = $property->getValue($manager_instance);
foreach ($instances as $connection) {
/* @var \think\db\connector\Mysql $connection */
if (method_exists($connection, 'getPdo')) {
$pdo = $connection->getPdo();
if ($pdo && $pdo->inTransaction()) {
$connection->rollBack();
$logs .= "[ERROR]\tUncommitted transaction found and try to rollback" . PHP_EOL;
}
}
}
} catch (Throwable $e) {
echo $e;
}
return $logs;
}
/**
* 判断是否需要记录异常
*
* @param Throwable $e
* @return bool
*/
protected function shouldntReport($e): bool
{
foreach (config('plugin.webman.log.app.exception.dontReport', []) as $type) {
if ($e instanceof $type) {
return true;
}
}
return false;
}
}