我的一台服务器最近空间不足。所以我开始调查。日志nginx
占用了分区的一半。此外,我注意到一件奇怪的事情。对于很多网站(60%)来说,存在额外的轮换(example.com-access.log.53.gz
当rotate 52
)。大多数最大的网站(但不是全部)只有两次轮换:
example.com-access.log
example.com-access.log.53.gz
50% 的原木只有这两种旋转。有时旋转中只有空洞 (30%):一个或多个文件。*.log.1
经常丢失 (25%)。有时两者兼有*.log.1
(*.log.1.gz
172 个中的 2 个)。
您能解释一下这种缺失/重复的旋转吗?*.log
+ 的*.log.53.gz
情况让我认为在某个时候它无法旋转*.log.1
到*.log.2.gz
。 但它不会在失败后停止吗gzip
? 那么一定没有洞。 或者至少必须存在,*.log.1
如果它不存在,不是吗?
如果有的话,我正在运行 Debian 服务器。
/etc/logrotate.conf
:
# see "man logrotate" for details
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# uncomment this if you want your log files compressed
#compress
# packages drop log rotation information into this directory
include /etc/logrotate.d
# no packages own wtmp, or btmp -- we'll rotate them here
/var/log/wtmp {
missingok
monthly
create 0664 root utmp
rotate 1
}
/var/log/btmp {
missingok
monthly
create 0660 root utmp
rotate 1
}
# system-specific logs may be configured here
/etc/logrotate.d/nginx
:
/var/log/nginx/*.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 0640 www-data adm
size 50M
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
[ -s /run/nginx.pid ] && kill -USR1 `cat /run/nginx.pid`
endscript
}
/etc/logrotate.d/httpd-prerotate
不存在。
答案1
总结gzip
如果压缩 ( ) 中断,则可能会产生重复。此类重复 (如果sharedscripts
) 最终只会留下一个经过 gzip 压缩的循环 (# rotate + 1
)。
以下是简化版的流程引擎盖下/path/to/dir/*.log {...}
(logset 是配置中的一个条目):
for (let logSet of logSets) {
rotateLogSet(logSet);
}
function rotateLogSet(logSet) {
const logHasErrors = [];
let hasErrors = false;
for (let i = 0; i < logSet.files().length; i++) {
findNeedRotating(log, i);
}
const jn = logSet['sharedscripts']
? 1
: logSet.files().length;
for (let j = 0; j < jn; j++) {
const in = logSet['sharedscripts'] ? logSet.files().length : j + 1;
for (let i = j; i < in; i++) {
logHasErrors[i] ||= ! prerotateSingleLog(logSet, i);
hasErrors ||= logHasErrors[i];
}
if (logSet['prerotate']
&& ( ! (
logSet['sharedscripts'] ? hasErrors : logHasErrors[j]
))
) {
if ( ! runScriptMultiple(logSet['prerotate']))
logHasErrors[j] = hasErrors = true;
}
for (let i = j; i < in; i++) {
if ( ! (
logSet['sharedscripts'] ? hasErrors : logHasErrors[i]
))
logHasErrors[i] ||= ! rotateSingleLog(logSet, i);
hasErrors ||= logHasErrors[i];
}
if (logSet['postrotate']
&& ( ! (
logSet['sharedscripts'] ? hasErrors : logHasErrors[j]
))
) {
if ( ! runScriptMultiple(logSet['postrotate']))
logHasErrors[j] = hasErrors = true;
}
for (let i = j; i < in; i++) {
if ( ! (
logSet['sharedscripts'] ? hasErrors : logHasErrors[i]
))
logHasErrors[i] ||= ! postrotateSingleLog(logSet, i);
hasErrors ||= logHasErrors[i];
}
}
}
function findNeedRotating(logSet, i) {
const log = logSet.files()[i];
if ( ! stat(log))
return logSet['missingok'] && errno == ENOENT;
log.doRotate = ...;
return ...;
}
function prerotateSingleLog(logSet, i) {
let hasErrors = false;
const log = logSet.files()[i];
if ( ! log.doRotate)
return;
if (stat(log))
hasErrors = compressLogFile(log);
for (let i = logSet['rotate']; i >= 0 && ! hasErrors; i--) {
if ( ! rename(`${log}.${i}.gz`, `${log}.${i + 1}.gz`))
if (errno != ENOENT)
hasErrors = true;
}
return ! hasErrors;
}
function rotateSingleLog(logSet, i) {
let hasErrors = false;
const log = logSet.files()[i];
if ( ! log.doRotate)
return;
if ( ! rename(log, `${log}.1`))
hasErrors = true;
if ( ! hasErrors && logSet['create'])
if ( ! createOutputFile(log))
hasErrors = true;
return ! hasErrors;
}
function postrotateSingleLog(logSet, i) {
const log = logSet.files()[i];
if ( ! log.doRotate)
return;
rm(`${log}.${logSet['rotate'] + 1}.gz`);
}
因此,sharedscripts
处理属于日志集的日志文件时通常会发生错误,从而停止整个日志集的处理。如果没有该错误,则只会停止一个日志文件的处理。
但是不存在的 gzip 旋转或日志文件的第一次旋转不算作错误。如果日志文件本身不存在,则不算作错误missingok
(在模式的情况下无关紧要)。此外,prerotateSingleLog()
阶段中的错误sharedscripts
不会中断循环。
请注意,我在编译上述代码时做了很多简化。如有疑问,请参阅原文。
有了这个,我能看到的唯一可能丢失或多余文件的情况是logrotate
中断时。这可能解释了
rotate + 1
旋转(gzip 旋转被重命名,但最后一个旋转尚未被删除)。此外,当gzip
中断时,它会留下目标文件。这解释了同时存在
*.log.1
和*.log.1.gz
旋转的原因。仍然无法解释旋转中的漏洞。
更新型多巴胺出现重复项 ( *.log.1
+ *.log.1.gz
)生产 一个 错误:
错误:创建输出文件 /var/log/nginx/example.com-access.log.1.gz 时出错:文件存在
阶段结束后,处理停止prerotateSingleLog()
。此时,所有经过 gzip 压缩的旋转都已重命名。但重命名*.log -> *.log.1
和删除*.log.${rotate + 1}.gz
被跳过。*.log
变得越来越大,最终耗尽空间。
这解释了一切,除了缺少*.log.1
轮换。但可能已经是最好的了。
logrotate
因此,请留意日志轮换中的错误。您可以通过在(即使非详细的)输出中发现“error:”行来识别问题。
作为奖励,脚本以一种很好的方式显示日志目录的内容(自然排序,带有大小):
#!/usr/bin/env bash
set -eu
for l in /var/log/nginx/*.log; do
du -bs "$l"* \
| sed -E 's/(.*\.([0-9]+)(\.gz)?)$/\2 \1/; t a; s/^/0 /; :a' \
| sort -nk1 \
| awk '{sub(/.*\//, "", $3); printf("%12s %s\n", $2, $3)}'
done
另外,您还可以检查是否遇到了我遇到的问题:
#!/usr/bin/env bash
set -eu
dir=/var/log/nginx
i=0
n_0_53=0
n_53=0
n_holes=0
n_missing_1=0
for f in `ls "$dir"/*.log`; do
f=`basename -- "$f"`
echo -- $f
rotations=$(ls "$dir/$f"* \
| sed -E 's/(.*\.([0-9]+)(\.gz)?)$/\2 \1/; t a; s/^/0 /; :a' \
| sort -nk1 \
| awk '{print $1}')
duplicates=$(echo "$rotations" | uniq -c | awk '$1 != 1 {print $2}')
if [ "$duplicates" ]; then
echo duplicates: $duplicates
fi
if [ "$rotations" = $'0\n53' ]; then
echo 0, 53
(( n_0_53 += 1))
else
missing=
last=$(echo "$rotations" | tail -n 1)
for (( j = 0; j <= $last; j++ )); do
if ! [[ "$rotations" =~ (^|$'\n')"$j"($'\n'|$) ]]; then
missing="$missing $j"
fi
done
if [ "$missing" ]; then
echo missing: $missing
(( n_holes += 1 ))
fi
if [ "$missing" = ' 1' ]; then
(( n_missing_1 += 1 ))
fi
fi
if [[ "$rotations" =~ (^|$'\n')53($'\n'|$) ]]; then
(( n_53 += 1 ))
fi
(( i += 1 ))
done
printf 'n_0_53: %s %s\n' "$n_0_53" "$(echo "$n_0_53 * 100 / $i" | bc)"
printf 'n_53: %s %s\n' "$n_53" "$(echo "$n_53* 100 / $i" | bc)"
printf 'n_holes: %s %s\n' "$n_holes" "$(echo "$n_holes * 100 / $i" | bc)"
printf 'n_missing_1: %s %s\n' "$n_missing_1" "$(echo "$n_missing_1 * 100 / $i" | bc)"
printf 'total: %s\n' "$i"