Linux

缺少日誌輪換

  • March 22, 2019

我的一台伺服器最近空間不足。所以我開始研究它。日誌佔用了分區的nginx一半。另外,我注意到一件奇怪的事情。對於很多網站(60%)存在額外的旋轉(example.com-access.log.53.gzrotate 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.gzcase 讓我覺得在某些時候它無法旋轉*.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;drgzip如果壓縮 ( ) 被中斷,可能會產生重複。一個這樣的副本(如果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"

引用自:https://serverfault.com/questions/959320