本文目录导读:

在PHP项目中进行日志归档和按周期压缩转储,通常有几种常见方案,以下是完整的实现方法:
使用Linux日志轮转工具(推荐)
logrotate配置
# /etc/logrotate.d/php-app
/var/log/php-app/*.log {
daily # 按天轮转
rotate 30 # 保留30份
compress # 压缩旧日志
delaycompress # 延迟压缩(保留最新一份不压缩)
missingok # 日志不存在不报错
notifempty # 空文件不轮转
dateext # 使用日期后缀
dateformat -%Y%m%d # 日期格式
copytruncate # 复制并截断原文件(不影响PHP写入)
postrotate
# 可选:重启PHP进程释放文件句柄
# systemctl reload php-fpm
endscript
}
手动测试配置
sudo logrotate -d /etc/logrotate.d/php-app # 调试模式 sudo logrotate -f /etc/logrotate.d/php-app # 强制轮转
PHP内置日志轮转实现
简单版:按文件大小轮转
<?php
class LogRotator {
private $logFile;
private $maxSize;
private $maxFiles;
private $compress;
public function __construct($logFile, $maxSize = 104857600, $maxFiles = 30, $compress = true) {
$this->logFile = $logFile;
$this->maxSize = $maxSize; // 100MB默认
$this->maxFiles = $maxFiles;
$this->compress = $compress;
}
public function write($message) {
file_put_contents($this->logFile, $message . PHP_EOL, FILE_APPEND | LOCK_EX);
if (filesize($this->logFile) > $this->maxSize) {
$this->rotate();
}
}
private function rotate() {
// 删除最旧文件
$oldestFile = $this->logFile . '.' . $this->maxFiles . '.gz';
if (file_exists($oldestFile)) {
unlink($oldestFile);
}
// 重命名并压缩旧文件
for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
$oldFile = $this->logFile . '.' . $i;
$oldFileGz = $oldFile . '.gz';
// 解压后的文件
if (file_exists($oldFileGz)) {
if ($i == $this->maxFiles - 1) {
unlink($oldFileGz);
} else {
rename($oldFileGz, $this->logFile . '.' . ($i + 1) . '.gz');
}
}
// 直接压缩的文件
if (file_exists($oldFile)) {
if ($this->compress) {
$this->compressFile($oldFile);
rename($oldFile . '.gz', $this->logFile . '.' . ($i + 1) . '.gz');
} else {
rename($oldFile, $this->logFile . '.' . ($i + 1));
}
}
}
// 当前日志文件编号为1
copy($this->logFile, $this->logFile . '.1');
if ($this->compress) {
$this->compressFile($this->logFile . '.1');
rename($this->logFile . '.1.gz', $this->logFile . '.1.gz');
}
// 清空当前日志
file_put_contents($this->logFile, '');
}
private function compressFile($file) {
$data = file_get_contents($file);
$gzData = gzencode($data, 9);
file_put_contents($file . '.gz', $gzData);
unlink($file);
}
}
高级版:按日期轮转
<?php
class DateBasedLogRotator {
private $logFile;
private $retentionDays;
private $compress;
private $dateFormat;
public function __construct($logFile, $retentionDays = 30, $compress = true) {
$this->logFile = $logFile;
$this->retentionDays = $retentionDays;
$this->compress = $compress;
$this->dateFormat = 'Y-m-d';
}
public function write($message) {
$today = date($this->dateFormat);
$currentDate = $this->getCurrentDate();
if ($currentDate !== $today) {
$this->archive();
}
file_put_contents($this->logFile, $message . PHP_EOL, FILE_APPEND | LOCK_EX);
}
private function archive() {
if (!file_exists($this->logFile)) return;
$archiveName = $this->logFile . '.' . date($this->dateFormat, strtotime('-1 day'));
if ($this->compress) {
$data = file_get_contents($this->logFile);
$gzData = gzencode($data, 9);
file_put_contents($archiveName . '.gz', $gzData);
} else {
rename($this->logFile, $archiveName);
}
file_put_contents($this->logFile, '');
$this->updateDateFile();
$this->cleanupOldFiles();
}
private function cleanupOldFiles() {
$pattern = $this->logFile . '.*';
$files = glob($pattern);
foreach ($files as $file) {
// 提取日期
preg_match('/\.(\d{4}-\d{2}-\d{2})/', $file, $matches);
if (isset($matches[1])) {
$fileDate = strtotime($matches[1]);
if ($fileDate < strtotime("-{$this->retentionDays} days")) {
unlink($file);
}
}
}
}
private function getCurrentDate() {
$dateFile = $this->logFile . '.date';
if (file_exists($dateFile)) {
return file_get_contents($dateFile);
}
return date($this->dateFormat);
}
private function updateDateFile() {
$dateFile = $this->logFile . '.date';
file_put_contents($dateFile, date($this->dateFormat));
}
}
使用Monolog日志库(推荐)
安装
composer require monolog/monolog
配置轮转
<?php
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;
$log = new Logger('app');
// 按天轮转,保留30天,压缩
$handler = new RotatingFileHandler('/var/log/php-app/app.log', 30, Logger::DEBUG);
$handler->setFilenameFormat('{filename}-{date}', 'Y-m-d'); // 自定义文件名格式
$handler->setFormatter(new LineFormatter(null, null, true, true));
// 启用压缩
$handler->setRotationCompress(true); // 自动压缩为.gz
$log->pushHandler($handler);
// 使用
$log->info('This is a log message', ['context' => 'value']);
按周/月轮转实现
自定义轮转周期
<?php
class CustomPeriodLogRotator {
private $logFile;
private $period; // 'daily', 'weekly', 'monthly'
private $retention;
private $compress;
public function __construct($logFile, $period = 'daily', $retention = 30, $compress = true) {
$this->logFile = $logFile;
$this->period = $period;
$this->retention = $retention;
$this->compress = $compress;
}
public function checkAndRotate() {
$currentPeriod = $this->getCurrentPeriod();
$storedPeriod = $this->getStoredPeriod();
if ($currentPeriod !== $storedPeriod) {
$this->doRotate($storedPeriod);
$this->saveCurrentPeriod($currentPeriod);
}
}
private function getCurrentPeriod() {
switch ($this->period) {
case 'daily':
return date('Y-m-d');
case 'weekly':
return date('Y-W');
case 'monthly':
return date('Y-m');
default:
return date('Y-m-d');
}
}
private function doRotate($oldPeriod) {
if (!file_exists($this->logFile)) return;
$archiveName = $this->logFile . '.' . $oldPeriod;
if ($this->compress) {
$data = file_get_contents($this->logFile);
file_put_contents($archiveName . '.gz', gzencode($data, 9));
} else {
copy($this->logFile, $archiveName);
}
file_put_contents($this->logFile, '');
$this->cleanup();
}
private function cleanup() {
$files = glob($this->logFile . '.*');
$now = time();
foreach ($files as $file) {
$fileTime = filemtime($file);
$ageInSeconds = $now - $fileTime;
$retentionInSeconds = $this->retention * 24 * 3600;
if ($ageInSeconds > $retentionInSeconds) {
unlink($file);
}
}
}
private function getStoredPeriod() {
$metaFile = $this->logFile . '.meta';
if (file_exists($metaFile)) {
return file_get_contents($metaFile);
}
return $this->getCurrentPeriod();
}
private function saveCurrentPeriod($period) {
file_put_contents($this->logFile . '.meta', $period);
}
}
最佳实践建议
生产环境配置
<?php
// config/log.php
return [
'default' => [
'driver' => 'daily',
'path' => storage_path('logs/app.log'),
'level' => 'debug',
'days' => 30,
'compress' => true,
],
// 分离错误日志
'error' => [
'driver' => 'daily',
'path' => storage_path('logs/error.log'),
'level' => 'error',
'days' => 60,
'compress' => true,
],
];
定时任务(Crontab)
# 每天凌晨执行日志轮转检查 0 0 * * * /usr/bin/php /path/to/rotate.php >> /var/log/php-rotate.log 2>&1
rotate.php脚本
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
$logger = new Logger('rotate');
$handler = new RotatingFileHandler('/var/log/php-app/app.log', 30, Logger::DEBUG);
$handler->setRotationCompress(true);
$logger->pushHandler($handler);
// 触发轮转(Monolog自动处理)
$logger->info('Log rotation check completed');
- 推荐使用logrotate:系统级工具,稳定高效
- PHP实现:适合没有系统权限的环境
- Monolog:专业日志库,功能完整
- 按需选择:根据项目需求和运维环境决定
选择方案时考虑:服务器负载、日志量大小、运维能力、是否需要实时压缩等因素。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播