私は次の問題を抱えています:
Stderrとstdoutへの出力を継続的に生成するアプリケーションがあります。このアプリケーションの出力はログファイルにキャプチャされます(アプリは次のようにリダイレクトされます:&> log.txt
)。このためにファイルへの適切なログを生成するオプションがありません。
これで、1時間ごとに実行される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
を使用すると、変更できない可能性のあるメカニズム全体が舞台裏にあります。
ありがとう。
さて、ここにインスピレーションを得たアイデアがあります http://en.wikibooks.org/wiki/Bourne_Shell_Scripting/Files_and_streams
名前付きパイプを作成します。
mkfifo /dev/mypipe
stdoutとstderrを名前付きパイプにリダイレクトします。
&> /dev/mypipe
mypipeからファイルに読み込む:
cat < /dev/mypipe > /var/log/log.txt &
ログローテーションが必要な場合は、猫を殺し、ログを回転させて、猫を再起動します。
今、私はこれをテストしていません。どうなるか教えてください。
注:名前付きパイプには、/ var/tmp/pipe1、/ var/log/pipe、/ tmp/abracadabraなどの任意の名前を付けることができます。起動後、logging-scriptが実行される前にパイプを再作成してください。
または、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:レコーダーを稼働させます:
名前付きパイプを作成します。
mkfifo $PIPENAME
アプリケーションのSTDOUTとSTDERRを名前付きパイプにリダイレクトします。
...things... &> $PIPENAME
レコーダを起動します。
/path/to/recorder.sh < $PIPENAME &
上記をNohup
して、ログアウト後も存続させることができます。
完了!
ステップ3:ログローテーションが必要な場合は、レコーダーを一時停止します。
touch /path/to/log/file.semaphore
mv /path/to/log/file /path/to/archive/of/log/file
rm /path/to/log/file.semaphore
上記の手順を独自のスクリプトに組み込むことをお勧めします。 2行目を、使用するログローテーション方式に自由に変更してください。
注: Cプログラミングが便利な場合は、recorder.sh
の機能を実行するための短いCプログラムを作成することをお勧めします。コンパイルされたCプログラムは、Nohupでデタッチされたbashスクリプトよりも確かに軽量です。
注2: David Newcombはコメントで役立つ警告を提供しました:レコーダーが実行されていない間、パイプに書き込みますブロックし、プログラムが予期せず失敗する可能性があります。レコーダーができるだけ短時間ダウン(または回転)していることを確認してください。
したがって、回転が本当にすばやく発生することを確認できる場合は、sleep
(整数値のみを受け入れる組み込みコマンド)を/bin/sleep
(プログラム)に置き換えることができます。 float値を受け入れます)、スリープ期間を0.5
以下に設定します。
まず第一に、ここで四角いホイールを再発明するべきではありません。あなたの仲間はおそらく反対しています /etc/logrotate.d/
のすべてのスクリプトに自動的に適用される毎日のスケジュールでログをローテーションする -これはスクリプトを別の場所に配置することで回避できます。
さて、 ログローテーションへの標準的なアプローチ (logrotate
に実装されています)は、他のどの機能でも同様に実装できます。例えば。 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>
最後の項目は、SIGHUPまたは(それほど頻繁ではありませんが)SIGUSR1を送信し、対応するファイル記述子または変数を置き換えるシグナルハンドラーをデーモンに含めることによって行われます。このように、スイッチはアトミックであるため、ロギングの可用性が中断されることはありません。 bashでは、これは次のようになります。
trap { exec &>"$LOGFILE"; } HUP
もう1つのアプローチは、書き込みプログラム自体が、書き込みプログラムとローテーションを実行するたびにログサイズを追跡するようにすることです。これにより、書き込み可能な場所とローテーションロジックのオプションが、プログラム自体がサポートするものに制限されます。ただし、自己完結型のソリューションであり、スケジュールではなく書き込みごとにログサイズをチェックするという利点があります。多くの言語の標準ライブラリにはそのような機能があります。ドロップインソリューションとして、これはApacheの rotatelogs
に実装されています。
<your_program> 2>&1 | rotatelogs <opts> <logfile> <rotation_criteria>
rotatelogs
( ここにドキュメント )を活用できます。このユーティリティは、スクリプトのstdoutをログファイルから切り離し、透過的な方法でローテーションを管理します。例えば:
your_script.sh | rotatelogs /var/log/your.log 100M
100Mに達すると、出力ファイルが自動的に回転します(時間間隔に基づいて回転するように構成できます)。
私は logrotee 今週末に書いた。 @JdeBPの multilog
についての素晴らしい答え を以前に読んだことがあるなら、私はおそらく読まないでしょう。
私はそれが軽量で、次のように出力チャンクをbzip2できることに焦点を合わせました。
verbosecommand | logrotee \
--compress "bzip2 {}" --compress-suffix .bz2 \
/var/log/verbosecommand.log
ただし、まだ実行してテストする必要のあることがたくさんあります。
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