スクリプト作成_ログ集約+定期削除

<h1>はじめに</h1>
<b>※あくまで個人的備忘録です</b>
サーバ構築の一部で、以下を目的としたスクリプト作成をすることになった。
①OSログやMWのログファイルを1つのサーバに集める（ログ管理サーバとする）
②サーバごとに対象ログファイルは異なる
②収集したログファイルを一定のタイミングで削除



<h1>スクリプトの仕様について</h1>
<b>処理概要図</b>
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639874/90a8f74c-0270-159b-064a-168cb3a94f7a.png)

<table>
<tr><th>①</th><th>sshコマンド経由で対象サーバに対してlsコマンドを発行する</th></tr>
<tr><th>②</th><th>①の出力結果から対象ファイルのパス情報を取得する</th></tr>
<tr><th>③</th><th>②で取得したパス情報を使用し、対象サーバにscpコマンドを発行する</th></tr>
<tr><th>④</th><th>ログ管理サーバに用意した一時保管領域にscp転送したファイルを保管する</th></tr>
<tr><th>⑤</th><th>一時領域に保管したファイルと、既に格納領域に保管されているファイルの更新日を比較する</th></tr>
<tr><th>⑥</th><th>⑤の結果、一時領域に保管したファイルの更新が新しい場合、そのファイルを圧縮状態にした後格納領域に移動する</th></tr>
<tr><th>⑦</th><th>⑤、⑥の処理後に一時領域をフォーマット(不要ファイルの削除)を行う</th></tr>
</table>


<h1>必要なコマンド</h1>
sshコマンドを使い、対象サーバ上でファイル情報の取得(ls)を行い、必要な部分だけ加工して取得する。

```java:sshコマンド
ssh -i SSH鍵ファイル ユーザ@サーバ "ls -ltr 検索対象のファイルパス | tail -1 "| awk '{print $NF}'
# ls -ltr           > l:詳細情報,t:更新時間順に並べる,r:逆順で表示する 
# tail -1           > 末尾のみを対象とする
# awk '{print $NF}' > 実行結果の最終カラム(NF)のみ取得する
```

sccpコマンドで対象ファイルの転送を行う。帯域制御を行うため通信を圧縮し、元ファイルのパーミッションを維持する。

```java:scpコマンド
scp -i SSH鍵ファイル -Cp ユーザ@サーバ:対象ファイル 転送先ディレクトリ
# -C : 通信を圧縮
# -i : コピー元の更新時間とモードを維持
```

```java:if分岐
if [ 条件式 ] ; then
   処理1
else
   処理2
fi
```


<h1>検証して見えた検討事項</h1>
<b>①sshをwhileで繰り返し処理をしたところ、ループせずに正常終了してしまった</b>
sshコマンド実行時に標準入力が切り替わり、ローカルホストからの標準入力を停止し、リモートホストからの標準入力を受け付ける仕様なので、whileではなくforを使用することで解消。

<h1>実際に作ってみた</h1>
<b>ディレクトリ構成</b>
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639874/35eca244-d8ab-7802-448a-abca94991b70.png)

```java:logcollect.sh
#!/bin/bash

# 環境変数設定
TODAY=`date "+%Y%m%d"`
SSH_USER=sshuser
SSH_KEY_FILE=/home/sshuser/.ssh/id_rsa_sshuser
SRC_LOGFILE_LIST=/home/sshuser/conf/logfile.lst
LOGFILE_DIR=/home/sshuser/logs
LOGFILE=${LOGFILE_DIR}/logcollect_${TODAY}.log
TMPDIR=/logs/tmp
ERR_CHECK=0

#----------------------
# 一時領域フォーマット
#----------------------
function tmp_format(){
    rm -f ${TMPDIR}/${SRC_HOST}/*
}

# スクリプトの開始時刻をログ出力
echo `date "+%Y/%m/%d %H:%M:%S"` [INFO] LogCollect Script Start >> ${LOGFILE}

# 実行ユーザのチェック
if [ "$USER" != "${SSH_USER}" ] ; then
	echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] User is Incorrect >> ${LOGFILE}
	echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] LogCollect Script Failed >> ${LOGFILE}
	exit 1	
fi

# リストファイルの所在確認
if [ ! -e ${SRC_LOGFILE_LIST} ] ; then
	echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] ListFile does not exist >> ${LOGFILE}
	echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] LogCollect Script Failed >> ${LOGFILE}
	exit 1
fi
## main
# リストファイルを元に繰り返し処理実行
for line in `cat ${LC_LOGFILE_LIST}`
do
    # リストファイルを読み込み、変数に設定する
    SRC_HOST=`echo ${line} | awk -F ',' '{print $1}'`
    SRC_PATH=`echo ${line} | awk -F ',' '{print $2}'`
    SRC_FILE=`echo ${line} | awk -F ',' '{print $3}'`
    DST_DIR=`echo ${line} | awk -F ',' '{print $4}'`
    ZIPTYPE=`echo ${line} | awk -F ',' '{print $5}'`

    # 一次領域フォーマット
    tmp_format

    # sshによるls実行と実行結果の取得
    SCP_FILE=`ssh -i ${SSH_KEY_FILE} ${SSH_USER}@${SRC_HOST} "ls -ltr ${SRC_PATH}/${SRC_FILE} | tail -1 "| awk '{print $NF}'`

    # scp転送の実行
    scp -i ${SSH_KEY_FILE} -Cp ${SSH_USER}@${SRC_HOST}:${SCP_FILE} ${TMPDIR}/${SRC_HOST}
    if [ "$?" -ne 0 ] ; then
	echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] SCP Failed ${SRC_HOST} ${SCP_FILE} >> ${LOGFILE}
	ERR_CHECK=1
    fi
    
    # 一時領域に保存するファイル
    NEW_FILE=`echo ${SCP_FILE} | awk -F '/' '{print $NF}'`

    # 格納先の最新ファイル情報取得
    OLD_FILE=`ls -ltr ${DST_DIR}/${SRC_FILE}`
    if [ "$?" -ne 0 ] ; then
        # 格納先に比較対象ファイルが存在しない場合
        OLD_FILE=`echo 0`
    else
        OLD_FILE=`ls -ltr ${DST_DIR}/${SRC_FILE} | tail -1 | awk '{print $NF}'`    
    fi

    # タイムスタンプ比較
    test ${TMPDIR}/${SRC_HOST}/${NEW_FILE} -nt ${OLD_FILE}
    #  一時領域のファイルが最新の場合
    if [ "$?" -eq 0 ] ; then
        if [ "${ZIPTYPE}" -eq 1 ] ; then
              # 非圧縮ファイルを圧縮
              gzip ${TMPDIR}/${SRC_HOST}/${NEW_FILE}
        fi
        # 対象ファイルを格納領域に移動
        mv ${TMPDIR}/${SRC_HOST}/${NEW_FILE}* ${DST_DIR}/
        if [ "$?" -ne 0 ] ; then
	   echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] Move Failed ${SRC_HOST} ${TMPDIR}/${SRC_HOST}/${NEW_FILE}* >> ${LOGFILE}
	   ERR_CHECK=1
        fi
    fi

    # 一時領域の不要ファイルを削除
    tmp_format
done

if [ "$ERR_CHECK" -eq 1 ] ; then
   # scp,mv処理でエラーが発生した場合
   echo `date "+%Y/%m/%d %H:%M:%S"` [ERROR] LogCollect Script Failed >> ${LOGFILE}
   exit 1
fi

# 作成後30日経過しているスクリプトログを削除
find ${LOGFILE_DIR} -mtime +30 -type f | xargs rm -f

# スクリプトの終了時刻をログ出力
echo `date "+%Y/%m/%d %H:%M:%S"` [INFO] LogCollect Script End >> ${LOGFILE}

exit 0

```

```java:logfile.lst
test-server,/var/log,cron-*,/log/System,1
test-server,/var/log,spooler-*,/log/System,1
test-server,/var/log,secure-*,/log/System,1
test-server,/var/log,messages-*,/log/System,1
test-server,/var/log,maillog-*,/log/System,1
test-server,/var/log/httpd,access_log-*,/log/System,0
test-server,/var/log/httpd,error_log-*,/log/System,0
```

```java:365format.sh
#!/bin/bash

# 環境変数の設定
TODAY=`date "+%Y%m%d"`
TARGET_DIR=/logs/logcollect/
LOGFILE=/home/sshuser/logs/logdelete_${TODAY}.log

#--------------------------
# スクリプトの開始時刻をログ出力
echo `date "+%Y/%m/%d %H:%M:%S"` [INFO] LogDelete Script Start >> ${LOGFILE}

# 最終更新日付が365日より古いファイルを検索し、削除する
find ${TARGET_DIR} -mtime +365 -type f | xargs rm -f

# スクリプトの終了時刻をログ出力
echo `date "+%Y/%m/%d %H:%M:%S"` [INFO] LogDelete Script End >> ${LOGFILE}

exit 0

```

<h1>ログ出力例</h1>
```java:logcollect.shの場合
# 正常終了
2021/04/28 16:12:50 [INFO] LogCollect Script Start
2021/04/28 16:12:52 [INFO] LogCollect Script End

# scp処理に失敗している場合
2021/04/28 16:18:00 [INFO] LogCollect Script Start
2021/04/28 16:18:01 [ERROR] SCP Failed test-server /var/log/messages-20210425
2021/04/28 16:18:01 [ERROR] SCP Failed test-server /home/sshuser/tmp/test20210428.gz
2021/04/28 16:18:01 [ERROR] LogCollect Script Failed

# mv処理が失敗している場合
2021/04/28 14:22:05 [INFO] LogCollect Script Start
2021/04/28 14:22:06 [ERROR] Move Failed test-server /logs/tmp/test-server/messages-20210425.gz
2021/04/28 14:22:06 [ERROR] Move Failed test-server /logs/tmp/test-server/test20210428.gz
2021/04/28 14:22:06 [ERROR] LogCollect Script Failed

# 指定されたユーザ以外で実行された場合
2021/04/28 14:12:31 [INFO] LogCollect Script Start
2021/04/28 14:12:31 [ERROR] User is Incorrect
2021/04/28 14:12:31 [ERROR] LogCollect Script Failed

# リストファイルが存在していない場合
2021/04/28 14:15:41 [INFO] LogCollect Script Start
2021/04/28 14:15:41 [ERROR] ListFile does not exist
2021/04/28 14:15:41 [ERROR] LogCollect Script Failed

```

<h1>おわりに</h1>
内部事情はさておき、久々に仕事らしい仕事ができて非常に楽しい時間だった。
ただ、エラーに対する処理を実装することができなかったことが悔やまれる。また、自宅検証で成功しただけなので、実環境では更に改良が必要になると考えている。（実機調査の時点で問題を確認しているので１つや２つで済まないと思われる。。。。）
ただ、引継ぎしてくれる先輩方はハイスペックな人なので瞬殺してくれるだろう。

定期削除は日付情報を見て削除するだけなので説明するまでもないと思ってまとめてません。


<h1>参考</h1>
整理中。。。