日志系统对于Shell脚本的重要性,就像黑匣子对于飞机一样关键。我在15年的运维生涯中处理过无数次脚本故障,90%的疑难问题都是依靠完善的日志记录才得以快速定位。一个设计良好的日志系统不仅能帮助排查问题,还能为性能优化、安全审计提供可靠依据。
我强烈建议采用JSON格式记录日志,这是经过多年实践验证的最佳方案。相比传统文本日志,JSON具有明确的字段结构,便于后续的自动化处理和分析。以下是一个推荐的基础字段结构:
bash复制{
"timestamp": "2023-08-20T14:23:45+08:00",
"level": "INFO",
"pid": 12345,
"host": "web-server-01",
"script": "backup.sh",
"message": "Starting database backup",
"details": {
"db_name": "production",
"backup_type": "full"
}
}
关键字段说明:
日志分级不是简单的形式主义,而是资源分配的艺术。我通常采用五级分类法:
| 级别 | 使用场景 | 存储策略 | 示例 |
|---|---|---|---|
| DEBUG | 开发调试详细信息 | 临时存储,定期清理 | 变量值、循环次数 |
| INFO | 正常运行关键节点 | 保留30天 | 脚本启动、任务完成 |
| WARNING | 可自动恢复的异常 | 保留90天 | 磁盘空间不足警告 |
| ERROR | 需要人工干预的故障 | 保留180天 | 数据库连接失败 |
| CRITICAL | 系统级严重错误 | 永久保存 | 硬件故障、数据损坏 |
实际项目中,我建议通过环境变量动态控制日志级别。例如设置
LOG_LEVEL=INFO时,DEBUG日志将被过滤,既保证生产环境整洁,又能在需要时开启详细调试。
日志记录最常见的性能陷阱是I/O阻塞。我曾遇到一个备份脚本因为同步写日志导致性能下降60%的情况。以下是几个关键优化技巧:
logger命令或自定义缓冲机制,避免每条日志都触发磁盘I/O一个实用的异步日志函数实现:
bash复制log_async() {
local level=$1
local message=$2
local details=$3
# 构造JSON日志
local log_entry=$(jq -n \
--arg timestamp "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--arg level "$level" \
--arg pid "$$" \
--arg host "$(hostname)" \
--arg script "$(basename "$0")" \
--arg message "$message" \
--argjson details "$details" \
'{timestamp: $timestamp, level: $level, pid: $pid, host: $host, script: $script, message: $message, details: $details}')
# 异步发送到syslog
echo "$log_entry" | logger -t "$(basename "$0")" &
}
经过多个项目的迭代,我提炼出一套高可靠的日志函数库。将其保存为lib/logging.sh,其他脚本通过source引入即可使用。
bash复制#!/bin/bash
# 日志级别常量
readonly LOG_DEBUG=0
readonly LOG_INFO=1
readonly LOG_WARNING=2
readonly LOG_ERROR=3
readonly LOG_CRITICAL=4
# 默认日志级别(可通过环境变量覆盖)
LOG_LEVEL=${LOG_LEVEL:-$LOG_INFO}
# 彩色输出定义
if [ -t 1 ]; then
readonly COLOR_DEBUG="\033[0;36m"
readonly COLOR_INFO="\033[0;32m"
readonly COLOR_WARNING="\033[0;33m"
readonly COLOR_ERROR="\033[0;31m"
readonly COLOR_CRITICAL="\033[1;31m"
readonly COLOR_RESET="\033[0m"
else
readonly COLOR_DEBUG=""
readonly COLOR_INFO=""
readonly COLOR_WARNING=""
readonly COLOR_ERROR=""
readonly COLOR_CRITICAL=""
readonly COLOR_RESET=""
fi
log() {
local level=$1
local message=$2
local details=${3:-"{}"}
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# 级别过滤
[ $level -lt $LOG_LEVEL ] && return
# 构造结构化日志
local log_json=$(jq -n \
--arg timestamp "$timestamp" \
--arg level "$level" \
--arg pid "$$" \
--arg host "$(hostname)" \
--arg script "$(basename "$0")" \
--arg message "$message" \
--argjson details "$details" \
'{timestamp: $timestamp, level: $level, pid: $pid, host: $host, script: $script, message: $message, details: $details}')
# 控制台输出(带颜色)
local human_level=""
local human_color=""
case $level in
$LOG_DEBUG) human_level="DEBUG"; human_color=$COLOR_DEBUG ;;
$LOG_INFO) human_level="INFO"; human_color=$COLOR_INFO ;;
$LOG_WARNING) human_level="WARNING"; human_color=$COLOR_WARNING ;;
$LOG_ERROR) human_level="ERROR"; human_color=$COLOR_ERROR ;;
$LOG_CRITICAL) human_level="CRITICAL"; human_color=$COLOR_CRITICAL ;;
esac
echo -e "${human_color}[${human_level}] ${message}${COLOR_RESET}"
# 文件记录(JSON格式)
echo "$log_json" >> "/var/log/$(basename "$0").log"
# 严重错误额外处理
[ $level -ge $LOG_ERROR ] && send_alert "$message" "$details"
}
# 快捷函数
debug() { log $LOG_DEBUG "$1" "$2"; }
info() { log $LOG_INFO "$1" "$2"; }
warning() { log $LOG_WARNING "$1" "$2"; }
error() { log $LOG_ERROR "$1" "$2"; }
critical() { log $LOG_CRITICAL "$1" "$2"; }
不合理的日志轮转配置是导致磁盘爆满的常见原因。以下是经过生产验证的logrotate配置示例,保存为/etc/logrotate.d/script_logs:
code复制/var/log/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 root adm
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
关键参数解析:
daily:按天轮转(高频脚本可用hourly)rotate 30:保留30个归档版本compress:使用gzip压缩旧日志delaycompress:延迟一天压缩,方便问题排查create 0640 root adm:新日志文件权限设置我曾遇到一个案例:某金融系统因为未配置日志轮转,导致200GB的日志文件拖垮整个系统。建议对重要脚本单独配置更精细的轮转策略,比如交易类脚本保留180天,调试类脚本保留7天。
对于关键业务脚本,仅记录日志是不够的,还需要实时监控。以下是基于journalctl的系统化方案:
bash复制# 创建专属日志规则
sudo tee /etc/rsyslog.d/10-script-monitor.conf > /dev/null <<'EOF'
if $programname == 'critical_script.sh' then {
action(type="omfile" file="/var/log/critical_script.log")
stop
}
EOF
# 创建systemd监控单元
sudo tee /etc/systemd/system/script-monitor.service > /dev/null <<'EOF'
[Unit]
Description=Critical Script Log Monitor
After=syslog.target
[Service]
ExecStart=/bin/bash -c '/usr/bin/journalctl -f -u critical_script.service | grep --line-buffered "ERROR\|CRITICAL" | while read line; do /usr/local/bin/send_alert.sh "$line"; done'
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable --now script-monitor.service
结构化日志的真正价值在于后续分析。以下是用jq分析错误日志的典型场景:
bash复制# 统计各脚本错误数量
cat /var/log/*.log | jq -r 'select(.level >= 3) | .script' | sort | uniq -c | sort -nr
# 提取最近10条CRITICAL日志的详细信息
cat /var/log/*.log | jq -r 'select(.level == 4) | "\(.timestamp) \(.host)/\(.script): \(.message)\nDetails: \(.details)"' | tail -n 10
# 生成错误时间分布图
cat /var/log/*.log | jq -r 'select(.level >= 3) | .timestamp[11:13]' | sort | uniq -c | gnuplot -p -e 'plot "/dev/stdin" using 2:1 with lines title "Error Frequency"'
在多用户环境下,日志权限设置不当会导致信息泄露或写入失败。推荐的安全实践:
bash复制# 创建日志专用组
sudo groupadd scriptlog
# 设置目录权限
sudo mkdir -p /var/log/scripts
sudo chown root:scriptlog /var/log/scripts
sudo chmod 2770 /var/log/scripts # SGID保持组继承
# 在日志函数中添加umask设置
umask 0027 # 新文件默认权限640
通过系统工具实测日志性能影响:
bash复制# 测试原始性能
time ./original_script.sh
# 测试带日志的性能
time ./logged_script.sh
# 使用strace跟踪系统调用
strace -c -e trace=write ./logged_script.sh
典型优化效果对比:
在Docker环境中,需要特殊处理日志输出:
dockerfile复制# Dockerfile配置
RUN ln -sf /dev/stdout /var/log/script.log
# 启动命令
CMD ["sh", "-c", "exec ./your_script.sh > /proc/1/fd/1 2>/proc/1/fd/2"]
Kubernetes环境下的最佳实践:
yaml复制apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
对于金融、医疗等受监管行业,日志系统需要满足特定合规要求。以下是一个符合PCI DSS标准的实施方案:
bash复制# 安装auditd并配置日志保护
sudo apt install auditd
sudo tee /etc/audit/rules.d/logging.rules > /dev/null <<'EOF'
-w /var/log/scripts/ -p wa -k script_logs
-w /etc/logrotate.d/ -p wa -k logrotate_conf
EOF
sudo service auditd restart
bash复制# 配置rsyslog远程传输
sudo tee /etc/rsyslog.d/99-remote.conf > /dev/null <<'EOF'
*.* @logserver.example.com:514
EOF
sudo systemctl restart rsyslog
bash复制# 为日志文件添加扩展属性
sudo apt install attr
sudo touch /var/log/secure_script.log
sudo chattr +a /var/log/secure_script.log # 只允许追加
bash复制# 配置详细的ACL规则
sudo setfacl -R -m g:auditors:r-x /var/log/scripts
sudo setfacl -R -m d:g:auditors:r-x /var/log/scripts
经过这些年的实践,我发现最容易被忽视的是日志的测试环节。建议将日志验证纳入CI/CD流程,确保:
一个简单的测试用例示例:
bash复制#!/bin/bash
# 加载被测脚本
source ./your_script.sh
# 测试日志函数
test_logging() {
# 重定向日志输出
local log_file=$(mktemp)
exec 3>&1 # 保存原始stdout
exec 1>"$log_file"
# 触发不同级别日志
info "Test info message"
error "Test error message"
# 恢复输出
exec 1>&3
# 验证日志内容
if ! grep -q "Test info message" "$log_file"; then
echo "FAIL: Info log not recorded"
return 1
fi
if ! grep -q "Test error message" "$log_file"; then
echo "FAIL: Error log not recorded"
return 1
fi
echo "PASS: Logging test"
rm "$log_file"
return 0
}