linux - Bash 中的简单日志记录级别

标签 linux bash logging

问题

我正在努力调整我编写并在许多脚本中重复使用多年的日志记录函数,以符合日志记录级别。

简而言之,我想使用一个全局变量来仅打印那些与所需日志记录级别详细程度匹配的日志。

目前

我当前的代码如下所示:

#################################################################################
# SCRIPT LOGGING CONFIGURATION
#
# The following is used by the script to output log data. Depending upon the log
# level indicated, more or less data may be output, with a "lower" level
# providing more detail, and the "higher" level providing less verbose output.
#################################################################################
DATETIME="`date +%Y-%m-%d` `date +%T%z`" # Date format at beginning of log entries to match RFC
DATE_FOR_FILENAME=`date +%Y%m%d`
#
SCRIPT_LOG_DIR="/var/log/company/${APP_NAME}/"
SCRIPT_LOGFILE="${SCRIPT_LOG_DIR}-APPNAME-${DATE_FOR_FILENAME}.log"
#
# Logging Level configuration works as follows:
# DEBUG - Provides all logging output
# INFO  - Provides all but debug messages
# WARN  - Provides all but debug and info
# ERROR - Provides all but debug, info and warn
#
# SEVERE and CRITICAL are also supported levels as extremes of ERROR
#
SCRIPT_LOGGING_LEVEL="DEBUG"
#################################################################################
#          ##      END OF GLOBAL VARIABLE CONFIGURATION      ##
#################################################################################
# LOGGING
#
# Calls to the logThis() function will determine if an appropriate log file
# exists. If it does, then it will use it, if not, a call to openLog() is made,
# if the log file is created successfully, then it is used.
#
# All log output is comprised of
# [+] An RFC 3339 standard date/time stamp
# [+] The declared level of the log output
# [+] The runtime process ID (PID) of the script
# [+] The log message
#################################################################################
function openLog {
    echo -e "${DATETIME} : PID $$ : INFO : New log file (${logFile}) created." >> "${SCRIPT_LOGFILE}"

    if ! [[ "$?" -eq 0 ]]
    then
        echo "${DATETIME} - ERROR : UNABLE TO OPEN LOG FILE - EXITING SCRIPT."
        exit 1
    fi
}

function logThis() {
    DATETIME=$(date --rfc-3339=seconds)
    if [[ -z "${1}" || -z "${2}" ]]
    then
        echo "${DATETIME} - ERROR : LOGGING REQUIRES A DESTINATION FILE, A MESSAGE AND A PRIORITY, IN THAT ORDER."
        echo "${DATETIME} - ERROR : INPUTS WERE: ${1} and ${2}."
        exit 1
    fi

    LOG_MESSAGE="${1}"
    LOG_PRIORITY="${2}"

    # Determine if logging level is supported and desired
    #
    # This seems more complex than may be necessary
    if [[ ${LOG_PRIORITY} -eq "DEBUG" ]] && [[ ${SCRIPT_LOGGING_LEVEL} -eq "DEBUG" ]]
    then
        LOG_PRIORITY_SUPPORTED=true
    elif [[ ${LOG_PRIORITY} -eq "INFO" ]] && [[ ${SCRIPT_LOGGING_LEVEL} -eq "DEBUG"||"INFO" ]]
    then
        LOG_PRIORITY_SUPPORTED=true
    elif [[ ${LOG_PRIORITY} -eq "WARN" ]] && [[ ${SCRIPT_LOGGING_LEVEL} -eq "DEBUG"||"INFO"||"WARN" ]]
    then
        LOG_PRIORITY_SUPPORTED=true
    elif [[ ${LOG_PRIORITY} -eq "ERROR"||"SEVERE"||"CRITICAL" ]] && [[ ${SCRIPT_LOGGING_LEVEL} -eq "DEBUG"||"INFO"||"WARN"||"ERROR"||"SEVERE"||"CRITICAL" ]]
    then
        LOG_PRIORITY_SUPPORTED=true
    else
        echo -e "CRITICAL: Declared log priority is not supported."
        exit 1
    fi

    # If logging level NOT supported, dump it
    if ! [ ${LOG_PRIORITY_SUPPORTED} ]
    then
        echo "priority unsupported"
        break
    fi

    # No log file, create it.
    if ! [[ -f ${SCRIPT_LOGFILE} ]]
    then
        echo -e "INFO : No log file located, creating new log file (${SCRIPT_LOGFILE})."
        echo "${DATETIME} : PID $$ :INFO : No log file located, creating new log file (${SCRIPT_LOGFILE})." >> "${SCRIPT_LOGFILE}"
        openLog
    fi

    # Write log details to file
    echo -e "${LOG_PRIORITY} : ${LOG_MESSAGE}"
    echo -e "${DATETIME} : PID $$ : ${LOG_PRIORITY} : ${LOG_MESSAGE}" >> "${SCRIPT_LOGFILE}"

    # Reset log level support flag
    LOG_PRIORITY_SUPPORTED=false
}

当使用这个函数时,就变成这样:

logThis "This is my log message" "DEBUG"

logThis "This is my log message" "ERROR"

logThis "This is my log message" "INFO"

尝试

您可以在上面的代码中看到,我已经尝试(无论多么复杂)在传入的消息上使用大小写选择来过滤消息。

这是行不通的。无论为 LOG_PRIORITY 提供的值如何,所有消息都会通过。

即使它不是受支持的值。例如,以下仍允许处理日志消息:

SCRIPT_LOGGING_LEVEL="FARCE"

或者即使我像这样设置给定消息的值:

logThis "This is my log message" "FARCE"

您的帮助

我不打算完全重构我拥有的功能。我在野外有太多使用所涉及功能的脚本,如果我改变标准化,也需要对这些进行返工。

我不一定需要有人像他们说的那样“为我做工作”,但考虑到我的限制,向有效方向轻推就足够了。 我很高兴在未来的编辑中发布最终实现

明白了

我认识到现在有更新更好的方法来处理 BASH 脚本中的日志记录功能,但是这些函数在如此多的脚本中的流行意味着对正在运行的函数进行简单更新将产生非常广泛的影响。

===

最终解决方案

为了结束这个问题的循环,最终解决方案包括一些最初不在范围内的更改,但为了满足一些更好的实践,我做了以下操作:

  1. 将所有变量名称转换为混合大小写,而不是为系统和环境变量保留的全部大写。一些评论者(@PesaThe 和@CharlesDuffy)注意到了这一点。
  2. 我原来的帖子指出我最初使用了 case select,但显示了 ifelif 语句的集合。我之前确实尝试过案例选择,但出于沮丧,我转向了维护繁重且难以辨认的 if + elif 选项。
  3. 代码更改允许删除(如@PesaThe 所建议的那样)在函数末尾重置不干净的变量。

解决方案详情

这个解决方案符合我的要求,因为它需要对现有脚本代码进行最少的更改,并且允许现有的调用函数的方法工作。

在接受解决方案时,有三个推荐选项作为对我帖子的回应。这三个选项都很有帮助而且很重要,但我最终选择的选项只需要三行代码即可实现。

如上所述,我确实进行了一些更改,这些更改不在范围内,但不会影响本文提供的代码之外的功能。

一个附加说明:我确实在我的目标环境中验证了这些功能,并且在编辑时它们在 Ubuntu 16.04 上按预期工作。

最终代码

#################################################################################
# SCRIPT LOGGING CONFIGURATION
#
# The following is used by the script to output log data. Depending upon the log
# level indicated, more or less data may be output, with a "lower" level
# providing more detail, and the "higher" level providing less verbose output.
#################################################################################
dateTime="`date +%Y-%m-%d` `date +%T%z`" # Date format at beginning of log entries to match RFC
dateForFileName=`date +%Y%m%d`
#
scriptLogDir="/var/log/company/${appName}/"
scriptLogPath="${scriptLogDir}${appName}-${dateForFileName}.log"
#
# Logging Level configuration works as follows:
# DEBUG - Provides all logging output
# INFO  - Provides all but debug messages
# WARN  - Provides all but debug and info
# ERROR - Provides all but debug, info and warn
#
# SEVERE and CRITICAL are also supported levels as extremes of ERROR
#
scriptLoggingLevel="DEBUG"
#################################################################################
#          ##      END OF GLOBAL VARIABLE CONFIGURATION      ##
#################################################################################
# LOGGING
#
# Calls to the logThis() function will determine if an appropriate log file
# exists. If it does, then it will use it, if not, a call to openLog() is made,
# if the log file is created successfully, then it is used.
#
# All log output is comprised of
# [+] An RFC 3339 standard date/time stamp
# [+] The declared level of the log output
# [+] The runtime process ID (PID) of the script
# [+] The log message
#################################################################################
function openLog {
    echo -e "${dateTime} : PID $$ : INFO : New log file (${scriptLogPath}) created." >> "${scriptLogPath}"

    if ! [[ "$?" -eq 0 ]]
    then
        echo "${dateTime} - ERROR : UNABLE TO OPEN LOG FILE - EXITING SCRIPT."
        exit 1
    fi
}

function logThis() {
    dateTime=$(date --rfc-3339=seconds)

    if [[ -z "${1}" || -z "${2}" ]]
    then
        echo "${dateTime} - ERROR : LOGGING REQUIRES A DESTINATION FILE, A MESSAGE AND A PRIORITY, IN THAT ORDER."
        echo "${dateTime} - ERROR : INPUTS WERE: ${1} and ${2}."
        exit 1
    fi

    logMessage="${1}"
    logMessagePriority="${2}"

    declare -A logPriorities=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3 [SEVERE]=4 [CRITICAL]=5)
    [[ ${logPriorities[$logMessagePriority]} ]] || return 1
    (( ${logPriorities[$logMessagePriority]} < ${logPriorities[$scriptLoggingLevel]} )) && return 2


    # No log file, create it.
    if ! [[ -f ${scriptLogPath} ]]
    then
        echo -e "INFO : No log file located, creating new log file (${scriptLogPath})."
        echo "${dateTime} : PID $$ :INFO : No log file located, creating new log file (${scriptLogPath})." >> "${scriptLogPath}"
        openLog
    fi

    # Write log details to file
    echo -e "${logMessagePriority} : ${logMessage}"
    echo -e "${dateTime} : PID $$ : ${logMessagePriority} : ${logMessage}" >> "${scriptLogPath}"
}

最佳答案

解决此问题的一种方法是创建所有级别的关联数组。每个级别都分配了一个数字,然后比较这些数字以决定您是否应该登录。假设您想添加另一个日志记录级别。你的那些 if 语句会失控:

#!/usr/bin/env bash

declare -A levels=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
script_logging_level="INFO"

logThis() {
    local log_message=$1
    local log_priority=$2

    #check if level exists
    [[ ${levels[$log_priority]} ]] || return 1

    #check if level is enough
    (( ${levels[$log_priority]} < ${levels[$script_logging_level]} )) && return 2

    #log here
    echo "${log_priority} : ${log_message}"
}

logThis "This will log" "WARN"
logThis "This will not log" "DEBUG"
logThis "This will not log" "OUCH"

LOG_PRIORITY_SUPPORTED=false 是您应该在函数中使用 local 变量的示例。此外,您不应使用大写变量,因为它们可能会与环境或内部 shell 变量发生冲突。

关于linux - Bash 中的简单日志记录级别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48086633/

相关文章:

java - Jetty:如何写入访问日志

Erlang/OTP 框架的 error_logger 在相当高的负载下挂起

linux - 查看 Ubuntu 安装了哪些磁盘驱动器

linux - 在 Linux 中散列目录?

ruby - Cron 和 Ruby.. "puts ` 系统命令`"有什么作用吗?

bash - 在 BASH 中以 N 进程批处理并行运行

linux - 如何用字符串作为变量sed替换整行?

bash - 如何安全地提前退出 bash 脚本?

android - 如何在 Android 启动前挂载 ramdisk?

elasticsearch - ElasticSearch:查询最新快照设计