PHP慢执行日志分离

wen PHP项目 3

本文目录导读:

PHP慢执行日志分离

  1. 方案一:修改 php.ini 配置(最基础)
  2. 方案二:自定义慢日志类(推荐)
  3. 方案三:数据库查询慢日志
  4. 方案四:完善版 - 支持多种记录方式
  5. 方案五:结合框架中间件(Laravel)
  6. 关键建议

在PHP中实现慢执行日志分离,主要有以下几种方案,我帮你逐一分析:

修改 php.ini 配置(最基础)

; 开启慢日志
slowlog = "/var/log/php/slow.log"
request_slowlog_timeout = 2
; 或者针对PHP-FPM
[www]
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 2s

优点:实现简单
缺点:只能记录超时脚本,无法自定义记录内容


自定义慢日志类(推荐)

<?php
class SlowLogHandler {
    private $threshold;
    private $logFile;
    private static $instance = null;
    private function __construct($threshold = 1) {
        $this->threshold = $threshold; // 秒数
        $this->logFile = __DIR__ . '/slow_logs_' . date('Y-m-d') . '.log';
    }
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    /**
     * 记录慢执行日志
     */
    public function log($startTime) {
        $elapsed = microtime(true) - $startTime;
        if ($elapsed >= $this->threshold) {
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
            $backtrace = $this->formatBacktrace($trace);
            $logData = [
                'time'       => date('Y-m-d H:i:s'),
                'elapsed'    => round($elapsed, 3),
                'uri'        => $_SERVER['REQUEST_URI'] ?? '',
                'method'     => $_SERVER['REQUEST_METHOD'] ?? '',
                'ip'         => $_SERVER['REMOTE_ADDR'] ?? '',
                'memory'     => memory_get_usage(true),
                'backtrace'  => $backtrace,
                'server'     => gethostname()
            ];
            file_put_contents(
                $this->logFile,
                json_encode($logData, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT) . "\n",
                FILE_APPEND | LOCK_EX
            );
        }
    }
    private function formatBacktrace($trace) {
        $result = [];
        foreach (array_slice($trace, 0, 10) as $item) {
            $result[] = sprintf(
                "%s:%d %s->%s",
                $item['file'] ?? 'unknown',
                $item['line'] ?? 0,
                $item['class'] ?? '',
                $item['function'] ?? ''
            );
        }
        return implode("\n", array_slice($result, 1)); // 跳过log()本身
    }
}

使用示例

<?php
// 在项目入口文件或基类中添加
$startTime = microtime(true);
// 注册关闭函数,在脚本结束时检查
register_shutdown_function(function() {
    SlowLogHandler::getInstance()->log($GLOBALS['_start_time']);
});
$GLOBALS['_start_time'] = $startTime;
// 或者使用中间件/拦截器方式
class SlowLogMiddleware {
    public function handle($request, $next) {
        $start = microtime(true);
        $response = $next($request);
        $elapsed = microtime(true) - $start;
        if ($elapsed > 2) {
            error_log("Slow request: {$elapsed}s, " . $request->getUri());
        }
        return $response;
    }
}

数据库查询慢日志

<?php
class QueryLogger {
    private $slowQueries = [];
    private $threshold = 0.5; // 500ms
    public function logQuery($sql, $params, $startTime) {
        $elapsed = microtime(true) - $startTime;
        if ($elapsed > $this->threshold) {
            $this->slowQueries[] = [
                'sql'      => $this->formatSql($sql, $params),
                'elapsed'  => $elapsed,
                'trace'    => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
            ];
        }
    }
    public function saveLog() {
        if (empty($this->slowQueries)) {
            return;
        }
        $log = [
            'datetime' => date('Y-m-d H:i:s'),
            'queries'  => $this->slowQueries
        ];
        file_put_contents(
            '/path/to/sql-slow-log.log',
            json_encode($log, JSON_UNESCAPED_UNICODE) . "\n",
            FILE_APPEND
        );
    }
}
// PDO 扩展示例
class SlowQueryPDO extends PDO {
    private $slowLogger;
    public function query() {
        $start = microtime(true);
        $result = parent::query(...func_get_args());
        $this->checkSlowQuery($start);
        return $result;
    }
    private function checkSlowQuery($startTime) {
        $elapsed = microtime(true) - $startTime;
        if ($elapsed > 0.5) {
            // 记录日志
        }
    }
}

完善版 - 支持多种记录方式

<?php
interface LogWriterInterface {
    public function write(array $logData);
}
class FileLogWriter implements LogWriterInterface {
    private $path;
    public function __construct($path) {
        $this->path = $path;
    }
    public function write(array $logData) {
        file_put_contents(
            $this->path,
            json_encode($logData, JSON_UNESCAPED_UNICODE) . "\n",
            FILE_APPEND | LOCK_EX
        );
    }
}
class RedisLogWriter implements LogWriterInterface {
    private $redis;
    private $key;
    public function __construct($redis, $key) {
        $this->redis = $redis;
        $this->key = $key;
    }
    public function write(array $logData) {
        $this->redis->rPush($this->key, json_encode($logData));
        $this->redis->expire($this->key, 86400); // 保留1天
    }
}
class SlowMonitor {
    private $writer;
    private $threshold;
    private $startTime;
    public function __construct(LogWriterInterface $writer, $threshold = 2) {
        $this->writer = $writer;
        $this->threshold = $threshold;
        $this->startTime = microtime(true);
    }
    public function checkAndLog() {
        $elapsed = microtime(true) - $this->startTime;
        if ($elapsed < $this->threshold) {
            return;
        }
        $logData = [
            'datetime'  => date('Y-m-d H:i:s'),
            'elapsed'   => round($elapsed, 3),
            'uri'       => $_SERVER['REQUEST_URI'] ?? '',
            'method'    => $_SERVER['REQUEST_METHOD'] ?? '',
            'client'    => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent'=> $_SERVER['HTTP_USER_AGENT'] ?? '',
            'memory'    => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true),
            'hostname'  => gethostname(),
            'php_version' => PHP_VERSION,
            'trace'     => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
        ];
        $this->writer->write($logData);
    }
    public function __destruct() {
        $this->checkAndLog();
    }
}
// 使用示例
$writer = new FileLogWriter('/var/log/php/slow-requests.log');
// $writer = new RedisLogWriter($redis, 'slow_requests');
$monitor = new SlowMonitor($writer, 2);
// 在请求结束时调用
register_shutdown_function([$monitor, 'checkAndLog']);

结合框架中间件(Laravel)

// app/Http/Middleware/RequestSlowLogger.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
class RequestSlowLogger
{
    public function handle($request, Closure $next)
    {
        $startTime = microtime(true);
        $response = $next($request);
        if (config('app.slow_threshold')) {
            $elapsed = microtime(true) - $startTime;
            if ($elapsed >= config('app.slow_threshold')) {
                Log::channel('slow')->info('Slow request', [
                    'url'         => $request->fullUrl(),
                    'method'      => $request->method(),
                    'elapsed'     => round($elapsed, 2),
                    'memory'      => memory_get_peak_usage(true),
                    'session_id'  => $request->session()->getId(),
                    'user_id'     => auth()->id()
                ]);
            }
        }
        return $response;
    }
}

关键建议

  1. 分级处理:根据耗时不同级别记录(警告、严重)
  2. 上下文信息:记录请求ID、用户ID、页面URL等
  3. 日志轮转:使用 logrotate 进行日志分割
  4. 性能影响:确保日志记录本身不会影响性能
  5. 监控报警:可以将慢日志接入监控系统(如ELK)

推荐使用方案二方案四,它们灵活且易于集成到现有项目中,如果需要更专业的性能分析,可以考虑使用 APM 工具(如 Sentry、XHProf)。

抱歉,评论功能暂时关闭!