Вопрос или проблема
РЕШЕНО:
Несколько месяцев назад я заинтересовался ведением логов в оболочке скриптов.
Первой идеей была ручная функция ведения логов, такая как эта:
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) с добавлением временной метки.
Основные идеи
- Структура скрипта: Начнем с создания основного скрипта, который будет содержать функции для логирования, а также переменную конфигурации для лог-Файлов.
- Логирующие функции: Реализуем функции для логирования различных уровней: общий лог, лог STDOUT и лог STDERR.
- Автоматизация захвата вывода: Используем возможности перенаправления вывода для автоматического логирования без необходимости вручную вставлять вызовы логирующих функций.
Пример реализации
#!/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)
Описание кода:
- Переменные: В этом разделе мы определяем имя и путь основного скрипта, а также путь к файлу логов.
- Функции логирования:
manualLogger
— записывает пользовательский лог.stdoutLogger
— обрабатывает стандартный вывод, добавляя временную метку.stderrLogger
— обрабатывает стандартный вывод ошибок аналогично.
- Создание лога: Функция
createLog
создает файл лога и записывает сообщение о его создании. - Основная логика: Все выполняемые функции
doSomthing1
,doSomthing2
, и т.д. заключены в фигурные скобки для переадресации вывода, что позволяет автоматически регистрировать как вывод, так и ошибки.
Заключение
Эта модель позволяет аккуратно записывать как стандартный вывод, так и ошибки в лог-файл, вводя временные метки для лучшего анализа работы скрипта. Каждая функция может быть легко настроена для добавления дополнительной информации или использования логирования по желанию. С помощью таких возможностей, как перенаправление вывода, вы можете эффективно управлять логированием в ваших Shell-скриптах без значительных усилий.
Этот подход не только упрощает процесс отладки, но также облегчает поддержку и расширение ваших скриптов, сохраняя вашу работу организованной и управляемой. Надеюсь, этот метод будет вам полезен в будущих проектах.