如何在shell脚本中管理日志详细程度?

Oca*_*b19 6 unix linux bash shell logging

我有一个调用相当多的外部命令(一个相当长的bash脚本git clone,wget,apt-get打印很多东西到标准输出等).

我希望脚本有一些详细的选项,所以它打印来自外部命令的所有内容,它的摘要版本(例如"安装依赖项......","编译......"等)或根本没有.但是如何在不弄乱我的所有代码的情况下做到这一点?

我已经考虑过可能的解决方案:一个是创建一个包装函数来运行外部命令并打印标准输出所需的内容,具体取决于开始时设置的选项.这些似乎更容易实现,但这意味着为代码添加了许多额外的混乱.

另一种解决方案是将所有输出发送到几个外部文件,并在解析脚本开头的参数时tail -f,如果指定了详细程度,则在该文件上运行.这将很容易实现,但对我来说似乎非常hacky,我担心它的性能影响.

哪一个更好?我也对其他解决方案持开放态度.

cod*_*ter 8

进一步改进@Fred 的想法,我们可以通过这种方式构建一个小型日志库:

declare -A _log_levels=([FATAL]=0 [ERROR]=1 [WARN]=2 [INFO]=3 [DEBUG]=4 [VERBOSE]=5)
declare -i _log_level=3
set_log_level() {
  level="${1:-INFO}"
  _log_level="${_log_levels[$level]}"
}

log_execute() {
  level=${1:-INFO}
  if (( $1 >= ${_log_levels[$level]} )); then
    "${@:2}" >/dev/null
  else
    "${@:2}"
  fi
}

log_fatal()   { (( _log_level >= ${_log_levels[FATAL]} ))   && echo "$(date) FATAL  $*";  }
log_error()   { (( _log_level >= ${_log_levels[ERROR]} ))   && echo "$(date) ERROR  $*";  }
log_warning() { (( _log_level >= ${_log_levels[WARNING]} )) && echo "$(date) WARNING  $*";  }
log_info()    { (( _log_level >= ${_log_levels[INFO]} ))    && echo "$(date) INFO   $*";  }
log_debug()   { (( _log_level >= ${_log_levels[DEBUG]} ))   && echo "$(date) DEBUG  $*";  }
log_verbose() { (( _log_level >= ${_log_levels[VERBOSE]} )) && echo "$(date) VERBOSE $*"; }

# functions for logging command output
log_debug_file()   { (( _log_level >= ${_log_levels[DEBUG]} ))   && [[ -f $1 ]] && echo "=== command output start ===" && cat "$1" && echo "=== command output end ==="; }
log_verbose_file() { (( _log_level >= ${_log_levels[VERBOSE]} )) && [[ -f $1 ]] && echo "=== command output start ===" && cat "$1" && echo "=== command output end ==="; }
Run Code Online (Sandbox Code Playgroud)

假设上述源代码位于名为 logging_lib.sh 的库文件中,我们可以通过以下方式在常规 shell 脚本中使用它:

#!/bin/bash

source /path/to/lib/logging_lib.sh

set_log_level DEBUG

log_info  "Starting the script..."

# method 1 of controlling a command's output based on log level
log_execute INFO date

# method 2 of controlling the output based on log level
date &> date.out
log_debug_file date.out

log_debug "This is a debug statement"
...
log_error "This is an error"
...
log_warning "This is a warning"
...
log_fatal "This is a fatal error"
...
log_verbose "This is a verbose log!"
Run Code Online (Sandbox Code Playgroud)

将导致此输出:

Fri Feb 24 06:48:18 UTC 2017 INFO    Starting the script...
Fri Feb 24 06:48:18 UTC 2017
=== command output start ===
Fri Feb 24 06:48:18 UTC 2017
=== command output end ===
Fri Feb 24 06:48:18 UTC 2017 DEBUG   This is a debug statement
Fri Feb 24 06:48:18 UTC 2017 ERROR   This is an error
Fri Feb 24 06:48:18 UTC 2017 WARNING   This is a warning
Fri Feb 24 06:48:18 UTC 2017 FATAL   This is a fatal error
Run Code Online (Sandbox Code Playgroud)

正如我们所见,log_verbose没有产生任何输出,因为日志级别为 DEBUG,比 VERBOSE 低一级。但是,log_debug_file date.out确实产生了输出,因此产生了输出log_execute INFO,因为日志级别设置为 DEBUG,即 >= INFO。

以此为基础,如果我们需要更多微调,我们还可以编写命令包装器:

git_wrapper() {
  # run git command and print the output based on log level
}
Run Code Online (Sandbox Code Playgroud)

有了这些,脚本可以得到增强,以采用--log-level level可以确定它应该运行的日志详细程度的参数。


这是 Bash 日志记录的完整实现,包含多个记录器:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


如果有人想知道为什么在上面的代码中某些变量的命名带有前导下划线,请参阅这篇文章:


Fre*_*red 4

您的问题中已经有了似乎最干净的想法(包装函数),但您似乎认为它会很混乱。我建议你重新考虑一下。它可能如下所示(不一定是成熟的解决方案,只是为了给您提供基本的想法):

#!/bin/bash

# Argument 1 : Logging level for that command
# Arguments 2... : Command to execute
# Output suppressed if command level >= current logging level 
log()
{
if
  (($1 >= logging_level))
then
  "${@:2}" >/dev/null 2>&1
else
  "${@:2}"
fi
}

logging_level=2

log 1 command1 and its args
log 2 command2 and its args
log 3 command4 and its args
Run Code Online (Sandbox Code Playgroud)

您可以安排在包装函数中处理任何所需的重定向(如果需要,可以使用文件描述符),以便脚本的其余部分保持可读并且不受重定向和条件的影响,具体取决于所选的日志记录级别。