【问题标题】:How do I manage log verbosity inside a shell script?如何在 shell 脚本中管理日志详细程度?
【发布时间】:2017-07-13 05:09:14
【问题描述】:

我有一个很长的 bash 脚本,它调用了很多外部命令(git clonewgetapt-get 等),这些命令会在标准输出中打印很多内容。

我希望脚本有一些详细选项,以便它打印来自外部命令的所有内容、它的摘要版本(例如“正在安装依赖项...”、“正在编译...”等),或者在全部。但是我怎样才能做到这一点又不会弄乱我的所有代码呢?

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

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

哪个更好?我也愿意接受其他解决方案。

【问题讨论】:

  • 你考虑过使用set -x吗?在脚本顶部调用此命令,随后的每个命令都将被回显。您可以使用set +x 将其关闭。
  • 此外,当任何命令以非零状态退出时,您也可以使用set -e 中止脚本。
  • 不,这不是我需要的。我知道我正在运行哪些命令,我​​只是想要一种方法让我的脚本根据某些选项打印它们输出的所有内容或不打印。它更多的是用户而不是实际代码。
  • 为了记录某些命令,我​​使用了一个包装函数,它接收要执行的命令作为参数,然后运行它。如果详细程度打开,它还会打印命令。对于附加信息的可选输出,我使用另一个包装函数,如果详细程度打开,它会简单地回显其参数,如果详细程度关闭,则不做任何事情。
  • cronutils 包有一些有用的程序,包括这个: chronic 运行一个命令,并安排它的标准输出和标准错误仅在命令失败时显示(退出非零或崩溃)。如果命令成功,任何无关的输出都将被隐藏。

标签: linux bash shell unix logging


【解决方案1】:

解决方案 1。 考虑使用额外的文件描述符。 根据选择的详细程度,将所需的文件描述符重定向到 STDOUT 或 /dev/null。 将脚本中每个语句的输出重定向到与其重要性相对应的文件描述符。 看看https://unix.stackexchange.com/a/218355

【讨论】:

    【解决方案2】:

    解决方案 2。

    将脚本中每个语句的 $required_verbosity 和管道 STDOUT 设置为具有两个参数的辅助脚本,如下所示:

    声明 |记录器 actual_verbosity $required_verbosity

    如果 $actual_verbosity >= $required_verbosity,则在记录器脚本中将 STDIN 回显到 STDOUT(或日志文件等)。

    【讨论】:

      【解决方案3】:

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

      #!/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
      

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

      【讨论】:

      • 我仍然不确定在我运行的每个命令中添加“日志”是否是最干净的解决方案。我担心其他人会很难以这种方式阅读代码。但似乎没有更好的方法,所以我想我最终会选择这个
      • @Ocab19 - 请查看我的answer 中的log_debug_filelog_verbose_file 函数。有了这些,我们可以消除由诸如“log 1”等结构引起的混乱/可读性损失,这些结构作为我们需要限制其输出的每个命令的前缀。但是重定向输出然后调用日志函数也很混乱。
      • 查看 github.com/codeforester/base/blob/master/lib/stdlib.sh 了解完整的 Bash 日志记录解决方案。
      【解决方案4】:

      在@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 ==="; }
      

      假设上述源代码位于名为 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!"
      

      将产生以下输出:

      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
      

      正如我们所见,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
      }
      

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


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

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


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

      【讨论】:

      • 很高兴得到@charlesduffy 对此的看法。
      • 缺少 log_warning() 函数?
      • @Beolap:添加了log_warning。感谢您指出。
      猜你喜欢
      • 1970-01-01
      • 2019-05-27
      • 1970-01-01
      • 1970-01-01
      • 2019-09-08
      • 1970-01-01
      • 1970-01-01
      • 2017-07-28
      • 2022-12-03
      相关资源
      最近更新 更多