Appearance
RabbitMQ 日志轮转
概述
日志轮转(Log Rotation)是日志管理的重要环节,可以防止日志文件无限增长导致磁盘空间耗尽。RabbitMQ 内置了日志轮转功能,同时也支持与系统级日志轮转工具配合使用。本文将详细介绍 RabbitMQ 日志轮转的配置方法和最佳实践。
核心知识点
日志轮转方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 基于大小 | 文件达到指定大小时轮转 | 日志量稳定的场景 |
| 基于时间 | 按固定时间间隔轮转 | 日常运维管理 |
| 外部工具 | 使用 logrotate 等工具 | 复杂轮转需求 |
RabbitMQ 内置轮转配置
| 配置项 | 说明 | 默认值 |
|---|---|---|
| log.file.rotation.date | 轮转时间 | 无 |
| log.file.rotation.size | 轮转大小 | 无 |
| log.file.rotation.count | 保留文件数 | 5 |
轮转时间格式
$D0 - 每天午夜
$D1 - 每周一午夜
$D5 - 每周五午夜
$W0 - 每周日午夜
$M1D0 - 每月1号午夜
$Q1D0 - 每季度1号午夜配置示例
基于大小的轮转
bash
log.file.rotation.size = 104857600
log.file.rotation.count = 10基于时间的轮转
bash
log.file.rotation.date = $D0
log.file.rotation.count = 30混合轮转策略
bash
log.file.rotation.date = $D0
log.file.rotation.size = 52428800
log.file.rotation.count = 20完整配置示例
bash
log.file.level = info
log.file = /var/log/rabbitmq/rabbit.log
log.file.formatter = text
log.file.rotation.date = $D0
log.file.rotation.size = 104857600
log.file.rotation.count = 30PHP 日志轮转管理类
php
<?php
class RabbitMQLogRotationManager
{
private $configFile;
private $logDir;
public function __construct(
$configFile = '/etc/rabbitmq/rabbitmq.conf',
$logDir = '/var/log/rabbitmq'
) {
$this->configFile = $configFile;
$this->logDir = $logDir;
}
public function getRotationConfig()
{
$config = [
'enabled' => true,
'size' => null,
'date' => null,
'count' => 5,
];
if (!file_exists($this->configFile)) {
return $config;
}
$content = file_get_contents($this->configFile);
if (preg_match('/log\.file\.rotation\.size\s*=\s*(\d+)/', $content, $m)) {
$config['size'] = (int)$m[1];
}
if (preg_match('/log\.file\.rotation\.date\s*=\s*(\S+)/', $content, $m)) {
$config['date'] = $m[1];
}
if (preg_match('/log\.file\.rotation\.count\s*=\s*(\d+)/', $content, $m)) {
$config['count'] = (int)$m[1];
}
return $config;
}
public function setRotationBySize($sizeMB, $count = 10)
{
$sizeBytes = $sizeMB * 1024 * 1024;
$this->updateConfig('log.file.rotation.size', $sizeBytes);
$this->updateConfig('log.file.rotation.count', $count);
$this->removeConfig('log.file.rotation.date');
return [
'type' => 'size',
'size_mb' => $sizeMB,
'count' => $count,
];
}
public function setRotationByDate($pattern = '$D0', $count = 30)
{
$this->updateConfig('log.file.rotation.date', $pattern);
$this->updateConfig('log.file.rotation.count', $count);
$this->removeConfig('log.file.rotation.size');
return [
'type' => 'date',
'pattern' => $pattern,
'count' => $count,
];
}
public function setRotationHybrid($pattern, $sizeMB, $count)
{
$sizeBytes = $sizeMB * 1024 * 1024;
$this->updateConfig('log.file.rotation.date', $pattern);
$this->updateConfig('log.file.rotation.size', $sizeBytes);
$this->updateConfig('log.file.rotation.count', $count);
return [
'type' => 'hybrid',
'pattern' => $pattern,
'size_mb' => $sizeMB,
'count' => $count,
];
}
public function getLogFiles()
{
$files = [];
if (!is_dir($this->logDir)) {
return $files;
}
$iterator = new DirectoryIterator($this->logDir);
foreach ($iterator as $file) {
if ($file->isFile() && preg_match('/\.log/', $file->getFilename())) {
$files[] = [
'name' => $file->getFilename(),
'path' => $file->getPathname(),
'size' => $file->getSize(),
'size_human' => $this->formatBytes($file->getSize()),
'modified' => date('Y-m-d H:i:s', $file->getMTime()),
'is_rotated' => preg_match('/\.log\.\d+$/', $file->getFilename()),
];
}
}
usort($files, function($a, $b) {
return strcmp($b['modified'], $a['modified']);
});
return $files;
}
public function getDiskUsage()
{
$files = $this->getLogFiles();
$totalSize = 0;
$rotatedSize = 0;
$currentSize = 0;
foreach ($files as $file) {
$totalSize += $file['size'];
if ($file['is_rotated']) {
$rotatedSize += $file['size'];
} else {
$currentSize += $file['size'];
}
}
return [
'total_files' => count($files),
'rotated_files' => count(array_filter($files, fn($f) => $f['is_rotated'])),
'current_files' => count(array_filter($files, fn($f) => !$f['is_rotated'])),
'total_size' => $totalSize,
'total_size_human' => $this->formatBytes($totalSize),
'rotated_size' => $rotatedSize,
'rotated_size_human' => $this->formatBytes($rotatedSize),
'current_size' => $currentSize,
'current_size_human' => $this->formatBytes($currentSize),
];
}
public function cleanupOldLogs($keepCount = null)
{
$config = $this->getRotationConfig();
$keepCount = $keepCount ?: $config['count'];
$files = $this->getLogFiles();
$rotatedFiles = array_filter($files, fn($f) => $f['is_rotated']);
usort($rotatedFiles, function($a, $b) {
return strcmp($b['modified'], $a['modified']);
});
$deleted = [];
$toDelete = array_slice($rotatedFiles, $keepCount);
foreach ($toDelete as $file) {
if (unlink($file['path'])) {
$deleted[] = $file['name'];
}
}
return [
'deleted_count' => count($deleted),
'deleted_files' => $deleted,
'kept_count' => $keepCount,
];
}
public function forceRotate()
{
$mainLogFile = $this->findMainLogFile();
if (!$mainLogFile || !file_exists($mainLogFile)) {
return [
'success' => false,
'message' => 'Main log file not found',
];
}
$timestamp = date('YmdHis');
$rotatedFile = $mainLogFile . '.' . $timestamp;
if (rename($mainLogFile, $rotatedFile)) {
touch($mainLogFile);
return [
'success' => true,
'original' => $mainLogFile,
'rotated' => $rotatedFile,
];
}
return [
'success' => false,
'message' => 'Failed to rotate log file',
];
}
public function compressOldLogs($daysOld = 7)
{
$files = $this->getLogFiles();
$compressed = [];
$threshold = time() - ($daysOld * 86400);
foreach ($files as $file) {
if ($file['is_rotated'] && !preg_match('/\.gz$/', $file['name'])) {
$mtime = filemtime($file['path']);
if ($mtime < $threshold) {
$gzFile = $file['path'] . '.gz';
if ($this->compressFile($file['path'], $gzFile)) {
unlink($file['path']);
$compressed[] = $file['name'];
}
}
}
}
return [
'compressed_count' => count($compressed),
'compressed_files' => $compressed,
];
}
public function generateRotationReport()
{
$config = $this->getRotationConfig();
$usage = $this->getDiskUsage();
$files = $this->getLogFiles();
$report = "RabbitMQ 日志轮转报告\n";
$report .= str_repeat("=", 50) . "\n\n";
$report .= "轮转配置:\n";
$report .= "- 保留文件数: {$config['count']}\n";
if ($config['size']) {
$report .= "- 大小限制: " . $this->formatBytes($config['size']) . "\n";
}
if ($config['date']) {
$report .= "- 时间模式: {$config['date']}\n";
}
$report .= "\n磁盘使用:\n";
$report .= "- 总文件数: {$usage['total_files']}\n";
$report .= "- 总大小: {$usage['total_size_human']}\n";
$report .= "- 当前日志: {$usage['current_size_human']}\n";
$report .= "- 轮转日志: {$usage['rotated_size_human']}\n";
$report .= "\n文件列表:\n";
foreach (array_slice($files, 0, 10) as $file) {
$report .= sprintf(
"- %-30s %10s %s\n",
$file['name'],
$file['size_human'],
$file['modified']
);
}
return $report;
}
private function findMainLogFile()
{
if (file_exists($this->configFile)) {
$content = file_get_contents($this->configFile);
if (preg_match('/^log\.file\s*=\s*(.+)$/m', $content, $m)) {
return trim($m[1]);
}
}
return $this->logDir . '/rabbit.log';
}
private function updateConfig($key, $value)
{
$content = file_exists($this->configFile)
? file_get_contents($this->configFile)
: '';
$pattern = '/^' . preg_quote($key, '/') . '\s*=.*$/m';
$replacement = "{$key} = {$value}";
if (preg_match($pattern, $content)) {
$content = preg_replace($pattern, $replacement, $content);
} else {
$content = rtrim($content) . "\n{$replacement}\n";
}
$this->ensureConfigDir();
file_put_contents($this->configFile, $content);
}
private function removeConfig($key)
{
if (!file_exists($this->configFile)) {
return;
}
$content = file_get_contents($this->configFile);
$pattern = '/^' . preg_quote($key, '/') . '\s*=.*\n?/m';
$content = preg_replace($pattern, '', $content);
file_put_contents($this->configFile, $content);
}
private function ensureConfigDir()
{
$dir = dirname($this->configFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
private function formatBytes($bytes)
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
private function compressFile($source, $destination)
{
$fp = fopen($source, 'rb');
$gz = gzopen($destination, 'wb9');
if (!$fp || !$gz) {
return false;
}
while (!feof($fp)) {
gzwrite($gz, fread($fp, 8192));
}
fclose($fp);
gzclose($gz);
return true;
}
}实际应用场景
场景一:使用 logrotate 管理日志
bash
/etc/logrotate.d/rabbitmq-server
/var/log/rabbitmq/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 rabbitmq rabbitmq
sharedscripts
postrotate
rabbitmqctl eval 'logger:set_primary_config(overwrite, true).' > /dev/null
endscript
}场景二:Docker 环境日志轮转
yaml
version: '3.8'
services:
rabbitmq:
image: rabbitmq:3.12-management
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
volumes:
- rabbitmq_log:/var/log/rabbitmq
volumes:
rabbitmq_log:场景三:Kubernetes 日志轮转
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: rabbitmq-config
data:
rabbitmq.conf: |
log.file.rotation.date = $D0
log.file.rotation.count = 7
log.file.rotation.size = 52428800
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rabbitmq
spec:
template:
spec:
containers:
- name: rabbitmq
image: rabbitmq:3.12-management
volumeMounts:
- name: config
mountPath: /etc/rabbitmq
- name: logs
mountPath: /var/log/rabbitmq
volumes:
- name: config
configMap:
name: rabbitmq-config
- name: logs
emptyDir: {}常见问题与解决方案
问题一:日志文件未轮转
现象:日志文件持续增长,没有轮转。
解决方案:
bash
log.file.rotation.date = $D0
log.file.rotation.count = 10或使用 logrotate:
bash
logrotate -f /etc/logrotate.d/rabbitmq-server问题二:轮转后日志丢失
现象:轮转后新日志没有写入。
解决方案:
bash
postrotate
rabbitmqctl eval 'logger:set_primary_config(overwrite, true).' > /dev/null
endscript问题三:磁盘空间不足
现象:日志占用过多磁盘空间。
解决方案:
php
$manager->setRotationBySize(50, 10);
$manager->cleanupOldLogs(10);
$manager->compressOldLogs(3);最佳实践
1. 轮转策略选择
| 场景 | 推荐策略 |
|---|---|
| 小型部署 | 每日轮转,保留 7 天 |
| 中型部署 | 每日轮转,保留 30 天 |
| 大型部署 | 大小+时间混合,保留 30 天 |
2. 配置建议
bash
log.file.rotation.date = $D0
log.file.rotation.size = 104857600
log.file.rotation.count = 303. 监控告警
php
$usage = $manager->getDiskUsage();
if ($usage['total_size'] > 1073741824) {
trigger_alert('Log disk usage exceeds 1GB');
}