Bash 中的简单日志记录级别
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/48086633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Simple logging levels in Bash
提问by Chris Giddings
THE ISSUE
问题
I am struggling to adapt a logging function I've written and reused for years across many scripts to honor logging levels.
我正在努力适应我多年来在许多脚本中编写和重用的日志记录功能,以尊重日志记录级别。
In short, I would like to use a single global variable to print only those logs which match the desired logging level verbosity.
简而言之,我想使用单个全局变量仅打印那些与所需日志级别详细程度匹配的日志。
CURRENTLY
目前
My current code looks like so:
我当前的代码如下所示:
#################################################################################
# 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 "" || -z "" ]]
then
echo "${DATETIME} - ERROR : LOGGING REQUIRES A DESTINATION FILE, A MESSAGE AND A PRIORITY, IN THAT ORDER."
echo "${DATETIME} - ERROR : INPUTS WERE: and ."
exit 1
fi
LOG_MESSAGE=""
LOG_PRIORITY=""
# 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
}
When using the function is becomes used as so:
使用该函数时,将使用如下:
logThis "This is my log message" "DEBUG"
or
或者
logThis "This is my log message" "ERROR"
or
或者
logThis "This is my log message" "INFO"
THE ATTEMPT
尝试
You can see in the code above that I have made an attempt (however complicated) at filtering messages using a case select on the message being passed in.
您可以在上面的代码中看到,我已经尝试(无论多么复杂)对传入的消息使用案例选择来过滤消息。
This does not work. All messages are getting through regardless of the value provided for LOG_PRIORITY
.
这不起作用。无论为 提供的值如何,所有消息都会通过LOG_PRIORITY
。
Even if it's not a supported value.For example, the following still allows log messages to process:
即使它不是受支持的值。例如,以下仍然允许处理日志消息:
SCRIPT_LOGGING_LEVEL="FARCE"
Or even if I set the value for a given message like so:
或者即使我为给定的消息设置了值,如下所示:
logThis "This is my log message" "FARCE"
YOUR HELP
你的帮助
I'm not looking to completely refactor the functions I have. I have too many scripts in the wild using the functions involved and it would require rework of those as well if I alter the standardization.
我不打算完全重构我拥有的功能。我有太多的脚本在使用所涉及的函数,如果我改变标准化,它也需要返工。
I don't necessarily need someone to "do the work for me" as they say, but a nudge in an effective direction given my constrains would be sufficient. I will be excited to post the final implementation in a future edit.
我不一定需要有人像他们说的那样“为我做工作”,但是考虑到我的限制,朝着有效的方向推动就足够了。我会很高兴在未来的编辑中发布最终实现。
UNDERSTOOD
了解
I recognize there are newer and better ways to handle logging functionality in BASH scripts now, but the prevalence of these functions across so many scripts means a simple update to the functions in play will have very broad impact.
我知道现在有更新更好的方法来处理 BASH 脚本中的日志记录功能,但是这些函数在如此多的脚本中的流行意味着对正在运行的函数的简单更新将产生非常广泛的影响。
===
===
FINAL SOLUTION
最终解决方案
To close the loop on this question, the final solution includes a few changes which weren't originally in-scope, but to meet some better practices I did the following:
为了结束这个问题的循环,最终的解决方案包括一些最初不在范围内的更改,但为了满足一些更好的实践,我做了以下操作:
- Converted all variable names to being mixed case vs ALL CAPS which is reserved for the system and environment variables. This was noted by a couple commenters (@PesaThe and @CharlesDuffy).
- My original post noted I used a case select initially, but showed a collection of
if
andelif
statements. I did attempt a case select previously, but had moved on to the upkeep-heavy and illegible if + elif option out of frustration. - The change in code allowed for the removal (as suggested by @PesaThe) of an unclean variable reset at the end of the function.
- 将所有变量名称转换为大小写混合与为系统和环境变量保留的全部大写。一些评论者(@PesaThe 和@CharlesDuffy)注意到了这一点。
- 我原来的帖子指出我最初使用了一个案例选择,但显示了一个
if
和elif
语句的集合。我之前确实尝试过案例选择,但出于沮丧,我转向了维护繁重且难以辨认的 if + elif 选项。 - 代码更改允许删除(如@PesaThe 所建议的)在函数结束时重置的不干净变量。
SOLUTION DETAILS
解决方案详情
This solution meets my requirements because it requires the least changes to existing script code and permits the existing method of calling the function to work.
此解决方案满足我的要求,因为它对现有脚本代码的更改最少,并且允许调用函数的现有方法起作用。
At the time of the acceptance of a solution, there were three recommended options in response to my post. All three options were helpful and on point, but the one I ultimately chose required only three lines of code to implement.
在接受解决方案时,针对我的帖子提供了三个推荐选项。所有三个选项都非常有用且切中要害,但我最终选择的一个只需要三行代码即可实现。
As noted above, I did make some changes which were not in-scope but which did not impact functionality outside the code provided in this post.
如上所述,我确实进行了一些不在范围内的更改,但不会影响本文提供的代码之外的功能。
One additional note: I did validate these functions in my target environment and they work as desired on Ubuntu 16.04 at the time of this edit.
另一个注意事项:我确实在我的目标环境中验证了这些功能,并且在进行此编辑时,它们在 Ubuntu 16.04 上按需要工作。
FINAL CODE
最终代码
#################################################################################
# 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 "" || -z "" ]]
then
echo "${dateTime} - ERROR : LOGGING REQUIRES A DESTINATION FILE, A MESSAGE AND A PRIORITY, IN THAT ORDER."
echo "${dateTime} - ERROR : INPUTS WERE: and ."
exit 1
fi
logMessage=""
logMessagePriority=""
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}"
}
回答by PesaThe
One way to approach this is to create an associative array of all the levels. Each level is assigned a number and those numbers are then compared to decide whether you should log or not. Imagine you wanted to add another logging level. Those if
statements of yours would get out of control:
解决此问题的一种方法是创建所有级别的关联数组。每个级别都分配了一个数字,然后将这些数字进行比较以决定您是否应该登录。假设您想添加另一个日志记录级别。if
你的那些陈述会失控:
#!/usr/bin/env bash
declare -A levels=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
script_logging_level="INFO"
logThis() {
local log_message=
local log_priority=
#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
is an example why you should use local
variables in functions. Also, you shouldn't use uppercase variables as they could clash with environmental or internal shell variables.
LOG_PRIORITY_SUPPORTED=false
是一个为什么你应该local
在函数中使用变量的例子。此外,您不应使用大写变量,因为它们可能与环境或内部 shell 变量发生冲突。
回答by paxdiablo
Your main issue is with using an expression like:
您的主要问题是使用如下表达式:
[[ ${LOG_PRIORITY} -eq "DEBUG" ]]
As the bash
documentation points out (my emphasis):
正如bash
文档所指出的(我的重点):
arg1 OP arg2
OP
is one of-eq
,-ne
,-lt
,-le
,-gt
, or-ge
. These arithmeticbinary operators return true ifarg1
is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal toarg2
, respectively.Arg1
andarg2
may be positive or negative integers.
arg1 OP arg2
OP
是以下之一-eq
,-ne
,-lt
,-le
,-gt
,或-ge
。如果分别等于、不等于、小于、小于或等于、大于或大于或等于,则这些算术二元运算符返回 true 。 并且可以是正整数或负整数。arg1
arg2
Arg1
arg2
So, attempting to use -eq
to compare strings is not going to work:
因此,尝试用于-eq
比较字符串是行不通的:
pax@paxbox1> [[ a -eq b ]] && echo equal
equal
In that example, both a
and b
are treated as zero.
在该示例中,a
和b
都被视为零。
For comparing strings, you should be using ==
rather than -eq
:
为了比较字符串,您应该使用==
而不是-eq
:
pax@paxbox1> [[ a == b ]] && echo equal
pax@paxbox1> [[ a == a ]] || echo unequal
unequal
pax@paxbox1> [[ a == a ]] && echo equal
equal
However, given your desire to use these logging levels as minimimthresholds for logging, I'd be turning them into integral values as quickly as possible so you can use numeric comparisons to minimise your code size, something like:
但是,鉴于您希望将这些日志记录级别用作日志记录的最小阈值,我会尽快将它们转换为整数值,以便您可以使用数字比较来最小化代码大小,例如:
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
回答by iBug
I believe you're writing something wrong.
我相信你写错了。
First, you should use ==
for string comparison, instead of -eq
(I doubt this exist in [[ ]]
, I think it's only in [ ]
)
首先,您应该==
用于字符串比较,而不是-eq
(我怀疑这存在于 中[[ ]]
,我认为它仅存在于[ ]
)
Second, ||
does notdo what you think it should. It could have been very complex because you have to write
其次,||
它不会做你认为它应该。它可能非常复杂,因为您必须编写
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" ]
You can, however, use case
:
但是,您可以使用case
:
case "${LOG_PRIORITY}" in
"ERROR"|"CRITICAL"|"SEVERE")
# Do stuff
;;
*)
echo "Error"
exit 1 ;;
esac
This can simpify your conditions.
这可以简化您的条件。
If you can stand a slightly (only slightly!) greater modification, I will suggest numerifying your logging levels (converting them into numeric log levels), then you'll be able to use arithmetic comparison, which is great and readable.
如果你能忍受稍微(只是稍微!)更大的修改,我会建议你对你的日志级别进行数字化(将它们转换为数字日志级别),然后你就可以使用算术比较,这是很好的和可读的。