如何为PHP项目设置请求日志?

wen PHP项目 2

本文目录导读:

如何为PHP项目设置请求日志?

  1. 基础方法:PHP内置错误日志
  2. 手动实现请求日志中间件
  3. 使用框架集成(以Laravel为例)
  4. 使用 Monolog(独立PHP项目)
  5. 最佳实践与配置示例
  6. 日志管理与分析建议

为PHP项目设置请求日志,有以下几种常用方法,从简单到完整逐步介绍:

基础方法:PHP内置错误日志

php.ini 配置:

log_errors = On
error_log = /var/log/php_errors.log
; 记录所有错误级别
error_reporting = E_ALL

代码中使用 error_log():

error_log("用户请求:{$_SERVER['REQUEST_URI']}", 3, '/var/log/requests.log');

手动实现请求日志中间件

<?php
class RequestLogger {
    private $logFile;
    public function __construct($logFile = '/var/log/requests.log') {
        $this->logFile = $logFile;
        $this->ensureLogDirectory();
    }
    public function log($request = null, $response = null) {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'CLI',
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
            'uri' => $_SERVER['REQUEST_URI'] ?? '/',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'status_code' => http_response_code(),
            'response_time_ms' => $this->getResponseTime(),
            'request_body' => file_get_contents('php://input')
        ];
        // 过滤敏感信息
        $logData['request_body'] = $this->maskSensitiveData($logData['request_body']);
        $logEntry = json_encode($logData) . PHP_EOL;
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    private function getResponseTime() {
        if (defined('REQUEST_START_TIME')) {
            return round((microtime(true) - REQUEST_START_TIME) * 1000, 2);
        }
        return 0;
    }
    private function maskSensitiveData($data) {
        $sensitive = ['password', 'token', 'credit_card', 'ssn'];
        $decoded = json_decode($data, true);
        if (is_array($decoded)) {
            foreach ($sensitive as $key) {
                if (isset($decoded[$key])) {
                    $decoded[$key] = str_repeat('*', strlen($decoded[$key]));
                }
            }
            return json_encode($decoded);
        }
        return $data;
    }
    private function ensureLogDirectory() {
        $dir = dirname($this->logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
    }
}
// 在项目入口文件使用
define('REQUEST_START_TIME', microtime(true));
$logger = new RequestLogger();
register_shutdown_function([$logger, 'log']);

使用框架集成(以Laravel为例)

Laravel 内置日志配置:

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'slack'],
    ],
    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'info',
        'days' => 14,
    ],
],
// 创建中间件
php artisan make:middleware RequestLogger
// app/Http/Middleware/RequestLogger.php
public function handle($request, Closure $next)
{
    $startTime = microtime(true);
    $response = $next($request);
    Log::channel('daily')->info('Request logged', [
        'url' => $request->fullUrl(),
        'method' => $request->method(),
        'ip' => $request->ip(),
        'user_agent' => $request->userAgent(),
        'status_code' => $response->status(),
        'duration_ms' => round((microtime(true) - $startTime) * 1000, 2),
        'request_data' => $this->filterSensitiveData($request->all()),
    ]);
    return $response;
}
// 在 Kernel.php 注册中间件
protected $middleware = [
    \App\Http\Middleware\RequestLogger::class,
];

使用 Monolog(独立PHP项目)

<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\JsonFormatter;
class RequestLogger {
    private $logger;
    public function __construct() {
        $this->logger = new Logger('request');
        // 按日期轮转日志
        $handler = new RotatingFileHandler('/var/log/requests.log', 30, Logger::INFO);
        // JSON格式输出,方便日志分析
        $handler->setFormatter(new JsonFormatter());
        $this->logger->pushHandler($handler);
    }
    public function logRequest() {
        $this->logger->info('HTTP Request', [
            'timestamp' => date('c'),
            'method' => $_SERVER['REQUEST_METHOD'],
            'path' => $_SERVER['REQUEST_URI'],
            'query' => $_SERVER['QUERY_STRING'],
            'ip' => $_SERVER['REMOTE_ADDR'],
            'headers' => getallheaders(),
            'body' => $this->getRequestBody(),
        ]);
    }
    private function getRequestBody() {
        $body = file_get_contents('php://input');
        return mb_strlen($body) > 10000 ? substr($body, 0, 10000) . '...' : $body;
    }
}
// 使用
$logger = new RequestLogger();
$logger->logRequest();

最佳实践与配置示例

综合配置文件:

<?php
// config/logging.php
return [
    'request_logging' => [
        'enabled' => true,
        'channel' => 'request',
        'format' => 'json', // json, line, structured
        'fields' => [
            'basic' => ['method', 'uri', 'ip'],
            'extended' => ['headers', 'query_params', 'request_body'],
            'performance' => ['response_time', 'memory_usage'],
        ],
        'sensitive_fields' => [
            'password', 'token', 'secret', 'credit_card', 'ssn'
        ],
        'exclude_paths' => [
            '/health', '/metrics', '/favicon.ico'
        ],
        'max_body_size' => 10000, // 限制请求体大小
        'rotation' => [
            'enabled' => true,
            'max_files' => 30,
            'max_size' => '100MB',
        ],
    ],
];

日志管理与分析建议

  1. 日志轮转:使用 logrotate 或框架内置功能
  2. 使用ELK Stack:Elasticsearch + Logstash + Kibana
  3. 日志级别:DEBUG < INFO < WARNING < ERROR < CRITICAL
  4. 不要记录敏感数据:密码、令牌、信用卡信息
  5. 性能考虑:异步日志写入或使用消息队列

logrotate 配置示例:

/var/log/php-requests.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    create 0640 www-data www-data
    postrotate
        /usr/bin/killall -USR1 php-fpm
    endscript
}

选择哪种方法取决于你的项目规模:

  • 小型项目:使用 error_log() 或文件追加
  • 中型项目:使用 Monolog 或框架内置功能
  • 大型项目:使用 ELK Stack 或专业的日志服务

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