本文目录导读:

在PHP项目中实现日志轮转(Log Rotation)有多种方法,以下是几种常见的实现方案:
使用PHP内置函数手动实现
<?php
class LogRotation {
private $logFile;
private $maxSize; // 字节
private $maxFiles; // 保留的文件数
public function __construct($logFile, $maxSize = 10485760, $maxFiles = 5) {
$this->logFile = $logFile;
$this->maxSize = $maxSize; // 默认10MB
$this->maxFiles = $maxFiles;
}
public function write($message) {
// 检查是否需要轮转
if (file_exists($this->logFile) && filesize($this->logFile) >= $this->maxSize) {
$this->rotate();
}
// 写入日志
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] {$message}" . PHP_EOL;
file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
}
private function rotate() {
// 删除最旧的日志文件
$oldestFile = $this->logFile . '.' . $this->maxFiles;
if (file_exists($oldestFile)) {
unlink($oldestFile);
}
// 轮转文件
for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
$oldFile = $this->logFile . '.' . $i;
$newFile = $this->logFile . '.' . ($i + 1);
if (file_exists($oldFile)) {
rename($oldFile, $newFile);
}
}
// 将当前日志重命名为 .1
rename($this->logFile, $this->logFile . '.1');
}
}
// 使用示例
$logger = new LogRotation('/var/log/app.log', 10485760, 5);
$logger->write('这是一条测试日志');
?>
使用Monolog库(推荐)
Monolog是PHP最流行的日志库,内置日志轮转支持:
composer require monolog/monolog
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
// 创建日志实例
$log = new Logger('app');
// 创建轮转文件处理器
// 参数:日志文件路径,最大文件数,日志级别
$handler = new RotatingFileHandler(
'/var/log/app.log', // 日志文件路径
7, // 保留最近7天的日志
Logger::DEBUG // 日志级别
);
// 设置日志文件名格式(可选)
$handler->setFilenameFormat(
'{filename}-{date}',
'Y-m-d'
);
$log->pushHandler($handler);
// 使用示例
$log->info('这是一条信息日志');
$log->error('这是一条错误日志', ['user_id' => 123]);
?>
基于时间的轮转(按天轮转)
<?php
class DailyLogRotation {
private $logDir;
private $logFilePrefix;
private $retentionDays;
public function __construct($logDir, $prefix = 'app', $retentionDays = 30) {
$this->logDir = rtrim($logDir, '/');
$this->logFilePrefix = $prefix;
$this->retentionDays = $retentionDays;
if (!is_dir($this->logDir)) {
mkdir($this->logDir, 0755, true);
}
}
public function write($message) {
$todayLog = $this->getTodayLogFile();
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] {$message}" . PHP_EOL;
file_put_contents($todayLog, $logMessage, FILE_APPEND | LOCK_EX);
// 清理过期日志
$this->cleanOldLogs();
}
private function getTodayLogFile() {
$date = date('Y-m-d');
return "{$this->logDir}/{$this->logFilePrefix}-{$date}.log";
}
private function cleanOldLogs() {
$files = glob("{$this->logDir}/{$this->logFilePrefix}-*.log");
$now = time();
foreach ($files as $file) {
if (is_file($file)) {
// 从文件名提取日期
preg_match('/-(\d{4}-\d{2}-\d{2})\.log$/', $file, $matches);
if (isset($matches[1])) {
$fileDate = strtotime($matches[1]);
$daysOld = ($now - $fileDate) / 86400;
if ($daysOld > $this->retentionDays) {
unlink($file);
}
}
}
}
}
}
// 使用示例
$logger = new DailyLogRotation('/var/log/app', 'app', 30);
$logger->write('用户登录成功');
?>
使用操作系统级日志轮转(推荐用于生产环境)
Linux logrotate配置
创建 /etc/logrotate.d/php-app:
/var/log/php-app/*.log {
daily # 每天轮转
missingok # 如果日志文件丢失,继续执行不报错
rotate 7 # 保留7个备份
compress # 压缩旧日志
delaycompress # 延迟压缩(下一次轮转时压缩)
notifempty # 如果日志为空,不轮转
create 0640 www-data www-data # 创建新日志文件的权限和所有者
sharedscripts # 所有日志文件共享一个脚本
postrotate
/bin/kill -USR1 `cat /var/run/php-fpm.pid 2>/dev/null` 2>/dev/null || true
endscript
}
高级配置示例:完整的日志管理类
<?php
class AdvancedLogger {
private $logDir;
private $currentLogFile;
private $maxFileSize;
private $maxFiles;
private $dateFormat;
public function __construct($config = []) {
$this->logDir = $config['log_dir'] ?? '/var/log/app';
$this->maxFileSize = $config['max_file_size'] ?? 10485760; // 10MB
$this->maxFiles = $config['max_files'] ?? 5;
$this->dateFormat = $config['date_format'] ?? 'Y-m-d H:i:s';
if (!is_dir($this->logDir)) {
mkdir($this->logDir, 0755, true);
}
$this->currentLogFile = $this->logDir . '/app.log';
}
public function log($level, $message, $context = []) {
$this->checkRotation();
$timestamp = date($this->dateFormat);
$contextStr = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] [{$level}] {$message}{$contextStr}" . PHP_EOL;
// 写入文件
file_put_contents($this->currentLogFile, $logMessage, FILE_APPEND | LOCK_EX);
// 同时输出到控制台(开发环境)
if (php_sapi_name() === 'cli') {
echo $logMessage;
}
}
private function checkRotation() {
if (!file_exists($this->currentLogFile)) {
return;
}
if (filesize($this->currentLogFile) >= $this->maxFileSize) {
$this->rotate();
}
}
private function rotate() {
$info = pathinfo($this->currentLogFile);
$base = $info['dirname'] . '/' . $info['filename'];
// 删除最旧的文件
$oldestFile = $base . '.' . $this->maxFiles . '.log';
if (file_exists($oldestFile)) {
unlink($oldestFile);
}
// 轮转文件
for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
$oldFile = $base . '.' . $i . '.log';
$newFile = $base . '.' . ($i + 1) . '.log';
if (file_exists($oldFile)) {
rename($oldFile, $newFile);
}
}
// 压缩日志(可选)
$logFile = $base . '.log';
if (file_exists($logFile)) {
rename($logFile, $base . '.1.log');
$this->compressLog($base . '.1.log');
}
}
private function compressLog($file) {
if (function_exists('gzopen')) {
$gzFile = $file . '.gz';
$fp = fopen($file, 'r');
$zp = gzopen($gzFile, 'w9');
while (!feof($fp)) {
gzwrite($zp, fread($fp, 8192));
}
fclose($fp);
gzclose($zp);
unlink($file);
}
}
// 便捷方法
public function info($message, $context = []) {
$this->log('INFO', $message, $context);
}
public function error($message, $context = []) {
$this->log('ERROR', $message, $context);
}
public function warning($message, $context = []) {
$this->log('WARNING', $message, $context);
}
public function debug($message, $context = []) {
$this->log('DEBUG', $message, $context);
}
}
// 使用示例
$logger = new AdvancedLogger([
'log_dir' => '/var/log/myapp',
'max_file_size' => 5242880, // 5MB
'max_files' => 10
]);
$logger->info('应用启动');
$logger->error('数据库连接失败', ['db' => 'mysql', 'host' => 'localhost']);
?>
建议
- 生产环境:推荐使用
logrotate系统工具,性能更好 - 开发环境:使用 Monolog 或自定义轮转逻辑
- 重要考虑:
- 确保日志目录有适当的权限
- 考虑使用异步写入提高性能
- 监控磁盘空间使用情况
- 设置合理的保留策略
选择哪种方案取决于你的具体需求、项目规模和部署环境。