Bash中的简单日志记录级别

时间:2018-01-03 23:33:27

标签: linux bash logging

问题

我正在努力调整我已编写并在多个脚本中重复使用多年的日志功能来记录日志记录级别。

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

CURRENTLY

我目前的代码如下:

#################################################################################
# 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"

THE ATTEMPT

您可以在上面的代码中看到,我使用传入的消息上的案例选择过滤消息时尝试(无论多么复杂)。

这不起作用。无论为LOG_PRIORITY提供的值如何,所有消息都将通过。

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

SCRIPT_LOGGING_LEVEL="FARCE"

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

logThis "This is my log message" "FARCE"

您的帮助

我不想完全重构我的功能。我使用所涉及的函数在野外有太多的脚本,如果我改变标准化,它也需要重新编写。

我不一定需要有人为我做这项工作"正如他们所说的那样,但考虑到我的约束,在一个有效的方向上轻推就足够了。 我很高兴能在未来的编辑中发布最终实施

理解

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

===

最终解决方案

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

  1. 将所有变量名称转换为混合大小写与ALL CAPS,后者是为系统和环境变量保留的。一些评论者(@PesaThe和@CharlesDuffy)注意到了这一点。
  2. 我的原始帖子指出我最初使用了一个案例选择,但显示了ifelif语句的集合。我之前尝试过一个案例选择,但是因为沮丧而转移到维护费用很高且难以辨认的if + elif选项。
  3. 代码的更改允许在函数结束时删除(如@Pesa所建议的)不干净的变量重置。
  4. 解决方案详情

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

    在接受解决方案时,我的帖子中有三个推荐选项。所有这三个选项都很有用,但我最终选择的只需要三行代码即可实现。

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

    另外一个注意事项:我在目标环境中验证了这些功能,并且在编辑时它们在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}"
    }
    

3 个答案:

答案 0 :(得分:6)

解决此问题的一种方法是创建所有级别的关联数组。为每个级别分配一个数字,然后比较这些数字以决定是否应该记录。想象一下,您想要添加另一个日志记录级别。你的那些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变量冲突。

答案 1 :(得分:1)

您的主要问题是使用如下表达式:

[[ ${LOG_PRIORITY} -eq "DEBUG" ]]

正如bash文档指出的那样(我强调):

  

arg1 OP arg2

     

OP-eq-ne-lt-le-gt-ge之一。如果arg1等于,不等于,小于,小于或等于,大于或大于或等于arg2,则这些算术二元运算符返回true , 分别。 Arg1arg2可能是正整数或负整数。

因此,尝试使用-eq来比较字符串是行不通的:

pax@paxbox1> [[ a -eq b ]] && echo equal
equal

在该示例中,ab都被视为零。

为了比较字符串,您应该使用==而不是-eq

pax@paxbox1> [[ a == b ]] && echo equal
pax@paxbox1> [[ a == a ]] || echo unequal
unequal
pax@paxbox1> [[ a == a ]] && echo equal
equal

但是,如果您希望将这些日志记录级别用作记录的 minimim 阈值,我会尽快将它们转换为整数值,以便您可以使用数字比较来最小化代码大小,如:

SCRIPT_LOGGING_LEVEL=1 # errors and warnings only
:
case ${LOG_PRIORITY} in
    ERROR) NUM_PRIO=0;;
    WARN)  NUM_PRIO=1;;
    INFO)  NUM_PRIO=2;;
    DEBUG) NUM_PRIO=3;;
    *)     return;;
esac
[[ ${NUM_PRIO} -le ${SCRIPT_LOGGING_LEVEL} ]] && logTheMessage

答案 2 :(得分:-1)

我相信你写错了。

首先,您应该使用==进行字符串比较,而不是使用-eq(我怀疑这存在于[[ ]]中,我认为它只存在于[ ]

其次,|| 做您认为应该做的事情。它可能非常复杂,因为你必须写

elif [ ${LOG_PRIORITY} = "ERROR" -o ${LOG_PRIORITY} = "SEVERE" -o ${LOG_PRIORITY} = "CRITICAL" ] && [ ${SCRIPT_LOGGING_LEVEL} = "DEBUG" -o ${SCRIPT_LOGGING_LEVEL} = "INFO" -o ${SCRIPT_LOGGING_LEVEL} = "WARN" -o ${SCRIPT_LOGGING_LEVEL} = "ERROR" -o ${SCRIPT_LOGGING_LEVEL} = "SEVERE" -o ${SCRIPT_LOGGING_LEVEL} = "CRITICAL" ]

但是,您可以使用case

case "${LOG_PRIORITY}" in
    "ERROR"|"CRITICAL"|"SEVERE")
        # Do stuff
        ;;
    *)
        echo "Error"
        exit 1 ;;
esac

这可以简化你的条件。

如果你能稍微(略微!)更大的修改,我会建议你记录你的日志记录级别(将它们转换为数字日志级别),然后你就可以使用算术比较,这是伟大的和可读的。