本文目录导读:

在PHP项目中实现日志分级,我推荐几种实用方案,从简单到完整逐步介绍。
使用PSR-3日志接口标准(推荐)
PSR-3定义了8个日志级别,是目前PHP生态的标准做法:
<?php
// 使用 monolog/monolog 库(最流行)
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// 创建日志通道
$log = new Logger('my_app');
// 添加处理器,设置不同级别
$log->pushHandler(new StreamHandler(__DIR__.'/logs/debug.log', Logger::DEBUG));
$log->pushHandler(new StreamHandler(__DIR__.'/logs/error.log', Logger::ERROR));
// 使用不同级别记录日志
$log->debug('调试信息', ['user_id' => 123]);
$log->info('用户登录成功');
$log->notice('需要注意的情况');
$log->warning('警告:磁盘空间不足');
$log->error('数据库连接失败', ['db' => 'mysql']);
$log->critical('系统严重错误');
$log->alert('需要立即处理');
$log->emergency('系统不可用');
手动实现的日志分级类
如果你不想引入外部库:
<?php
class Logger {
const DEBUG = 100;
const INFO = 200;
const NOTICE = 250;
const WARNING = 300;
const ERROR = 400;
const CRITICAL = 500;
const ALERT = 550;
const EMERGENCY = 600;
private $minLevel;
private $logPath;
private $levelNames = [
100 => 'DEBUG',
200 => 'INFO',
250 => 'NOTICE',
300 => 'WARNING',
400 => 'ERROR',
500 => 'CRITICAL',
550 => 'ALERT',
600 => 'EMERGENCY'
];
public function __construct($logPath, $minLevel = self::DEBUG) {
$this->logPath = $logPath;
$this->minLevel = $minLevel;
if (!is_dir(dirname($logPath))) {
mkdir(dirname($logPath), 0755, true);
}
}
public function log($level, $message, array $context = []) {
if ($level < $this->minLevel) {
return; // 低于最低级别,不记录
}
// 替换上下文占位符 {key}
if (!empty($context)) {
$message = preg_replace_callback('/\{([^}]+)\}/', function($matches) use ($context) {
return isset($context[$matches[1]]) ? $context[$matches[1]] : $matches[0];
}, $message);
}
$levelName = $this->levelNames[$level] ?? 'UNKNOWN';
$timestamp = date('Y-m-d H:i:s');
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$file = $trace[1]['file'] ?? 'unknown';
$line = $trace[1]['line'] ?? 0;
$logEntry = sprintf(
"[%s] %s: %s (in %s:%d)\n",
$timestamp,
$levelName,
$message,
$file,
$line
);
// 按级别分文件存储
$levelFile = $this->logPath . '/' . strtolower($levelName) . '.log';
file_put_contents($levelFile, $logEntry, FILE_APPEND | LOCK_EX);
// 同时写入综合日志
file_put_contents($this->logPath . '/app.log', $logEntry, FILE_APPEND | LOCK_EX);
}
public function debug($message, array $context = []) {
$this->log(self::DEBUG, $message, $context);
}
public function info($message, array $context = []) {
$this->log(self::INFO, $message, $context);
}
public function warning($message, array $context = []) {
$this->log(self::WARNING, $message, $context);
}
public function error($message, array $context = []) {
$this->log(self::ERROR, $message, $context);
}
}
// 使用示例
$logger = new Logger(__DIR__ . '/logs', Logger::INFO);
$logger->debug('这个不会记录'); // 因为最低级别是INFO
$logger->info('用户 #{user_id} 登录成功', ['user_id' => 123]);
$logger->error('数据库查询失败: {error}', ['error' => 'Connection refused']);
使用环境变量控制日志级别
<?php
class EnvironmentLogger {
private $environment;
private $levelMap;
public function __construct() {
$this->environment = getenv('APP_ENV') ?: 'production';
// 不同环境设置不同级别
$this->levelMap = [
'development' => Logger::DEBUG,
'staging' => Logger::INFO,
'production' => Logger::WARNING
];
}
public function getMinLevel() {
return $this->levelMap[$this->environment] ?? Logger::WARNING;
}
}
// 使用
$envLogger = new EnvironmentLogger();
$logger = new Logger(__DIR__ . '/logs', $envLogger->getMinLevel());
配置文件驱动的高级方案
<?php
// config/logging.php
return [
'default' => 'stack',
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack']
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/app.log'),
'level' => 'debug',
'days' => 14
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'level' => 'critical'
],
'emergency' => [
'path' => storage_path('logs/emergency.log')
]
]
];
// 使用(类似 Laravel 的实现)
class LogManager {
private $channels = [];
private $config;
public function __construct($config) {
$this->config = $config;
}
public function channel($name = null) {
$name = $name ?: $this->config['default'];
if (!isset($this->channels[$name])) {
$this->channels[$name] = $this->createChannel($name);
}
return $this->channels[$name];
}
private function createChannel($name) {
$config = $this->config['channels'][$name];
switch ($config['driver']) {
case 'daily':
return new DailyLogger($config['path'], $config['level'], $config['days']);
case 'slack':
return new SlackLogger($config['url'], $config['level']);
default:
return new Logger($config['path'], $config['level']);
}
}
}
实际项目中的最佳实践
<?php
// 1. 创建全局日志实例(单例)
class AppLogger {
private static $instance = null;
private $logger;
private function __construct() {
$logLevel = $this->determineLogLevel();
$this->logger = new Logger(__DIR__ . '/logs', $logLevel);
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance->logger;
}
private function determineLogLevel() {
// 从环境变量或配置文件读取
$env = getenv('APP_ENV') ?: 'production';
$levels = [
'development' => Logger::DEBUG,
'testing' => Logger::DEBUG,
'staging' => Logger::INFO,
'production' => Logger::WARNING
];
return $levels[$env] ?? Logger::WARNING;
}
}
// 2. 实际使用
$logger = AppLogger::getInstance();
try {
// 业务逻辑
$logger->info('开始处理请求', ['url' => $_SERVER['REQUEST_URI']]);
// ...
$logger->info('请求完成', ['duration' => $duration]);
} catch (Exception $e) {
$logger->error('请求处理失败', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
if ($e instanceof DatabaseException) {
$logger->critical('数据库不可用', [
'db_host' => DB_HOST,
'error' => $e->getMessage()
]);
}
}
日志分级的最佳实践建议
// 日志级别使用建议表
$usageGuide = [
'debug' => [
'用途' => '开发调试,详细运行信息',
'建议' => '只在开发环境启用'
],
'info' => [
'用途' => '常规操作日志,用户行为记录',
'建议' => '开发环境全开,生产环境选择性记录'
],
'notice' => [
'用途' => '正常但重要的事件',
'建议' => '所有环境都记录'
],
'warning' => [
'用途' => '潜在问题,不影响运行',
'建议' => '生产环境必须记录'
],
'error' => [
'用途' => '运行时错误,需要处理',
'建议' => '立即通知开发团队'
],
'critical' => [
'用途' => '系统级严重错误',
'建议' => '触发报警机制'
],
'alert' => [
'用途' => '需要立即处理',
'建议' => '发送短信/电话通知'
],
'emergency' => [
'用途' => '系统不可用',
'建议' => '全员紧急通知'
]
];
集成到框架中
// 以 Slim 框架为例
$container['logger'] = function($c) {
$logger = new Logger('slim_app');
// 文件处理器
$fileHandler = new StreamHandler(__DIR__ . '/logs/app.log', Logger::DEBUG);
$logger->pushHandler($fileHandler);
// 错误邮件处理器(仅生产环境)
if ($c->get('settings')['environment'] === 'production') {
$mailHandler = new SwiftMailerHandler(
new Swift_Mailer($transport),
'admin@example.com',
'应用错误通知',
Logger::ERROR
);
$logger->pushHandler($mailHandler);
}
return $logger;
};
总结建议
- 使用 PSR-3 标准:便于与其他库集成
- 配置驱动:通过环境变量控制日志级别
- 区分环境:开发环境记录所有级别,生产环境只记录 WARNING 以上
- 合理使用上下文:记录相关数据便于调试
- 考虑性能:使用异步写入或队列处理高并发场景
- 轮转日志:避免单个日志文件过大
选择哪种方案取决于项目规模:
- 小项目:手动实现简单类即可
- 中型项目:使用 Monolog
- 大型项目:使用框架内置日志系统或 ELK 等专业方案