商用プロジェクトは1から100に至るまで、すべてエビデンスの世界です。
システムに障害は付きものですので、障害報告を行う際には、必ず「1次報告」「中間報告」「最終報告」をエビデンス付きで提出しなければなりません。
シェルスクリプトを作成する場合、予め障害が発生することを念頭に入れて設計を進めていきます。
基盤として挙げるなら「CPU」や「メモリ」、「ディスク」など、リソース周りの障害は約束されていることです。
では、そのエビデンスとして必要になるものは何でしょう?
エビデンスとして必ず必要になるものとは、ずばり「ログ」です。
実行から障害に至るまでの経緯を時系列にログとして記録していなければ、対象方法が見つかりません。
「ログ出力なくしてシステム開発にあらず!」
システムの成功は、すべて「ログの出力の有無」にかかっているといっても過言ではありません!
そこで、ちょっとしたログ出力のロジックを作成してみることにしました。
ログ出力機能を実装する
前提
Beエンジニアでシェルスクリプトを実行する環境は下記の通りとします。
実行環境
BASE_DIR(任意のディレクトリ)
- scripts
- bin(実行スクリプト格納領域)
- func.sh (実行ファイル)
- com(共通スクリプト格納領域)
- comFunc.shrc(共通関数定義ファイル)
- etc(設定ファイル等の格納領域)
- message.conf(メッセージ定義ファイル)
- log(スクリプト実行ログの格納領域)
- スクリプト名.log
- tmp(テンポラリ領域)
- rep(レポート出力領域)
- bin(実行スクリプト格納領域)
ログは、すべて「scripts/log/」配下へ出力されるようにロジックを作成します。
ログ出力関数の作成
この記事で作成するログ出力ロジックの対象は下記を想定しています。
標準出力ログを${LOG_PATH}/<スクリプト名>.logへ出力します。
ログの開始関数
実行シェルスクリプトが起動する際に呼び出され「SCRIPT:[ func.sh ] PID:[ 11223 ] STARTED LOG.」メッセージを出力し、実行シェルが起動したことを通知します。
1 2 3 4 5 6 7 8 9 10 |
# -------------------------------------------------- # Start log function. # -------------------------------------------------- # return N/A # -------------------------------------------------- startLog() { exec >> "${LOG_PATH}/${SCRIPT_NAME%%.*}.log" 2>&1 logOut "SCRIPT:[ ${SCRIPT_NAME} ] PID:[ $$ ] STARTED LOG." } |
1-5行目:コメント
6行目:関数「startLog ()」の宣言
8行目:ログファイルの作成、及び追記標準エラーと標準出力を追記
9行目:開始メッセージを出力
ログの終了関数
実行シェルスクリプトが終了する間際に呼び出され「SCRIPT:[ func.sh ] PID:[ ●●●● ] ENDED LOG with NOMAL.」メッセージを出力し、実行シェルが終了したことを通知します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# -------------------------------------------------- # End log function. # -------------------------------------------------- # param exit code # return N/A # -------------------------------------------------- exitLog() { rc=${rc:-${JOB_ER}} case "${rc}" in 0) rtn="NOMAL." ;; 1) rtn="WARNING." ;; 2) rtn="ERROR." ;; *) rtn="FATAL." ;; esac logOut "SCRIPT:[ ${SCRIPT_NAME} ] PID:[ $$ ] ENDED LOG with ${rtn}" exit ${rc} } |
1-6行目:コメント
7行目:関数「exitLog ()」の宣言
8行目:終了ステータスを引数で受け取る(※ 未設定の場合は2を挿入)
9-14行目:「終了ステータス」の選択
※ 終了ステータス毎に出力メッセージを変更(Case文)
15行目:終了ログのメッセージ出力(logOut)
16行目:シェルスクリプトを終了する(exit文)
ログ出力関数
実行シェル起動後に、任意のタイミングでメッセージを出力します。
1 2 3 4 5 6 7 8 9 |
# -------------------------------------------------- # Write log message. # -------------------------------------------------- # param message # return N/A # -------------------------------------------------- logOut() { printf "%s [ %-5s] %s\n" "`date +"%Y-%m-%d %H:%M:%S"`" "${scope}" "${*}" } |
1-6行目:コメント
7行目:関数「logOut ()」の宣言
8行目:ログファイルへ出力要素を整形しながら出力する
※ 「printf」コマンドは、データを整形して表示するコマンドです。ここでは、実行領域「var」「func」「pre」「main」「post」をログ出量時に”[ ]”で括っています。
共通関数定義ファイルへの実装
実際に「comFunc.sh(共通関数定義ファイル)」へ実装すると下記のようになります。
[ <BASE_DIR>/scripts/com/comFunc.shrc ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# Common function definition file #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ # # comFunc.shrc ver.1.0.0 2020.04.24 # # Usage: # -------- # # Description: # 各種機能より呼び出される共通機能を実装する。 # # 設計書 # none # #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ # ------------------------------------------------------------ # Constant declaration(定数の宣言領域) # ------------------------------------------------------------ BASE_PATH=${BASE_PATH:-"/root/scripts"} COM_PATH=${BASE_PATH}/com ETC_PATH=${BASE_PATH}/etc LOG_PATH=${BASE_PATH}/log TMP_PATH=${BASE_PATH}/tmp REP_PATH=${BASE_PATH}/rep SCRIPT_NAME=`basename $0` JOB_OK=0 JOB_WR=1 JOB_ER=2 MSG_CONF=${ETC_PATH}/message.conf # ------------------------------------------------------------ # Functions (関数を記述する領域) # ------------------------------------------------------------ # -------------------------------------------------- # Devider. # -------------------------------------------------- # return N/A # -------------------------------------------------- line (){ echo -e "\\n ------------" echo -e " ▼ ${1}" } # -------------------------------------------------- # Start log function. # -------------------------------------------------- # return N/A # -------------------------------------------------- startLog() { exec >> "${LOG_PATH}/${SCRIPT_NAME%%.*}.log" 2>&1 logOut "SCRIPT:[ ${SCRIPT_NAME} ] PID:[ $$ ] STARTED LOG." } # -------------------------------------------------- # End log function. # -------------------------------------------------- # param1 exit code # return N/A # -------------------------------------------------- exitLog() { rc=${rc:-${JOB_ER}} case "${rc}" in 0) rtn="NOMAL." ;; 1) rtn="WARNING." ;; 2) rtn="ERROR." ;; *) rtn="FATAL." ;; esac logOut "SCRIPT:[ ${SCRIPT_NAME} ] PID:[ $$ ] ENDED LOG with ${rtn}" exit ${rc} } # -------------------------------------------------- # Write log message. # -------------------------------------------------- # param messages # return N/A # -------------------------------------------------- logOut() { printf "%s [ %-5s] %s\n" "`date +"%Y-%m-%d %H:%M:%S"`" "${scope}" "${*}" } |
呼び出し側のシェルスクリプト作成
「func.sh(実行シェルスクリプト)」へ実装すると下記の様になります。
※ 「func.sh」は「template.sh」を基に作成しています。
[ template.sh ]は、【Shellの基礎知識】で解説しています。下記のリンクページを参照してください。
[ <BASE_DIR>/scripts/bin/func.sh ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#!/bin/sh #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ # # ver.1.0.0 yyyy.mm.dd # # Usage: # # sh /root/scripts/bin/func.sh # # Description: # 例題コマンド実行スクリプト # # 設計書 # なし # #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ . `dirname $0`/../com/comFunc.shrc # ---------------------------------------------------------- # variables (変数の宣言領域) # ---------------------------------------------------------- scope="var" rc=${JOB_ER} # ---------------------------------------------------------- # functions (関数を記述する領域) # ---------------------------------------------------------- scope="func" # -------------------------------------------------- # devider. # -------------------------------------------------- # return N/A # -------------------------------------------------- line (){ echo -e "\\n ------------" echo -e " ▼ ${1}" } # ---------------------------------------------------------- # pre-process (事前処理ロジックを記述する領域) # ---------------------------------------------------------- scope="pre" startLog 👈 ログ開始関数呼び出し # ---------------------------------------------------------- # main-process (メインロジックを記述する領域) # ---------------------------------------------------------- scope="main" # ログ出力実行 logOut "このメッセージは[ ${SCRIPT_NAME} ]の[ ${scope} ]領域から出力されています。" if [ $? -eq ${JOB_OK} ]; then rc=${JOB_OK} fi # ---------------------------------------------------------- # post-process (事後処理ロジックを記述する領域) # ---------------------------------------------------------- scope="post" exitLog $rc 👈 ログ終了関数呼び出し |
ログ出力関数の実行
では、実際に「func.sh」を実行してみましょう。
# 実行シェルスクリプトからログの出力テスト
$ <BASE_DIR>/scripts/bin/func.sh
1 2 3 4 5 6 7 8 9 10 11 |
[root@CentOS7 bin]# sh func.sh [root@CentOS7 bin]# cd ../log [root@CentOS7 log]# pwd /root/scripts/log [root@CentOS7 log]# ls -l -rw-r--r--. 1 root root 488 4月 25 12:49 func.log [root@CentOS7 log]# cat func.log 2020-04-25 12:49:10 [ pre ] SCRIPT:[ func.sh ] PID:[ 11223 ] STARTED LOG. 2020-04-25 12:49:10 [ main ] このメッセージは[ func.sh ]の[ main ]領域から出力されています。 2020-04-25 12:49:10 [ post ] SCRIPT:[ func.sh ] PID:[ 11223 ] ENDED LOG with NOMAL. [root@CentOS7 log]# |
「<BASE_DIR>/scripts/log/」配下へ「func.log」が作成され、その中に標準出力がログとして記載されているのが分かります。
この記事では、簡単なログの出力ロジックの作成までを行いました。ただし、ここで作成したシェル出力ロジックには、あくまでも最低限必要なログの出力機能しか盛り込んでいません!
実際に現場で使用するには、さらに「ログレベルの設定(FATAL | ERROR | WARN | INFO | DEBUG | TRACE)」や「出力文字コードの設定(UTF-8 | SJIS)」などの改良が必要となることに注意してください。
※ 本スクリプト利用により発生した利用者の損害全てに対し、いかなる責任をも負わないものとし、損害賠償をする一切の義務はないものとします。