Логирование с помощью скрипта оболочки – перехват STDERR для логирования с меткой времени

Вопрос или проблема

РЕШЕНО:

Несколько месяцев назад я заинтересовался ведением логов в оболочке скриптов.

Первой идеей была ручная функция ведения логов, такая как эта:

add2log() {
    printf "$(date)\tINFO\t%s\t%s\n" "$1" "$2" >>"$logPATH"
}

Но я хотел автоматизировать это, чтобы STDERR автоматически записывался в журнал.

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


Для каждого моего оболочного скрипта я теперь использую “main.sh”, который содержит функции для ведения логов, а также настройки (настройка файлов логов и конфигурации).
Вот как это выглядит:

#!/bin/bash

###################################################################
# МОИ ЗАГОЛОВКИ
###################################################################


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~Глобальные переменные

mainScriptNAME=$(basename "$0")
mainScriptNAME="${mainScriptNAME%.*}"
mainScriptDIR=$(dirname "$0")
version="v0.0"
scriptsDIR="$mainScriptDIR/SCRIPTS"
addonsDIR="$mainScriptDIR/ADDONS"


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~ФУНКЦИИ ДЛЯ ВЕДЕНИЯ ЛОГОВ

manualLogger() {

    # $1=ПРИОРИТЕТ $2=ИМЯ ФУНКЦИИ $3=сообщение
    printf "$(date)\t%s\t%s()\t%s\n" "$1" "$2" "$3" >>"$logFilePATH"
}

stdoutLogger() {

    # $1=сообщение
    printf "$(date)\tSTDOUT\t%s\n" "$1" >>"$logFilePATH"
}

stderrLogger() {

    # $1=сообщение
    printf "$(date)\tSTDERR\t%s\n" "$1" >>"$logFilePATH"
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~ЛОГИ И КОНФИГ

createLog() {
    # код для настройки и создания logFilePATH & confFilePATH
    manualLogger "INFO" "${FUNCNAME[0]}" "Log файл создан."
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~ОСНОВНОЕ

#
createLog

#запуск скриптов
{

    #
    source "$scriptsDIR/file1.sh"
    doSomthing1
    doSomthing2

    #
    source "$scriptsDIR/file2.sh"
    anotherFunction1
    anotherFunction2

    ...


} 2> >(while read -r line; do stderrLogger "$line"; done) \
1> >(while read -r line; do stdoutLogger "$line"; done) 


Другими словами:

  • все скрипты/функции, которые я хочу запустить, находятся в отдельных файлах в папке ./SCRIPTS (поэтому мой main.sh почти всегда одинаковый)
  • я вызываю все эти скрипты в группе { … }, чтобы перехватить их STDOUT и STDERR
  • их STDOUT и STDERR перенаправляются в соответствующие функции ведения логов

Файл логов выглядит так (примеры одного ручного логгера, одного логгера STDOUT, одного логгера STDERR):

Пн 23 мая 2022 12:20:42 CEST   INFO    createLog() Log файл создан.
Пн 23 мая 2022 12:20:42 CEST   STDOUT  Некоторый стандартный вывод
Пн 23 мая 2022 12:20:42 CEST   STDERR  ls: sadsdasd: Нет такого файла или каталога

Группа позволяет собрать весь вывод в журнал.
Вместо этого вы, очевидно, могли бы использовать перенаправление 2> >(while read ... и 1> >(while read ... по своему усмотрению для каждой функции.
Например, doSomthing1 будет иметь свой STDOUT и STDERR в журнале, но doSomthing2 будет иметь только перенаправление своего STDERR.

Судя по всему, похоже, что вы просто добавляете отладочные операторы. Что вы имеете в виду под “тяжелым” здесь? В Bash-скрипте (или, скорее, в любом языке программирования по этому поводу), если вам нужно что-то вывести, вам нужно иметь оператор, который сообщает интерпретатору выполнить echo содержимое, которое вы передали.

Не существует замены для написания команды, если вы хотите, чтобы действие произошло в определенной точке в вашем скрипте (или любом участке кода).

Вы можете использовать различные трюки с перенаправлением ввода-вывода, но вам нужно иметь два оператора – один в начале и один в конце, чтобы достичь желаемого результата.

Если вы хотите тщательного ведения логов вашего скрипта, обратите внимание на set -x. Если вы хотите записывать только некоторые функции вашего скрипта, используйте set -x с этими функциями в подсчетах.

(set -x; func1)

Я использую файлы-флаги в специальном каталоге:

foo.bash:
  
#!/bin/bash
[[ -f $HOME/.local/debug/foo.debug ]] && \
  source $HOME/.local/debug/foo.debug
...

Я могу помещать все, что мне нравится, в файл foo.debug, включая такие вещи, как debug_foofunction=1, а затем в foo‘s foofunction я могу использовать debug_foofunction (осторожно – это может быть неопределенным), чтобы вызывать (или не вызывать) код отладки.
Если я просто rm $HOME/.local/debug/foo.debug, весь код отладки отключается. Если я изменю его на debug_foofunction=0, я могу отключить отладку foofunction, оставляя другие настройки отладки (например, debug_foootherfunction=1).

Читать man bash, о встроенной команде “trap“.

Реальное решение – не писать сложные скрипты. Используйте язык программирования, который соответствует вашей проблеме.

Всегда вставляйте свой скрипт в https://shellcheck.net, проверщик синтаксиса, или устанавливайте shellcheck локально. Сделайте использование shellcheck частью вашего процесса разработки.

Ответ или решение

Логирование в Shell-скриптах: Уловка STDERR с временной меткой

Введение

Логирование — важная часть разработки и эксплуатации скриптов, позволяющая отслеживать выполнение программ, выявлять ошибки и получать необходимую информацию для анализа. В данном руководстве мы рассмотрим, как можно значительно упростить процесс логирования в Shell-скриптах, автоматизировав захват стандартного вывода (STDOUT) и стандартного вывода ошибок (STDERR) с добавлением временной метки.

Основные идеи

  1. Структура скрипта: Начнем с создания основного скрипта, который будет содержать функции для логирования, а также переменную конфигурации для лог-Файлов.
  2. Логирующие функции: Реализуем функции для логирования различных уровней: общий лог, лог STDOUT и лог STDERR.
  3. Автоматизация захвата вывода: Используем возможности перенаправления вывода для автоматического логирования без необходимости вручную вставлять вызовы логирующих функций.

Пример реализации

#!/bin/bash

###################################################################
# Основные переменные
###################################################################

mainScriptNAME=$(basename "$0")
mainScriptDIR=$(dirname "$0")
version="v0.0"
scriptsDIR="$mainScriptDIR/SCRIPTS"
logFilePATH="$mainScriptDIR/script.log"

#~~~~~~~~~~~~~~~~~~LOGGER FUNCTIONS

manualLogger() {
    printf "$(date '+%Y-%m-%d %H:%M:%S')\t%s\t%s()\t%s\n" "$1" "$2" "$3" >> "$logFilePATH"
}

stdoutLogger() {
    printf "$(date '+%Y-%m-%d %H:%M:%S')\tSTDOUT\t%s\n" "$1" >> "$logFilePATH"
}

stderrLogger() {
    printf "$(date '+%Y-%m-%d %H:%M:%S')\tSTDERR\t%s\n" "$1" >> "$logFilePATH"
}

#~~~~~~~~~~~~~~~~~~LOG & CONFIG

createLog() {
    touch "$logFilePATH"
    manualLogger "INFO" "${FUNCNAME[0]}" "Log file created."
}

#~~~~~~~~~~~~~~~~~~MAIN

createLog

{
    source "$scriptsDIR/file1.sh"
    doSomthing1
    doSomthing2

    source "$scriptsDIR/file2.sh"
    anotherFunction1
    anotherFunction2

} 2> >(while read -r line; do stderrLogger "$line"; done) \
1> >(while read -r line; do stdoutLogger "$line"; done)

Описание кода:

  1. Переменные: В этом разделе мы определяем имя и путь основного скрипта, а также путь к файлу логов.
  2. Функции логирования:
    • manualLogger — записывает пользовательский лог.
    • stdoutLogger — обрабатывает стандартный вывод, добавляя временную метку.
    • stderrLogger — обрабатывает стандартный вывод ошибок аналогично.
  3. Создание лога: Функция createLog создает файл лога и записывает сообщение о его создании.
  4. Основная логика: Все выполняемые функции doSomthing1, doSomthing2, и т.д. заключены в фигурные скобки для переадресации вывода, что позволяет автоматически регистрировать как вывод, так и ошибки.

Заключение

Эта модель позволяет аккуратно записывать как стандартный вывод, так и ошибки в лог-файл, вводя временные метки для лучшего анализа работы скрипта. Каждая функция может быть легко настроена для добавления дополнительной информации или использования логирования по желанию. С помощью таких возможностей, как перенаправление вывода, вы можете эффективно управлять логированием в ваших Shell-скриптах без значительных усилий.

Этот подход не только упрощает процесс отладки, но также облегчает поддержку и расширение ваших скриптов, сохраняя вашу работу организованной и управляемой. Надеюсь, этот метод будет вам полезен в будущих проектах.

Оцените материал
Добавить комментарий

Капча загружается...