本文目录导读:

在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;
}
}
关键建议
- 分级处理:根据耗时不同级别记录(警告、严重)
- 上下文信息:记录请求ID、用户ID、页面URL等
- 日志轮转:使用 logrotate 进行日志分割
- 性能影响:确保日志记录本身不会影响性能
- 监控报警:可以将慢日志接入监控系统(如ELK)
推荐使用方案二或方案四,它们灵活且易于集成到现有项目中,如果需要更专业的性能分析,可以考虑使用 APM 工具(如 Sentry、XHProf)。