前提
・AWS CLIがインストールされて、初期設定が完了できていること
・実行OS:RedhatLinux 8.2
・jqがインストールされていること
概要
あるPJで毎日、前日のログをローカルに取得して、確認する要望があって、検証してみました。
ハマったこと
ログがあるのに取得できない。
原因:get-log-eventsのデフォルト制限
1MBまたは10,000のログイベントを超えると、追加のオプションが必要です。
参考[1]から翻訳した内容:
デフォルトでは、「get-log-events」は 1MB の応答サイズに収まるできるだけ多くのログイベントを返します (最大10,000のログイベント)。後続の呼び出しでトークンの 1 つを指定することで、追加のログ イベントを取得できます。
解決方法
EXT_TOKEN数分でループすることで解決できました。
補足:–start-from-headオプションの必要になるので、追加しています。
参考[1]から抜粋:
ーーーーーーーーーーーーーーーー
–start-from-head | –no-start-from-head (boolean)
If the value is true, the earliest log events are returned first. If the value is false, the latest log events are returned first. The default value is false.
If you are using nextToken in this operation, you must specify true for startFromHead .
ーーーーーーーーーーーーーーーー
コードから抜粋:
NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextForwardToken')
RET_CODE=${?}
if [ ${RET_CODE} -ge 1 ]; then
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKEN取得に失敗しました。"
END_FUNC 9
else
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKENを取得しました。"
echo "NEXT_TOKEN:" $NEXT_TOKEN
fi
# NEXT_TOKEN数分でループ
while [ -n "${NEXT_TOKEN}" ]; do
cat "${RESPONSE_FILE_NAME}" | jq -r '.events[] | [(.timestamp/1000+32400 | strftime("%Y-%m-%d %H:%M:%S")), .message] |@tsv' | sed 's/\\n$//' >> $OUT_FILE
aws logs get-log-events --log-group-name "${LOGGROUP}" --log-stream-name "${LOGSTREAM}" --start-from-head --next-token "${NEXT_TOKEN}" > "${RESPONSE_FILE_NAME}"
NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextForwardToken')
RET_CODE=${?}
if [ ${RET_CODE} -ge 1 ]; then
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKEN取得に失敗しました。"
END_FUNC 9
else
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKENを取得しました。"
echo "NEXT_TOKEN:" $NEXT_TOKEN
fi
if [[ $(cat "${RESPONSE_FILE_NAME}" | jq -e '.events == []') == "true" ]]; then
break
fi
done
日付が跨る箇所のログ数が正しく取得できていない。
原因:ロググループのタイムスタンプとEC2のOS上にあるログファイルの時間が少しずれていて、ロググループのタイムスタンプでgrepしたため。
解決方法
EC2のOS上にあるログファイルの時間でgrepすることで解決しました。
修正前のgrepキー:
「2021-05-29」
正しく取得できませんでした。
修正後grepキー:
「May 29」
正しく取得しました。
シェルの内容
ファイル名:DownloadCloudwatchlogsByOneDayAgo.sh
#!/bin/bash
############################################################################
## 事前準備
# 実行ユーザ:root
# 引数例:ロググループの名前
# messages
############################################################################
#環境変数
NORMAL_END=0
ABNORMAL_END=9
############################################################################
# END_FUNC
# exit コードによる後処理
# ${1} = exit コード
############################################################################
END_FUNC()
{
case ${1} in
${NORMAL_END})
;;
${ABNORMAL_END})
;;
esac
exit ${1}
}
#シェルの引数チェック
if [ $# -ne 1 ]; then
echo "usage:" "`basename $0` <aws cloudwatchlogsのロググループの名前>"
END_FUNC ${ABNORMAL_END}
fi
SYS_DATE_CURRENT=$(TZ=UTC-9 date -d '0 days ago' '+%Y-%m-%d')
SYS_DATE_CURRENT_YYYYMMDD_HHMMSS_3N=$(TZ=UTC-9 date +%Y%m%d_%H%M%S_%3N)
SYS_DATE_ONE_DAYS_AGO=$(TZ=UTC-9 date -d '1 days ago' '+%Y-%m-%d')
SYS_DATE_TWO_DAYS_AGO=$(TZ=UTC-9 date -d '2 days ago' '+%Y-%m-%d')
GREP_DATE=$(env LANG=en_US.UTF-8 TZ=UTC-9 date -d '1 days ago' '+%b %d')
LOG_STREAMS_DATE_FROM=$(echo "${SYS_DATE_TWO_DAYS_AGO}" '23:59:00')
LOG_STREAMS_DATE_TO=$(echo "${SYS_DATE_CURRENT}" '00:00:01')
LOG_GROUP_NAME="${1}"
OUT_LOG_GROUP_FILENAME="${1}"_ALL.log
OUT_LOG_GROUP_FILENAME_BY_DATE="${1}"_ONE_DAY_"${SYS_DATE_ONE_DAYS_AGO}".log
# CloudwatchLogsのロググループを存在しているかをチェックする(messagesで始まるロググループをチェック)
LOG_GROUP_NAME_NUM=$(aws logs describe-log-groups --log-group-name-prefix messages | jq '.logGroups[] | .logGroupName' -r | grep -e "${LOG_GROUP_NAME}" | wc -l)
if [ "${LOG_GROUP_NAME_NUM}" -ge 1 ]; then
echo "info:ロググループ["${LOG_GROUP_NAME}"]が存在しているので、後続処理を行う。"
else
echo "info:ロググループ["${LOG_GROUP_NAME}"]が存在していないため、終了します。"
END_FUNC 9
fi
############################################################################
# DOWNLOAD_CLOUDWATCHLOGS
# CLOUDWATCHLOGSをローカルにダウンロードする
# ${1} = ロググループの名前
# ${2} = 開始時間(YYYY-MM-DD hh:mm:ss)
# ${3} = 終了時間(YYYY-MM-DD hh:mm:ss)
# ${4} = 出力先のログファイル名
############################################################################
DOWNLOAD_CLOUDWATCHLOGS()
{
if [ $# -ne 4 ]; then
echo "usage:" "`basename $0` <ロググループの名前> <開始時間(YYYY-MM-DD hh:mm:ss)> <終了時間(YYYY-MM-DD hh:mm:ss)> <出力先のログファイル名>"
END_FUNC 9
fi
# 引数
LOGGROUP="${1}"
DT_FROM="${2}"
DT_TO="${3}"
OUT_FILE="${4}"
# Unixtimeに変換
UT_FROM=`date -d "${DT_FROM}" +%s`000
UT_TO=`date -d "${DT_TO}" +%s`000
# jqのselect条件
COND="(($UT_FROM <= .firstEventTimestamp) and (.firstEventTimestamp <= $UT_TO))"
COND="$COND or (($UT_FROM <= .lastEventTimestamp) and (.lastEventTimestamp <= $UT_TO))"
COND="$COND or ((.firstEventTimestamp <= $UT_FROM) and ($UT_TO <= .lastEventTimestamp))"
echo "COND:" $COND
RET_CODE=${?}
if [ ${RET_CODE} -ge 1 ]; then
END_FUNC 9
fi
TEMP_OUT_LINES=$(aws logs describe-log-streams --log-group-name $LOGGROUP --order-by LastEventTime --no-descending | jq -r ".logStreams[] | select($COND) | .logStreamName" | wc -l)
if [ ${TEMP_OUT_LINES} -ge 1 ]; then
echo "info:ロググループ["${LOG_GROUP_NAME}"]の対象ログストリームが存在しているので、後続処理を行う。"
# LOGSTREAM数分でループ
aws logs describe-log-streams --log-group-name $LOGGROUP --order-by LastEventTime --no-descending | jq -r ".logStreams[] | select($COND) | .logStreamName" | while read LOGSTREAM; do
echo "LOGSTREAM:" $LOGSTREAM
RESPONSE_FILE_NAME="RESPONSE"_$SYS_DATE_CURRENT_YYYYMMDD_HHMMSS_3N.log
aws logs get-log-events --log-group-name $LOGGROUP --log-stream-name $LOGSTREAM --start-from-head > "${RESPONSE_FILE_NAME}"
NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextForwardToken')
RET_CODE=${?}
if [ ${RET_CODE} -ge 1 ]; then
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKEN取得に失敗しました。"
END_FUNC 9
else
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKENを取得しました。"
echo "NEXT_TOKEN:" $NEXT_TOKEN
fi
# NEXT_TOKEN数分でループ
while [ -n "${NEXT_TOKEN}" ]; do
cat "${RESPONSE_FILE_NAME}" | jq -r '.events[] | [(.timestamp/1000+32400 | strftime("%Y-%m-%d %H:%M:%S")), .message] |@tsv' | sed 's/\\n$//' >> $OUT_FILE
aws logs get-log-events --log-group-name "${LOGGROUP}" --log-stream-name "${LOGSTREAM}" --start-from-head --next-token "${NEXT_TOKEN}" > "${RESPONSE_FILE_NAME}"
NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextForwardToken')
RET_CODE=${?}
if [ ${RET_CODE} -ge 1 ]; then
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKEN取得に失敗しました。"
END_FUNC 9
else
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームのNEXT_TOKENを取得しました。"
echo "NEXT_TOKEN:" $NEXT_TOKEN
fi
if [[ $(cat "${RESPONSE_FILE_NAME}" | jq -e '.events == []') == "true" ]]; then
break
fi
done
done
else
echo "info:ロググループ["${LOGGROUP}"]の対象ログストリームが存在していないため、終了します。"
END_FUNC 9
fi
}
############################################################################
# ログ取得処理(期間中全量)
############################################################################
echo "LOG_STREAMS_DATE_FROM:" $LOG_STREAMS_DATE_FROM
echo "LOG_STREAMS_DATE_TO:" $LOG_STREAMS_DATE_TO
echo "LOG_FILE_DATE:" $SYS_DATE_ONE_DAYS_AGO
echo "GREP_DATE:" $GREP_DATE
DOWNLOAD_CLOUDWATCHLOGS "${LOG_GROUP_NAME}" "${LOG_STREAMS_DATE_FROM}" "${LOG_STREAMS_DATE_TO}" "${OUT_LOG_GROUP_FILENAME}"
############################################################################
# 対象日付でGrep
############################################################################
cat "${OUT_LOG_GROUP_FILENAME}" | grep -e "${GREP_DATE}" > "${OUT_LOG_GROUP_FILENAME_BY_DATE}"
############################################################################
# 終了処理:戻り値を返却
############################################################################
if [ "${RET_CODE}" == "9" ]; then
END_FUNC "${RET_CODE}"
elif [ "${RET_CODE}" == "0" ]; then
END_FUNC "${RET_CODE}"
fi
実行結果
ロググループ「messages」のログを取得してみます。
sh DownloadCloudwatchlogs.sh messages
info:ロググループ[messages]が存在しているので、後続処理を行う。
LOG_STREAMS_DATE_FROM: 2021-05-28 23:59:00
LOG_STREAMS_DATE_TO: 2021-05-30 00:00:01
LOG_FILE_DATE: 2021-05-29
GREP_DATE: May 29
COND: ((1622246340000 <= .firstEventTimestamp) and (.firstEventTimestamp <= 1622332801000)) or ((1622246340000 <= .lastEventTimestamp) and (.lastEventTimestamp <= 1622332801000)) or ((.firstEventTimestamp <= 1622246340000) and (1622332801000 <= .lastEventTimestamp))
info:ロググループ[messages]の対象ログストリームが存在しているので、後続処理を行う。
LOGSTREAM: i-064c12df5219b073a
info:ロググループ[messages]の対象ログストリームのNEXT_TOKENを取得しました。
NEXT_TOKEN: f/36180115725620556155173784387743061284168486914523070494
info:ロググループ[messages]の対象ログストリームのNEXT_TOKENを取得しました。
NEXT_TOKEN: f/36180115725620556155173784387743061284168486914523070494
出力ファイル名:messages_ONE_DAY_2021-05-29.log
ファイルの内容を割愛しますが、「May 29」でgrepした結果を取得しています。
参考
[1]
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/get-log-events.html