Отсутствует ротация журналов

На одном из моих серверов недавно закончилось место. Итак, я начал изучать это. Журналы nginx занимали половину раздела. Еще я заметил странную вещь. Для большого количества сайтов (60%), где присутствует дополнительная ротация ( example.com-access.log.53.gz , когда повернуть 52 ). И у большинства самых больших - но не у всех - было только два поворота:

example.com-access.log
example.com-access.log.53.gz

50% бревен имели только эти два поворота. Иногда были просто дыры в поворотах (30%): один напильник и более. * .log.1 часто отсутствовал (25%). Иногда были и *. Log.1 , и *. Log.1.gz (2 из 172).

Вы можете объяснить это отсутствующее / повторяющееся вращение? Случай *. 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 не существует.

0
задан 21 March 2019 в 16:14
1 ответ

tl;dr Дубликаты могут быть произведены, если сжатие (gzip) было прервано. Один такой дубликат (если sharedscripts) заставляет в конечном итоге оставить только одно вращение gzip (#поворот + 1).

Вот упрощенная версия того, что происходит под капотом (логсет - это запись /путь/к/к/фр/*.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 или 1-ое вращение лог-файла не считается ошибкой. Так же как и в случае отсутствия самого лог-файла, если missok (не имеет значения в случае шаблона). Также ошибка во время фазы prerotateSingleLog() с sharedscripts не имеет значения. Разорвите цикл.

Обратите внимание, я сделал много упрощений при компиляции кода, описанного выше. Проконсультируйтесь по адресу . оригинал, когда сомневаешься.

С этим, единственные случаи, в которых я вижу, где может не хватать или дополнительные файлы, когда прерывается logrotate . Это может объяснить поворот + 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. Но, наверное, все равно хорошо.

Так что остерегайтесь ошибок в вращении лога. Вы можете определить проблему, заметив строку "error:" в выводе 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"
0
ответ дан 5 December 2019 в 03:42

Теги

Похожие вопросы