Как вывести ошибку в лог в bash скрипте
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Логирование ошибок в bash-скриптах: полное руководство
В bash-скриптах выводить ошибки в логи можно несколькими способами, в зависимости от требуемого уровня детализации, формата логов и целей мониторинга. Вот основные подходы, которые я использую в production-среде.
Базовые методы вывода ошибок
1. Стандартные потоки вывода и перенаправление
Каждый процесс в Linux имеет три стандартных потока:
stdout(дескриптор 1) - для обычного выводаstderr(дескриптор 2) - для сообщений об ошибкахstdin(дескриптор 0) - для ввода данных
#!/bin/bash
# Простой вывод ошибки в stderr
echo "Ошибка: файл не найден" >&2
# Перенаправление stderr в файл
command_that_might_fail 2>>/var/log/myapp/errors.log
# Перенаправление обоих потоков в один файл
some_command >>/var/log/myapp/all.log 2>&1
# Или более современная запись:
some_command &>>/var/log/myapp/all.log
2. Функция для логирования с временными метками
#!/bin/bash
LOG_FILE="/var/log/myapp/script.log"
ERROR_LOG="/var/log/myapp/errors.log"
# Функция для логирования с временной меткой
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[${timestamp}] [${level}] ${message}" | tee -a "$LOG_FILE"
}
# Функция для логирования ошибок
log_error() {
log_message "ERROR" "$1" >&2
# Дополнительно пишем в отдельный файл ошибок
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$ERROR_LOG"
}
# Использование
if [[ ! -f "/required/file.txt" ]]; then
log_error "Файл /required/file.txt не существует"
exit 1
fi
Продвинутые методы для production-среды
3. Использование trap для перехвата ошибок и завершения работы
#!/bin/bash
set -euo pipefail # Строгий режим: завершать при ошибках
# Настройка обработчиков ошибок
cleanup() {
local exit_code=$?
local error_line="$1"
if [[ $exit_code -ne 0 ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] Скрипт завершился с ошибкой $exit_code на строке $error_line" >&2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] Скрипт завершился с ошибкой $exit_code на строке $error_line" >> "/var/log/myapp/critical.log"
fi
# Дополнительная очистка ресурсов при необходимости
exit $exit_code
}
# Устанавливаем trap для обработки ошибок
trap 'cleanup ${LINENO}' ERR EXIT
# Пример операции, которая может завершиться ошибкой
important_operation() {
if ! mount /dev/sdb1 /mnt/data; then
echo "Ошибка монтирования" >&2
return 1
fi
}
4. Структурированное логирование в JSON (для интеграции с ELK/Grafana)
#!/bin/bash
json_log() {
local level=$1
local message=$2
local script_name=$(basename "$0")
local timestamp=$(date --iso-8601=seconds)
cat <<EOF
{
"timestamp": "${timestamp}",
"level": "${level}",
"script": "${script_name}",
"pid": "$$",
"message": "${message}",
"hostname": "$(hostname)"
}
EOF
}
# Использование
json_log "ERROR" "Не удалось подключиться к базе данных" >> /var/log/myapp/structured.json
# Для отправки напрямую в Logstash или syslog
json_log "ERROR" "Критическая ошибка конфигурации" | logger -t "myapp-script" -p user.err
Практические рекомендации для DevOps
-
Используйте систему управления логами:
- Настройте logrotate для ротации логов
- Интегрируйте с rsyslog или systemd-journald
- Для контейнеров используйте json-file драйвер или отправляйте напрямую в Loki/Fluentd
-
Уровни логирования:
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3 [FATAL]=4)
CURRENT_LOG_LEVEL="INFO"
log() {
local level=$1
local message=$2
if [[ ${LOG_LEVELS[$level]} -ge ${LOG_LEVELS[$CURRENT_LOG_LEVEL]} ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >&2
fi
}
- Контекст и трассировка:
# Добавление stack trace для ошибок
log_error_with_trace() {
log_error "$1"
log_error "Stack trace:"
local i=0
while caller $i; do
((i++))
done | head -10 >&2
}
- Метрики и алертинг:
- Логируйте ошибки с уникальными кодами для мониторинга
- Интегрируйте с Prometheus через текстовые файлы метрик
- Используйте exit codes для автоматического алертинга
Пример комплексного решения
#!/bin/bash
set -euo pipefail
# Конфигурация
readonly LOG_DIR="/var/log/myapp"
readonly SCRIPT_NAME=$(basename "$0")
readonly HOSTNAME=$(hostname)
# Инициализация логгера
init_logger() {
mkdir -p "$LOG_DIR"
exec 3>>"$LOG_DIR/${SCRIPT_NAME}.log"
exec 4>>"$LOG_DIR/${SCRIPT_NAME}.error.log"
}
# Функция логирования
log() {
local severity=$1
local message=$2
local timestamp=$(date --iso-8601=ns)
# В человекочитаемый лог
echo "[${timestamp:0:23}] [${severity}] [${HOSTNAME}] ${message}" >&3
# В лог ошибок (если это ошибка)
if [[ $severity == "ERROR" || $severity == "FATAL" ]]; then
echo "[${timestamp:0:23}] [${severity}] ${message}" >&4
# Отправка алерта (пример)
send_alert "$severity" "$message"
fi
# В консоль (только ошибки)
if [[ $severity == "ERROR" || $severity == "FATAL" ]]; then
echo "[${severity}] ${message}" >&2
fi
}
# Обработчик ошибок
error_handler() {
local exit_code=$?
local line_no=$1
log "ERROR" "Скрипт завершился с кодом ${exit_code} на строке ${line_no}"
exit $exit_code
}
trap 'error_handler ${LINENO}' ERR
# Основная логика
init_logger
log "INFO" "Скрипт запущен"
if ! perform_critical_operation; then
log "FATAL" "Критическая операция не удалась"
exit 1
fi
log "INFO" "Скрипт успешно завершен"
Ключевые принципы, которые я применяю:
- Всегда разделяйте stdout и stderr
- Добавляйте временные метки и контекст (PID, hostname, script name)
- Используйте структурированный формат для машинной обработки
- Настройте ротацию логов для предотвращения заполнения диска
- Интегрируйте с централизованной системой сбора логов
- Используйте exit codes для автоматического определения статуса выполнения
В production-среде рекомендую использовать специализированные системы логирования, такие как vector, fluent-bit или logstash для агрегации и обработки логов из bash-скриптов.