当前位置:首页>Linux>Linux shell脚本log日志模板

Linux shell脚本log日志模板

  • 2026-09-09 07:10:00
Linux shell脚本log日志模板

十年山茶花

一,为什么设计一个模板

最近有朋友留言说想在shell脚本中,有一个类似pyhton--logging模块类似的功能,所以设计如下一个log模板

大概步骤

  1. 初始化全局配置
    • 定义脚本名称 SCRIPT_NAME
    • 设置默认日志目录 /var/log/<script_name>
    • 配置日志轮转参数(最大文件大小 1024KB,保留 5 个归档)
    • 定义 ANSI 颜色码和日志级别常量(DEBUG 到 EMERG)
  2. 主函数 main() 启动
    • 调用 init_logger() 初始化日志环境
  3. 日志系统初始化 init_logger()
    • 创建日志目录(mkdir -p)
    • 检查目录是否可写,不可写则报错退出
    • 立即执行一次日志轮转检查(防止启动时已有大日志)
  4. 用户自定义配置(可选)
    • 通过 set_log_level() 设置最低记录级别(如 "DEBUG")
    • 通过 set_log_context() 添加上下文前缀(如 [user:123])
    • 通过 set_log_filter() 设置正则过滤条件
    • 通过 enable_performance_logging() 启用性能计时
  5. 记录日志(通过快捷函数)
    • 检查日志级别是否 ≥ 当前阈值,否则丢弃
    • 构造带时间戳、脚本名、进程 ID、上下文的日志行
    • 若启用性能监控,附加执行耗时(需配合 start_timer)
    • 若设置过滤器,仅匹配消息才输出
    • 彩色输出到标准错误(终端)
    • 原样追加到日志文件(纯文本)
    • 调用 log_info(), log_debug(), log_error() 等
    • 每次调用触发 log() 核心函数:
  6. 日志轮转机制
    • 删除最旧归档(.5.gz)
    • 将 .4.gz → .5.gz … .1.gz → .2.gz
    • 当前日志重命名为 .1 并 gzip 压缩
    • 创建新的空日志文件
    • 每次写入日志后自动调用 rotate_logs()
    • 检查当前日志文件大小是否超过 MAX_LOG_SIZE_KB
    • 若超限:
  7. 性能监控(可选)
    • 使用 log_perf "command" 包装外部命令
    • 自动计时、捕获输出与退出状态
    • 成功记录为 INFO,失败记录为 ERROR,均含耗时(毫秒)
  8. 脚本正常结束
    • 所有日志已写入文件
    • 无异常则退出码为 0
    • 若调用 log_emerg(),会立即 exit 1

二,脚本代码(精简注释版)

#!/bin/bash# ==============================================# 日志记录系统 for Shell 脚本# 功能:# - 多级别彩色日志输出# - 日志文件记录与轮转# - 日志过滤功能# - 性能监控# - 上下文信息记录# ==============================================# 基础配置readonly SCRIPT_NAME=$(basename "$0")readonly LOG_DIR="/var/log/${SCRIPT_NAME%.sh}"  # 默认日志目录readonly MAX_LOG_SIZE_KB=20  # 单个日志文件最大大小(KB)readonly MAX_LOG_FILES=5       # 保留的旧日志文件数量# 颜色定义readonly RESET='\033[0m'readonly BOLD='\033[1m'readonly DIM='\033[2m'readonly UNDERLINE='\033[4m'readonly BLINK='\033[5m'readonly INVERT='\033[7m'readonly HIDDEN='\033[8m'readonly BLACK='\033[0;30m'readonly RED='\033[0;31m'readonly GREEN='\033[0;32m'readonly YELLOW='\033[0;33m'readonly BLUE='\033[0;34m'readonly PURPLE='\033[0;35m'readonly CYAN='\033[0;36m'readonly WHITE='\033[0;37m'# 日志级别配置readonly LOG_LEVELS=("DEBUG" "INFO" "NOTICE" "WARN" "ERROR" "CRIT" "ALERT" "EMERG")readonly LOG_LEVEL_DEBUG=0readonly LOG_LEVEL_INFO=1readonly LOG_LEVEL_NOTICE=2readonly LOG_LEVEL_WARN=3readonly LOG_LEVEL_ERROR=4readonly LOG_LEVEL_CRIT=5readonly LOG_LEVEL_ALERT=6readonly LOG_LEVEL_EMERG=7# 默认日志级别 (INFO)LOG_LEVEL=${LOG_LEVEL_INFO}# 日志文件配置LOG_FILE="${LOG_DIR}/${SCRIPT_NAME%.sh}.log"LOG_FILTER=""  # 日志过滤正则表达式LOG_CONTEXT="" # 日志上下文信息LOG_PERFORMANCE=false # 是否记录性能数据# 初始化日志系统init_logger() {    # 创建日志目录    mkdir -p "${LOG_DIR}" 2>/dev/null    chmod 755 "${LOG_DIR}" 2>/dev/null    # 检查日志目录是否可写    if [[ ! -w "${LOG_DIR}" ]]; then        echo -e "${RED}ERROR: Log directory ${LOG_DIR} is not writable${RESET}" >&2        exit 1    fi    # 执行日志轮转    rotate_logs}# 设置日志级别set_log_level() {    local level=$1    case ${level^^} in        DEBUG) LOG_LEVEL=$LOG_LEVEL_DEBUG ;;        INFO) LOG_LEVEL=$LOG_LEVEL_INFO ;;        NOTICE) LOG_LEVEL=$LOG_LEVEL_NOTICE ;;        WARN) LOG_LEVEL=$LOG_LEVEL_WARN ;;        ERROR) LOG_LEVEL=$LOG_LEVEL_ERROR ;;        CRIT) LOG_LEVEL=$LOG_LEVEL_CRIT ;;        ALERT) LOG_LEVEL=$LOG_LEVEL_ALERT ;;        EMERG) LOG_LEVEL=$LOG_LEVEL_EMERG ;;        *) echo -e "${YELLOW}WARN: Invalid log level '$level', using INFO${RESET}" >&2           LOG_LEVEL=$LOG_LEVEL_INFO ;;    esac}# 设置日志过滤set_log_filter() {    LOG_FILTER=$1}# 设置日志上下文set_log_context() {    LOG_CONTEXT=$1}# 启用性能监控enable_performance_logging() {    LOG_PERFORMANCE=true}#mkdir -p "$(dirname "$LOG_FILE")"#touch "$LOG_FILE"# 日志轮转# 日志轮转(修复版)rotate_logs() {    # 确保日志目录存在且可写    if [[ ! -d "$LOG_DIR" || ! -w "$LOG_DIR" ]]; then        echo -e "${RED}ERROR: Log directory $LOG_DIR is not accessible${RESET}" >&2        return 1    fi    # 确保日志文件存在    if [[ ! -f "$LOG_FILE" ]]; then        touch "$LOG_FILE" || {            echo -e "${RED}ERROR: Cannot create log file $LOG_FILE${RESET}" >&2            return 1        }        return 0    fi    # 更可靠的文件大小检查方法    local file_size_kb=$(wc -c < "$LOG_FILE" 2>/dev/null)    file_size_kb=$((file_size_kb / 1024))    # 如果文件小于最大大小,直接返回    if (( file_size_kb <= MAX_LOG_SIZE_KB )); then        return 0    fi    # 记录轮转信息(直接写入文件,避免循环)    local rotate_msg="[$(date "+%Y-%m-%d %H:%M:%S")] [NOTICE] [${SCRIPT_NAME}] Rotating logs (size ${file_size_kb}KB > ${MAX_LOG_SIZE_KB}KB)"    echo "$rotate_msg" >> "$LOG_FILE"    # 清理最旧日志(先检查是否存在)    local oldest_log="${LOG_FILE}.${MAX_LOG_FILES}.gz"    if [[ -f "$oldest_log" ]]; then        if ! rm -f "$oldest_log"; then            echo -e "${RED}ERROR: Cannot remove oldest log file $oldest_log${RESET}" >&2            return 1        fi    fi    # 轮转现有日志(从旧到新)    for ((i=MAX_LOG_FILES-1; i>=1; i--)); do        local src_log="${LOG_FILE}.${i}.gz"        local dst_log="${LOG_FILE}.$((i+1)).gz"        if [[ -f "$src_log" ]]; then            if ! mv -f "$src_log" "$dst_log"; then                echo -e "${RED}ERROR: Cannot rotate log $src_log to $dst_log${RESET}" >&2                return 1            fi        fi    done    # 移动当前日志并压缩    local log_archive="${LOG_FILE}.1"    if ! mv -f "$LOG_FILE" "$log_archive"; then        echo -e "${RED}ERROR: Cannot move current log to $log_archive${RESET}" >&2        return 1    fi    if ! gzip "$log_archive"; then        echo -e "${RED}ERROR: Cannot compress log archive $log_archive${RESET}" >&2        # 尝试恢复原始日志文件        mv -f "$log_archive" "$LOG_FILE"        return 1    fi    # 创建新的空日志文件    touch "$LOG_FILE" || {        echo -e "${RED}ERROR: Cannot create new log file $LOG_FILE${RESET}" >&2        return 1    }    # 确保新日志文件有正确的权限    chmod 644 "$LOG_FILE" 2>/dev/null    return 0}# 核心日志函数log() {    local level=$1    local message=$2    local timestamp=$(date "+%Y-%m-%d %H:%M:%S.%3N")  # 毫秒级时间戳    local level_index=-1    local color=$RESET    local pid=$$    local thread=""  # Shell 通常没有线程ID,但可以记录子shell    # 检查是否在子shell中    if [[ "$BASHPID" != "$pid" ]]; then        thread="[$BASHPID]"    fi    # 获取日志级别索引    for i in "${!LOG_LEVELS[@]}"; do        if [[ "${LOG_LEVELS[$i]}" == "${level^^}" ]]; then            level_index=$i            break        fi    done    # 如果请求的日志级别低于设置的级别,则不记录    if (( level_index < LOG_LEVEL )); then        return    fi    # 设置颜色    case ${level^^} in        DEBUG) color=$CYAN ;;        INFO) color=$GREEN ;;        NOTICE) color=$BLUE ;;        WARN) color=$YELLOW ;;        ERROR) color=$RED ;;        CRIT) color=$PURPLE ;;        ALERT) color=$PURPLE; message="${BLINK}${message}${RESET}" ;;        EMERG) color=$PURPLE; message="${BOLD}${INVERT}${message}${RESET}" ;;        *) color=$RESET ;;    esac    # 性能监控数据    local perf_data=""    if [[ "$LOG_PERFORMANCE" == true ]]; then        local end_time=$(date +%s%3N)  # 毫秒级时间戳        # 假设我们记录了开始时间在 _LOG_START_TIME 变量中        # 实际应用中,可以在函数开始时调用 start_timer        if [[ -n "${_LOG_START_TIME}" ]]; then            local duration=$((end_time - _LOG_START_TIME))            perf_data=" [${duration}ms]"        fi    fi    # 格式化日志消息    local log_line="[$timestamp] [${level^^}] [${SCRIPT_NAME}${thread}]${LOG_CONTEXT}${perf_data}$message"    # 应用日志过滤    if [[ -n "$LOG_FILTER" ]]; then        if [[ ! "$message" =~ $LOG_FILTER ]]; then            return        fi    fi    # 输出到控制台 (带颜色)    echo -e "${color}${log_line}${RESET}" >&2    # 输出到日志文件 (如果设置了)    if [[ -n "$LOG_FILE" ]]; then        echo "$log_line" >> "$LOG_FILE"        # 触发日志轮转检查        rotate_logs    fi}# 计时器开始start_timer() {    _LOG_START_TIME=$(date +%s%3N)}# 各种级别的日志快捷方式log_debug() { log "DEBUG" "$1"; }log_info() { log "INFO" "$1"; }log_notice() { log "NOTICE" "$1"; }log_warn() { log "WARN" "$1"; }log_error() { log "ERROR" "$1"; }log_crit() { log "CRIT" "$1"; }log_alert() { log "ALERT" "$1"; }log_emerg() { log "EMERG" "$1"; exit 1; }# 带性能监控的日志函数log_perf() {    start_timer    local result=$($1 2>&1)    local status=$?    local duration=$(( $(date +%s%3N) - _LOG_START_TIME ))    if (( status == 0 )); then        log_info "PERF: $1 completed in ${duration}ms"    else        log_error "PERF: $1 failed in ${duration}ms: $result"    fi}# 示例用法main() {    # 初始化日志系统    init_logger    # 设置日志级别 (可选,默认为INFO)    set_log_level "DEBUG"    # 设置日志过滤 (只记录包含"important"的日志)    # set_log_filter "important"    # 设置日志上下文 (如当前处理的用户/ID)    set_log_context "[user:1234]"    # 启用性能监控    enable_performance_logging    log_info "脚本开始执行"    log_debug "这是一条调试信息"    log_info "处理中..."    if [[ ! -f "/etc/passwd" ]]; then        log_warn "/etc/passwd 文件不存在,但这可能没问题"    else        log_debug "/etc/passwd 文件存在"    fi    # 示例性能监控    log_perf "sleep 0.5"    # 模拟错误    if false; then        log_error "某个操作失败了"    fi    # 模拟致命错误    # log_emerg "发生致命错误,脚本将退出"    # 带上下文的日志    set_log_context "[user:5678]"    log_notice "切换到用户5678的处理"    log_info "脚本执行完成"}# 执行主函数main "$@"

三,如何在其它脚本中引用

1、把loglog.sh 放到脚本同一层目录,或者固定目录,每次使用source一下就好 

# 引入日志系统(假设 loglog.sh 和当前脚本在同一目录)source "$(dirname "$0")/loglog.sh" || {    echo "ERROR: 无法加载日志系统 loglog.sh" >&2    exit 1

2、完整测试脚本内容,测试主体是ls 遍历当前目录,

测试脚本说明:

日志函数覆盖:

  • init_logger:初始化日志系统

  • set_log_level:设置日志级别为 DEBUG

  • set_log_context:添加上下文信息

  • enable_performance_logging:启用性能监控

  • log_perf:监控 ls -l 命令的执行时间

  • log_debug/info/warn/error:记录不同级别的日志

  • log_notice:记录文件总数

特色功能演示:

  • 性能监控(log_perf)

  • 条件错误处理(log_error + 条件判断)

  • 目录检查(log_warn 跳过目录)

  • 上下文信息([操作:文件列表])

预期输出:

  • 控制台会显示彩色日志(DEBUG=青色,INFO=绿色等)

  • 日志文件会记录到 /var/log/test_loglog/test_loglog.log

  • 当日志超过 1MB 时会自动轮转(rotate_logs 功能)

#!/bin/bash# 引入日志系统(假设 loglog.sh 和当前脚本在同一目录)source "$(dirname "$0")/loglog.sh" || {    echo "ERROR: 无法加载日志系统 loglog.sh" >&2    exit 1}# 主测试函数test_ls_with_logging() {    # 初始化日志系统(init_logger)    init_logger    # 设置日志级别为 DEBUG 以显示所有日志(set_log_level)    set_log_level "DEBUG"    # 设置日志上下文(set_log_context)    set_log_context "[操作:文件列表]"    # 启用性能监控(enable_performance_logging)    enable_performance_logging    # 记录脚本开始(log_info)    log_info "===== 开始测试: 列出当前目录文件 ====="    # 模拟一个性能监控操作(log_perf)    log_perf "ls -l"  # 实际会执行并记录耗时    # 记录调试信息:准备列出文件(log_debug)    log_debug "准备扫描目录: $(pwd)"    # 检查目录是否可读(log_warn + 条件判断)    if [[ ! -r "." ]]; then        log_error "当前目录不可读!"        return 1    fi    # 记录普通信息:开始扫描(log_info)    log_info "扫描目录内容..."    # 使用数组存储文件列表(演示循环中的日志)    local files=(*)    local file_count=${#files[@]}    # 记录通知级日志:文件数量(log_notice)    log_notice "找到 ${file_count} 个文件/目录"    # 遍历文件并记录日志    for file in "${files[@]}"; do        # 记录调试信息:处理每个文件(log_debug)        log_debug "处理文件: $file"        # 如果是目录则记录警告(log_warn)        if [[ -d "$file" ]]; then            log_warn "跳过目录: $file"            continue        fi        # 记录文件大小(演示带性能数据的日志)        local size=$(du -h "$file" | cut -f1)        log_info "文件: $file (大小: $size)"    done    # 模拟一个错误场景(log_error)    if [[ ! -f "nonexistent_file" ]]; then        log_error "预期外的文件缺失: nonexistent_file"    fi    # 记录脚本结束(log_info)    log_info "===== 测试完成 ====="}# 执行测试test_ls_with_logging "$@"

四,验证

[root@master1 test2]# ./testloglog.sh [2026-01-24 18:51:08.683] [INFO] [testloglog.sh][user:1234] 脚本开始执行[2026-01-24 18:51:08.691] [DEBUG] [testloglog.sh][user:1234] 这是一条调试信息[2026-01-24 18:51:08.700] [INFO] [testloglog.sh][user:1234] 处理中...[2026-01-24 18:51:08.708] [DEBUG] [testloglog.sh][user:1234] /etc/passwd 文件存在[2026-01-24 18:51:09.222] [INFO] [testloglog.sh][user:1234] [509ms] PERF: sleep 0.5 completed in 504ms[2026-01-24 18:51:09.231] [NOTICE] [testloglog.sh][user:5678] [519ms] 切换到用户5678的处理[2026-01-24 18:51:09.240] [INFO] [testloglog.sh][user:5678] [527ms] 脚本执行完成[2026-01-24 18:51:09.254] [INFO] [testloglog.sh][操作:文件列表] [541ms] ===== 开始测试: 列出当前目录文件 =====[2026-01-24 18:51:09.268] [INFO] [testloglog.sh][操作:文件列表] [9ms] PERF: ls -l completed in 6ms[2026-01-24 18:51:09.275] [DEBUG] [testloglog.sh][操作:文件列表] [16ms] 准备扫描目录: /root/test2[2026-01-24 18:51:09.282] [INFO] [testloglog.sh][操作:文件列表] [22ms] 扫描目录内容...[2026-01-24 18:51:09.288] [NOTICE] [testloglog.sh][操作:文件列表] [29ms] 找到 9 个文件/目录[2026-01-24 18:51:09.294] [DEBUG] [testloglog.sh][操作:文件列表] [35ms] 处理文件: 2025-07-28[2026-01-24 18:51:09.300] [WARN] [testloglog.sh][操作:文件列表] [41ms] 跳过目录: 2025-07-28[2026-01-24 18:51:09.306] [DEBUG] [testloglog.sh][操作:文件列表] [46ms] 处理文件: BBBBBB.txt[2026-01-24 18:51:09.314] [INFO] [testloglog.sh][操作:文件列表] [55ms] 文件: BBBBBB.txt (大小: 0)[2026-01-24 18:51:09.320] [DEBUG] [testloglog.sh][操作:文件列表] [60ms] 处理文件: clean_log.log[2026-01-24 18:51:09.328] [INFO] [testloglog.sh][操作:文件列表] [68ms] 文件: clean_log.log (大小: 4.0K)[2026-01-24 18:51:09.333] [DEBUG] [testloglog.sh][操作:文件列表] [73ms] 处理文件: clwan_dir.sh[2026-01-24 18:51:09.340] [INFO] [testloglog.sh][操作:文件列表] [81ms] 文件: clwan_dir.sh (大小: 4.0K)[2026-01-24 18:51:09.346] [DEBUG] [testloglog.sh][操作:文件列表] [86ms] 处理文件: file.txt.newlink[2026-01-24 18:51:09.353] [INFO] [testloglog.sh][操作:文件列表] [93ms] 文件: file.txt.newlink (大小: 0)[2026-01-24 18:51:09.358] [DEBUG] [testloglog.sh][操作:文件列表] [99ms] 处理文件: loglog.sh[2026-01-24 18:51:09.366] [INFO] [testloglog.sh][操作:文件列表] [106ms] 文件: loglog.sh (大小: 8.0K)[2026-01-24 18:51:09.371] [DEBUG] [testloglog.sh][操作:文件列表] [111ms] 处理文件: tesh.sh[2026-01-24 18:51:09.378] [INFO] [testloglog.sh][操作:文件列表] [118ms] 文件: tesh.sh (大小: 4.0K)[2026-01-24 18:51:09.383] [DEBUG] [testloglog.sh][操作:文件列表] [124ms] 处理文件: testloglog.sh[2026-01-24 18:51:09.391] [INFO] [testloglog.sh][操作:文件列表] [131ms] 文件: testloglog.sh (大小: 4.0K)[2026-01-24 18:51:09.396] [DEBUG] [testloglog.sh][操作:文件列表] [136ms] 处理文件: test_logs[2026-01-24 18:51:09.401] [WARN] [testloglog.sh][操作:文件列表] [142ms] 跳过目录: test_logs[2026-01-24 18:51:09.406] [ERROR] [testloglog.sh][操作:文件列表] [146ms] 预期外的文件缺失: nonexistent_file[2026-01-24 18:51:09.411] [INFO] [testloglog.sh][操作:文件列表] [151ms] ===== 测试完成 =====
  1. 检查 /var/log/test_loglog/ 目录是否自动创建
  2. 查看日志文件内容:
[root@master1 test2]# tail -f /var/log/testloglog/testloglog.log [2026-01-24 18:51:09.358] [DEBUG] [testloglog.sh][操作:文件列表] [99ms] 处理文件: loglog.sh[2026-01-24 18:51:09.366] [INFO] [testloglog.sh][操作:文件列表] [106ms] 文件: loglog.sh (大小: 8.0K)[2026-01-24 18:51:09.371] [DEBUG] [testloglog.sh][操作:文件列表] [111ms] 处理文件: tesh.sh[2026-01-24 18:51:09.378] [INFO] [testloglog.sh][操作:文件列表] [118ms] 文件: tesh.sh (大小: 4.0K)[2026-01-24 18:51:09.383] [DEBUG] [testloglog.sh][操作:文件列表] [124ms] 处理文件: testloglog.sh[2026-01-24 18:51:09.391] [INFO] [testloglog.sh][操作:文件列表] [131ms] 文件: testloglog.sh (大小: 4.0K)[2026-01-24 18:51:09.396] [DEBUG] [testloglog.sh][操作:文件列表] [136ms] 处理文件: test_logs[2026-01-24 18:51:09.401] [WARN] [testloglog.sh][操作:文件列表] [142ms] 跳过目录: test_logs[2026-01-24 18:51:09.406] [ERROR] [testloglog.sh][操作:文件列表] [146ms] 预期外的文件缺失: nonexistent_file[2026-01-24 18:51:09.411] [INFO] [testloglog.sh][操作:文件列表] [151ms] ===== 测试完成 =====
3.测试日志轮转:为了测试我修改了多大日志开始轮转为20kb, 生成环境记得改成自定义大小
readonly MAX_LOG_SIZE_KB=20  # 单个日志文件最大大小(KB)
# 快速填充日志文件for i in {1..10000}; do ./test_loglog.sh; donels -l /var/log/test_loglog/  # 应看到 .1.gz 等轮转文件
[root@master1 test2]# ls -lth  /var/log/testloglog/总用量 28K-rw-r--r-- 1 root root 1.2K 1月  24 20:15 testloglog.log-rw-r--r-- 1 root root 1.8K 1月  24 20:15 testloglog.log.1.gz-rw-r--r-- 1 root root 1.8K 1月  24 20:14 testloglog.log.2.gz-rw-r--r-- 1 root root 1.8K 1月  24 20:14 testloglog.log.3.gz-rw-r--r-- 1 root root 1.8K 1月  24 20:13 testloglog.log.4.gz-rw-r--r-- 1 root root 5.9K 1月  24 20:13 testloglog.log.5.gz

五,总结

以上,就是shell脚本log日志模板的全部内容

最后的最后(Last but not least),欢迎交流:

关注公众号留言,或者在下方直接留言:

最新文章

随机文章