缺少日誌輪換
我的一台伺服器最近空間不足。所以我開始研究它。日誌佔用了分區的
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
case 讓我覺得在某些時候它無法旋轉*.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
不存在。
tl;dr
gzip
如果壓縮 ( ) 被中斷,可能會產生重複。一個這樣的副本(如果sharedscripts
)使它最終只留下一個壓縮旋轉(#rotate + 1
)。這是幕後發生的事情的簡化版本(日誌集是配置中的一個條目
/path/to/dir/*.log {...}
):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
通常在處理屬於日誌集的日誌文件時發生錯誤,會停止對整個日誌集的處理。沒有它,只會停止處理一個日誌文件。但是不存在的 gzipped 旋轉或日誌文件的第一次旋轉不算作錯誤。當日誌文件本身不存在時
missingok
,情況並非如此(在模式的情況下無關緊要)。prerotateSingleLog()
階段中的錯誤sharedscripts
也不會破壞循環。請注意,我在編譯上面的程式碼時做了很多簡化。如有疑問,請查閱原件。
有了這個,我可以看到可能失去或額外文件的唯一情況是何時
logrotate
被中斷。這可能解釋rotate + 1
了旋轉(gzipped 旋轉已重命名,但最後一個尚未刪除)。此外,當gzip
被中斷時,它會將目標文件留在後面。這解釋了同時具有*.log.1
和*.log.1.gz
旋轉。仍然沒有解釋旋轉中的孔。UPD出現重複(
*.log.1
+*.log.1.gz
)會產生 錯誤 :錯誤:創建輸出文件/var/log/nginx/example.com-access.log.1.gz時出錯:文件存在
階段後停止處理
prerotateSingleLog()
。那時,所有 gzipped 旋轉都被重命名。但是會跳過重命名*.log -> *.log.1
和刪除。變得越來越大,最終你會耗盡空間。*.log.${rotate + 1}.gz``*.log
這解釋了除了缺少
*.log.1
旋轉之外的一切。但可能和它一樣好。因此,請注意日誌輪換中的錯誤。
logrotate
您可以通過在(甚至是非詳細)輸出中發現“錯誤:”行來辨識問題。作為獎勵,一個以很好的方式顯示日誌目錄內容的腳本(自然排序,大小):
#!/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"