web-dev-qa-db-ja.com

すべてのbashスクリプトアクションを完全にログに記録するにはどうすればよいですか?

スクリプト出力から、すべてのログデータをエラーメッセージと共にキャプチャし、それらすべてをログファイルにリダイレクトします。

以下のようなスクリプトがあります:

#!/bin/bash
(
echo " `date` : part 1 - start "
ssh -f [email protected] 'bash /www/htdocs/server.com/scripts/part1.sh logout exit'
echo " `date` : sleep 120"
sleep 120
echo " `date` : part 2 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part2.sh logout exit'
echo " `date` : part 3 - start"
ssh [email protected] 'bash /www/htdocs/server.com/scripts/part3.sh logout exit'
echo " `date` : END"
) | tee -a /home/scripts/cron/logs

ファイル/home/scripts/cron/logsのすべてのアクションを確認したい

しかし、これはechoコマンドの後に置いたものだけです。

SSHコマンドが成功したことをログにチェックインする方法は?

すべてのログを収集する必要があります。これは、スクリプトのすべてのコマンドの結果を監視し、スクリプトが失敗したときに何が起こっているかをよりよく分析するために必要です。

50
BlueMark

私は通常、すべてのスクリプトの最初に次のようなものを置きます(特に、デーモンとして実行する場合)。

#!/bin/bash
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>log.out 2>&1
# Everything below will go to the file 'log.out':

説明:

  1. exec 3>&1 4>&2

    ファイル記述子を保存して、リダイレクト前の状態に復元したり、次のリダイレクト前の状態に出力するために自分自身を使用したりできるようにします。

  2. trap 'exec 2>&4 1>&3' 0 1 2 3

    特定の信号のファイル記述子を復元します。サブシェルの終了時に復元する必要があるため、通常は必要ありません。

  3. exec 1>log.out 2>&1

    stdoutをファイルlog.outにリダイレクトし、次にstderrstdoutにリダイレクトします。同じファイルに移動する場合は、順序が重要であることに注意してください。 stdoutmuststderrstdoutにリダイレクトされる前にリダイレクトされる必要があります。

それ以降、コンソールに出力を表示するには(おそらく)、&3にリダイレクトします。例えば、

echo "$(date) : part 1 - start" >&3

上記の3行目を実行する前に、stdoutが指示された場所、おそらくコンソールに移動します。

77
nicerobot

ログファイルにssh出力を取得するには、stderrstdoutにリダイレクトする必要があります。これを行うには、bashスクリプトの後に2>&1を追加します。

次のようになります。

#!/bin/bash
(
...
) 2>&1 | tee ...

メッセージが正しい順序で表示されない場合は、別のサブシェルを追加してみてください:

#!/bin/bash
((
...
) 2>&1) | tee ...
16
Christian

私があなたの質問を読んでいるとき、あなたは出力をログに記録したくありませんが、コマンドのシーケンス全体、その場合、他の答えはあなたを助けません。

-xを使用してシェルスクリプトを呼び出し、すべてを出力します。

sh -x foo.sh

必要なファイルにログインします。

sh -x foo.sh >> /home/scripts/cron/logs

15
Alex Holst

Bashでは、set -xを置くことができ、その後に実行するすべてのコマンド(およびbash変数)を出力します。 set +xでオフにできます。

偏執的になりたい場合は、スクリプトにset -o errexitを含めることができます。つまり、1つのコマンドがゼロ以外の終了コードを返した場合、スクリプトは失敗して停止します。これは、何か問題が発生したことを知らせるUNIX標準の方法です。

より良いログを取得したい場合は、ts debian/ubuntuパッケージのmoreutilsを調べる必要があります。各行の前にタイムスタンプを付けて出力します。だから、物事がいつ起こったかを見ることができます。

10
Rory

@nicerobot( すべてのbashスクリプトアクションを完全にログに記録するにはどうすればよいですか? )の回答では、完全にすべての出力をコンソール。一部の出力はまだ失われる可能性があります。

完全なリダイレクトは次のようになります。

#!/bin/bash

# some basic initialization steps including `NEST_LVL` and `SCRIPTS_LOGS_ROOT variables...
source ".../__myinit__.sh"

# no local logging if nested call
(( ! IMPL_MODE && ! NEST_LVL )) && {
  export IMPL_MODE=1
  exec 3>&1 4>&2
  trap 'exec 2>&4 1>&3' EXIT HUP INT QUIT RETURN

  [[ ! -e "${SCRIPTS_LOGS_ROOT}/.log" ]] && mkdir "${SCRIPTS_LOGS_ROOT}/.log"

  # RANDOM instead of milliseconds
  case $BASH_VERSION in
    # < 4.2
    [123].* | 4.[01] | 4.0* | 4.1[^0-9]*)
      LOG_FILE_NAME_SUFFIX=$(date "+%Y'%m'%d_%H'%M'%S''")$(( RANDOM % 1000 ))
      ;;
    # >= 4.2
    *)
      printf -v LOG_FILE_NAME_SUFFIX "%(%Y'%m'%d_%H'%M'%S'')T$(( RANDOM % 1000 ))" -1
      ;;
  esac

  (
  (
    myfoo1
    #...
    myfooN

    # self script reentrance...
    exec $0 "$@"
  ) | tee -a "${SCRIPTS_LOGS_ROOT}/.log/${LOG_FILE_NAME_SUFFIX}.myscript.log" 2>&1
  ) 1>&3 2>&4

  exit $?
}

(( NEST_LVL++ ))

# usual script body goes here...

説明

  1. 内部のecho呼び出しをコンソールとファイルの両方にリダイレクトする必要があるため、出力をリダイレクトするだけでは十分ではないため、出力ストリームを分割する唯一の方法はpipe-plus-teeです。
  2. (...)演算子を使用して、各ストリームをスタンドアロンファイルにリダイレクトするか、別の手順ですべてのストリームを元の状態に戻す必要があります。 2番目の理由は、自己リエントランスが追加のリダイレクトなしでそのまま機能する前にmyfooが呼び出されるためです。
  3. スクリプトはネストされたスクリプトから呼び出すことができるため、ファイルの出力を最上位の呼び出しで一度だけリダイレクトする必要があります。したがって、NEST_LVL変数を使用して、ネスト呼び出しレベルを示します。
  4. リダイレクトはスクリプトに関係なく外部で実行できるため、IMPL_MODE変数によって内部リダイレクトを明示的に有効にする必要があります。
  5. スクリプトの実行ごとに新しい一意のログファイルを自動的に作成するには、日時の値を使用する必要があります。
1
Andry

他の人が言ったことに続いて、セット manual は良いリソースです。置いた:

#!/usr/bin/env bash
exec 1> command.log 2>&1
set -x

続けたいスクリプトの先頭、またはset -exエラー時に終了するかどうか。

1
dragon951

TL; DR-昨日、プログラムの実行とセッションをログに記録するための一連のツールを書きました。

現在 https://github.com/wwalker/quick-log で入手できます

管理者として、私は常に、スクリプトではなく、いくつかのコマンドの出力をログに記録したいと思っています。その問題を解決するために、いくつか書きました。最も簡単なのは、xX0v0Xxが言及するように、プログラム「スクリプト」を使用することです。スクリプトを(引数なしで)呼び出すと、以前のスクリプトの出力が上書きされることがよくありました。そこで、このエイリアスを作成しました。上書きを防ぐだけです。 〜/ tmpディレクトリが必要です。

$ alias scr='script ~/tmp/TypeScript-$(date +%FT%T)'
$ scr
Script started, file is /home/wwalker/tmp/TypeScript-2019-12-05T18:56:31
$

インタラクティブなセッションをキャッチしたいときに最適です。

コマンド(スクリプトまたはバイナリ)の出力をログに記録する場合は、正確な出力を取得するか、各行の前にタイムスタンプを付けて出力を取得します。そこで、次の2つのbash関数を作成しました。

alias iso8601="date +%Y-%m-%dT%H:%M:%S"

justlog(){
  name=$(basename "$1")
  log=~/logs/${name}-$(iso8601)
  "$@" > "$log" 2>&1
}

timelog(){
  name=$(basename "$1")
  log=~/logs/${name}-$(iso8601)
  # https://github.com/wwalker/ilts
  # You could replace ilts with ts
  # /usr/bin/ts %FT%H:%M:%.S
  "$@" |& ilts -S -E > "$log" 2>&1
}

通常どおりにコマンドを実行するだけです。

justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'

または

timelog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'

これにより、次の2つのファイルが作成されました。

wwalker@polonium:~ ✓ $ ls -l ~/logs/run*
-rw-r--r-- 1 wwalker wwalker 10495 2019-12-05 18:21:14.985 /home/wwalker/logs/run-2019-12-05T18:21:13
-rw-r--r-- 1 wwalker wwalker  1694 2019-12-05 18:24:02.878 /home/wwalker/logs/run-2019-12-05T18:24:01
-rw-r--r-- 1 wwalker wwalker  7623 2019-12-05 18:25:07.873 /home/wwalker/logs/run-2019-12-05T18:25:06
-rw-r--r-- 1 wwalker wwalker 10296 2019-12-05 18:34:59.546 /home/wwalker/logs/run-2019-12-05T18:34:57

しかし、まだまだあります!!

Justlogまたはtimelogが作成したログファイルの名前を見つけるためにlsを実行する必要はありませんでした。そこで、さらに3つの関数を追加しました。

newestlog(){
  name=$(basename "$1")
  ls  ~/logs/"${name}"* | tail -1
}

viewlog(){
  name=$(basename "$1")
  view $( newestlog "$name" )
}

lesslog(){
  name=$(basename "$1")
  less $( newestlog "$name" )
}

したがって、コマンドをjustlog(またはtimelog)で実行してから、lesslogまたはviewlogを使用するだけです(これらの人のためにemacsログを作成します)。

justlog run rcn rcn-work\\\\\* 'ps -ef -o lstart,cmd | grep [s]upervisor'
lesslog run

それでおしまい、ls ~/tmp、ファイル名を見つけるためのタブ補完ゲームはありません。 lesslog(またはvimを使用してログを確認する場合はviewlog)を実行するだけです。

ちょっと待って!もっとあります!

「ログファイルでは常にgrepを使用しています」-答えは、あなたが推測したとおり、greplogです。

まず、壊れているすべての800サーバーの/etc/cron.d/atopファイルからテキストを取得します。

justlog fordcrun 'cat /etc/cron.d/atop; md5dum /etc/cron.d/atop'

次に、greplogを使用してホスト名(ファイルの出力の上の行:-)を取得します。

wwalker@polonium:~ ✓ $ greplog fordcrun  -B1 -F "0 0 * * root"
int-salt-01:
    0 0 * * root systemctl restart atop
--
rcn-pg-01:
    0 0 * * root systemctl restart atop
rcn-pg-02:
    0 0 * * root systemctl restart atop
rcn-pg-03:
    0 0 * * root systemctl restart atop
0
Wayne Walker