使用Bash脚本记录日志

时间:2011-04-26 11:18:47

标签: bash logging rotation

我有以下问题:

我有一个应用程序,它不断向stderr和stdout生成输出。此应用程序的输出在日志文件中捕获(应用程序重定向为:&> log.txt)。我没有任何选择来为此文件生成正确的日志记录。

现在,我有一个cron作业,每小时运行一次,除了执行其他操作外,它还尝试将上面的日志文件旋转到log.txt.1,然后创建一个空文件并将其复制到log.txt的

看起来像:

cp log.txt log.txt.1
touch /tmp/empty
cp /tmp/empty log.txt

问题是,应用程序仍在写入它,因此我在log.txt.1中得到一些非常奇怪的东西,它以很多垃圾字符开头,实际的日志文件在某处最后。

你有什么想法,如何针对这种特定情况制作正确的日志(我也试过cat log.txt > log.txt.1,不起作用)?使用logrotate这个特定的应用程序不是一个选项,幕后有一个完整的机制,我可能不会改变。

谢谢, F。

5 个答案:

答案 0 :(得分:10)

好的,这是一个受http://en.wikibooks.org/wiki/Bourne_Shell_Scripting/Files_and_streams

启发的想法
  1. 制作命名管道:

    mkfifo /dev/mypipe
    
  2. 将stdout和stderr重定向到命名管道:

    &> /dev/mypipe
    
  3. 从mypipe读到一个文件:

    cat < /dev/mypipe > /var/log/log.txt &
    
  4. 当你需要记录旋转,杀死猫,旋转日志,然后重启猫。

  5. 现在,我还没有测试过这个。告诉我们它是怎么回事。

    注意:您可以为命名管道指定任何名称,例如/ var / tmp / pipe1,/ var / log / pipe,/ tmp / abracadabra等。只需确保在运行日志脚本之前启动后重新创建管道。


    或者,不要使用cat,而是使用简单的脚本文件:

    #!/bin/bash
    
    while : ; do
      read line
      printf "%s\n" "$line"
    done
    

    此脚本保证每个换行符的输出。 (cat可能无法开始输出,直到其缓冲区已满或遇到EOF)


    最终 - 并且测试 - 尝试

    重要提示: 请阅读以下 @andrew 的评论。有几种情况需要注意。

    好的!终于可以访问我的Linux机器了。方法如下:

    第1步:制作此刻录机脚本:

    #!/bin/bash
    
    LOGFILE="/path/to/log/file"
    SEMAPHORE="/path/to/log/file.semaphore"
    
    while : ; do
      read line
      while [[ -f $SEMAPHORE ]]; do
        sleep 1s
      done
      printf "%s\n" "$line" >> $LOGFILE
    done
    

    第2步:让录音机投入使用:

    1. 创建命名管道:

      mkfifo $PIPENAME
      
    2. 重定向您的应用程序的STDOUT&amp; STDERR到命名管道:

      ...things... &> $PIPENAME
      
    3. 启动录像机:

      /path/to/recorder.sh < $PIPENAME &
      

      您可能希望nohup以上内容使其无法注销。

    4. 完成!

    5. 第3步:如果您需要进行日志转换,请暂停录音机:

      touch /path/to/log/file.semaphore
      mv /path/to/log/file /path/to/archive/of/log/file
      rm /path/to/log/file.semaphore
      

      我建议将上述步骤放入自己的脚本中。随意将第二行更改为您要使用的任何日志旋转方法。


      注意: 如果你对C编程很方便,你可能想制作一个简短的C程序来执行recorder.sh的功能。编译的C程序肯定比nohup-ed分离的bash脚本更轻。


      注意2: David Newcomb在评论中提供了一条有用的警告:当录像机未运行时,写入管道将阻止可能导致程序无法预测地失败。确保录音机在尽可能短的时间内停机(或旋转)。

      因此,如果您可以确保真正快速发生旋转,则可以使用sleep替换/bin/sleep(仅接受整数值的内置命令)(a接受浮动值的程序)并将睡眠周期设置为0.5或更短。

答案 1 :(得分:1)

首先,你真的不应该在这里重新发明方形轮。你的同伴可能反对rotating the logs on daily schedule which automatically applies to all scripts in /etc/logrotate.d/ - 这可以通过将脚本放在别处来避免。

现在,the standard approach to log rotation(在Collection中实现)也可以由任何其他设施实施。例如。这是logrotate中的示例实现:

bash

最后一项是通过发送SIGHUP或(不常使用)SIGUSR1并在守护程序中使用信号处理程序来替换相应的文件描述符或变量来完成的。这样,交换机是原子的,因此日志记录可用性不会中断。在bash中,这看起来像:

MAXLOG=<maximum index of a log copy>
for i in `seq $((MAXLOG-1)) -1 1`; do
    mv "log."{$i,$((i+1))}    #will need to ignore file not found errors here
done 
mv log log.1    # since a file descriptor is linked to an inode rather than path,
                #if you move (or even remove) an open file, the program will continue
                #to write into it as if nothing happened
                #see https://stackoverflow.com/questions/5219896/how-do-the-unix-commands-mv-and-rm-work-with-open-files
<make the daemon reopen the log file with the old path>

另一种方法是使编写程序每次写入程序时都跟踪日志大小并进行旋转。这限制了您可以写入的选项以及程序本身支持的旋转逻辑。但它有一个独立的解决方案,并在每次写入时检查日志大小而不是按计划进行检查。许多语言的标准库都有这样的功能。作为一种插入式解决方案,这是在Apache的rotatelogs

中实现的
trap { exec &>"$LOGFILE"; } HUP

答案 2 :(得分:0)

您还可以通过Apache rotatelogs实用程序管道输出。 或者遵循以下脚本:

#!/bin/ksh
#rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]

numberOfFiles=10

while getopts "n:fltvecp:L:" opt; do
    case $opt in
  n) numberOfFiles="$OPTARG"
    if ! printf '%s\n' "$numberOfFiles" | grep '^[0-9][0-9]*$' >/dev/null; then
      printf 'Numeric numberOfFiles required %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$numberOfFiles" 1>&2
      exit 1
    elif [ $numberOfFiles -lt 3 ]; then
      printf 'numberOfFiles < 3 %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$numberOfFiles" 1>&2
    fi
  ;;
  *) printf '-%s ignored. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$opt" 1>&2
  ;;
  esac
done
shift $(( $OPTIND - 1 ))

pathToLog="$1"
fileSize="$2"

if ! printf '%s\n' "$fileSize" | grep '^[0-9][0-9]*[BKMG]$' >/dev/null; then
  printf 'Numeric fileSize followed by B|K|M|G required %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$fileSize" 1>&2
  exit 1
fi

sizeQualifier=`printf "%s\n" "$fileSize" | sed "s%^[0-9][0-9]*\([BKMG]\)$%\1%"`

multip=1
case $sizeQualifier in
B) multip=1 ;;
K) multip=1024 ;;
M) multip=1048576 ;;
G) multip=1073741824 ;;
esac

fileSize=`printf "%s\n" "$fileSize" | sed "s%^\([0-9][0-9]*\)[BKMG]$%\1%"`
fileSize=$(( $fileSize * $multip ))
fileSize=$(( $fileSize / 1024 ))

if [ $fileSize -le 10 ]; then
  printf 'fileSize %sKB < 10KB. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$fileSize" 1>&2
  exit 1
fi

if ! touch "$pathToLog"; then
  printf 'Could not write to log file %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]\n' "$pathToLog" 1>&2
  exit 1
fi

lineCnt=0
while read line
do
  printf "%s\n" "$line" >>"$pathToLog"
  lineCnt=$(( $lineCnt + 1 ))
  if [ $lineCnt -gt 200 ]; then
    lineCnt=0
    curFileSize=`du -k "$pathToLog" | sed -e 's/^[  ][  ]*//' -e 's%[   ][  ]*$%%' -e 's/[  ][  ]*/[    ]/g' | cut -f1 -d" "`
    if [ $curFileSize -gt $fileSize ]; then
      DATE=`date +%Y%m%d_%H%M%S`
      cat "$pathToLog" | gzip -c >"${pathToLog}.${DATE}".gz && cat /dev/null >"$pathToLog"
      curNumberOfFiles=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | wc -l | sed -e 's/^[   ][  ]*//' -e 's%[   ][  ]*$%%' -e 's/[  ][  ]*/[    ]/g'`
      while [ $curNumberOfFiles -ge $numberOfFiles ]; do
        fileToRemove=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | head -1`
        if [ -f "$fileToRemove" ]; then
          rm -f "$fileToRemove"
          curNumberOfFiles=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | wc -l | sed -e 's/^[   ][  ]*//' -e 's%[   ][  ]*$%%' -e 's/[  ][  ]*/[    ]/g'`
        else
          break
        fi
      done
    fi
  fi
done

答案 3 :(得分:0)

本周末我写了logrotee。如果我以前读过@JdeBP的great answer about multilog,我可能不会。

我专注于它是轻量级的,并且能够bzip2输出块,如:

verbosecommand | logrotee  \
  --compress "bzip2 {}" --compress-suffix .bz2 \
  /var/log/verbosecommand.log

尽管如此,还有很多工作要做和测试。

答案 4 :(得分:0)

您可以利用rotatelogsdocs here)。该实用程序将使脚本的标准输出与日志文件脱钩,从而以透明的方式管理轮换。例如:

your_script.sh | rotatelogs /var/log/your.log 100M

当输出文件达到100M时将自动旋转输出文件(可以配置为根据时间间隔旋转)。