前提

・AWS CLIがインストールされて、初期設定が完了できていること
・jqがインストールされていること
・実行OS:Ubuntu 20.04 On Windows

背景

同じログストリームにログがたまり続ける場合、-start-from-headを使うと、かなり時間がかかります。
この場合、最新ログイベントから次の最新ログイベントの順番で取得すると、速くなります。

・nextForwardToken
説明:最古のログから順番にログを取得する場合に利用する。–start-from-headを使う
nextForwardTokenを使った記事

・nextBackwardToken
説明:最新のログから順番にログを取得する場合に利用する。–no-start-from-headを使う

シェル内容

VPCFlowLogsを例にシェルを作成してみました。

ファイル名:Get_VPCFlowLogs_OneDayAgo_EveryLogStream.sh

# !/bin/bash

NORMAL_END=0
ABNORMAL_END=1

# 終了処理
END_FUNC()
{
    case ${1} in
        ${NORMAL_END})
        # 正常(返却値:0)
        echo "=====正常終了しました。===="
        ;;
        ${ABNORMAL_END})
        # 異常
        echo "=====異常終了しました。===="
        ;;
    esac
    exit ${1}
}

# ログ出力先
EXE_DIR=/mnt/c/AWSLogs/LOG

if [ ! -d $EXE_DIR ]; then
    # 存在しない場合、作成する
    mkdir -m 777 $EXE_DIR
fi

# ログ日付(CloudwatchlogsのLocalタイムスタンプ(日本時間))
LOG_DATE=$(TZ=UTC-9 date -d '1 days ago' '+%Y-%m-%d')

# ログ日付の前の日(CloudwatchlogsのLocalタイムスタンプ(日本時間))
LOG_DATE_ONE_DAY_AGO=$(TZ=UTC-9 date -d "$LOG_DATE 1 days ago" '+%Y-%m-%d')

# ログ日付の次の日(CloudwatchlogsのLocalタイムスタンプ(日本時間))
LOG_DATE_ONE_DAY_AFTER=$(TZ=UTC-9 date -d "$LOG_DATE 1 day" '+%Y-%m-%d')

# シェル実行日付
SYS_DATE_CURRENT=$LOG_DATE_ONE_DAY_AFTER

# CloudWatchLogsのLasteEventTimeFrom
LOG_STREAMS_DATE_FROM=$(echo "${LOG_DATE_ONE_DAY_AGO}" '23:59:00')
# CloudWatchLogsのLasteEventTimeTo
LOG_STREAMS_DATE_TO=$(echo "${SYS_DATE_CURRENT}" '23:59:00')

# CloudWatchLogsのLasteEventTimeFrom(Unixtime)
UT_FROM=`date -d "$LOG_STREAMS_DATE_FROM" +%s`000
# CloudWatchLogsのLasteEventTimeTo(Unixtime)
UT_TO=`date -d "$LOG_STREAMS_DATE_TO" +%s`000

# 標準出力(確認用)
echo "LOG_DATE:" $LOG_DATE
echo "LOG_DATE_ONE_DAY_AGO:" $LOG_DATE_ONE_DAY_AGO
echo "LOG_DATE_ONE_DAY_AFTER:" $LOG_DATE_ONE_DAY_AFTER
echo "LOG_STREAMS_DATE_FROM:" $LOG_STREAMS_DATE_FROM
echo "LOG_STREAMS_DATE_TO:" $LOG_STREAMS_DATE_TO
echo "UT_FROM:" $UT_FROM
echo "UT_TO:" $UT_TO

LOG_GROUP_NAME="KbyDev3-Tyo-Clwlog-VPCFlowLogs"
OUT_LOG_GROUP_FILENAME=$EXE_DIR/"${1}"_ALL.log

# 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))"

# 全ロググループ
aws logs describe-log-groups | jq '.logGroups[] | .logGroupName' -r | less > $EXE_DIR/LOG_GROUP_ALL.txt

# 対象ロググループ
grep -e $LOG_GROUP_NAME $EXE_DIR/LOG_GROUP_ALL.txt > $EXE_DIR/LOG_GROUP.txt

LOGGROUP_cat=`cat $EXE_DIR/LOG_GROUP.txt`

while read TEMP_OUT_LINES
do
    echo "ロググループ名:"$TEMP_OUT_LINES
    aws logs describe-log-streams --log-group-name $TEMP_OUT_LINES --order-by LastEventTime --no-descending | jq -r ".logStreams[] | select($COND) | .logStreamName" | while read LOGSTREAM; do
        # ログストリームのログレコードを、タイムスタンプとメッセージのタブ区切りに整形して出力
        if [ ! -d $EXE_DIR/${TEMP_OUT_LINES##/*/} ]; then
            # 存在しない場合、作成する
            mkdir -m 777 $EXE_DIR//${TEMP_OUT_LINES##/*/}
        fi

        RESPONSE_FILE_NAME="$EXE_DIR/RESPONSE.log"
        aws logs get-log-events --log-group-name $TEMP_OUT_LINES --log-stream-name $LOGSTREAM --no-start-from-head > "${RESPONSE_FILE_NAME}"

        echo "ロググループ名:"$TEMP_OUT_LINES "ログストリーム名:"$LOGSTREAM

        NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextBackwardToken')
        RET_CODE=${?}
        if [ ${RET_CODE} -ge 1 ]; then
            echo "info:対象ログストリーム["${LOGSTREAM}"]のNEXT_TOKEN取得に失敗しました。"
            END_FUNC 1
        else
            echo "info:対象ログストリーム["${LOGSTREAM}"]の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$//' >> $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL.log
            aws logs get-log-events --log-group-name "${TEMP_OUT_LINES}" --log-stream-name "${LOGSTREAM}" --no-start-from-head --next-token "${NEXT_TOKEN}" > "${RESPONSE_FILE_NAME}"
            NEXT_TOKEN=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.nextBackwardToken')
            RET_CODE=${?}
            if [ ${RET_CODE} -ge 1 ]; then
                echo "info:対象ログストリーム["${LOGSTREAM}"]のNEXT_TOKEN取得に失敗しました。"
                END_FUNC 1
            else
                echo "info:対象ログストリーム["${LOGSTREAM}"]のNEXT_TOKENを取得しました。"
                echo "NEXT_TOKEN:" $NEXT_TOKEN
            fi

            HEAD_FIRST_TIMESTAMP=$(cat "${RESPONSE_FILE_NAME}" | jq -r '.events[].timestamp' | sort -r | head -n 1)
            echo "HEAD_FIRST_TIMESTAMP:" $HEAD_FIRST_TIMESTAMP
            # 空ではない場合
            if [ -n $HEAD_FIRST_TIMESTAMP ]; then
                # ログストリームの開始時間より古い場合、ループを終了
                # if [ $(( $HEAD_FIRST_TIMESTAMP / 1000 )) -lt $(( $UT_FROM / 1000 )) ]; then
                if [ $(( $HEAD_FIRST_TIMESTAMP )) -lt $(( $UT_FROM )) ]; then
                    break
                fi
            fi

            # ログストリームの次ページが存在しない場合、ループを終了
            if [[ $(cat "${RESPONSE_FILE_NAME}" | jq -e '.events == []') == "true" ]]; then
                break
            fi

        done

        # ソートする
        cat $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL.log | sort > $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL_Sorted.log

        # 対象ログを抽出
        echo "対象日付(抽出条件:抽出対象のタイムスタンプ >= $LOG_DATE && 抽出対象のタイムスタンプ < $SYS_DATE_CURRENT)のログを抽出します。"
        awk '$1 >= "'"$LOG_DATE"'" && $1 < "'"$SYS_DATE_CURRENT"'" {print $0}' $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL_Sorted.log > $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_$LOG_DATE.log

        # 一時ファイルを削除
        rm -f $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL_Sorted.log
        rm -f $EXE_DIR/${TEMP_OUT_LINES##/*/}/"${LOGSTREAM}"_ALL.log

        rm -f $EXE_DIR/LOG_GROUP_ALL.txt
        rm -f $EXE_DIR/LOG_GROUP.txt
        rm -f $EXE_DIR/RESPONSE.log

    done

done << FILE
$LOGGROUP_cat
FILE

# フォルダの日付
DT_RESULT=$LOG_DATE
# 「/mnt/c/AWSLogs/LOG」の上の階層「/mnt/c/AWSLogs」に移動
cd ${EXE_DIR%/*}
# 一時フォルダを「日付_ログ名」にコピー
cp -r ${EXE_DIR}/$LOG_GROUP_NAME $DT_RESULT"_$LOG_GROUP_NAME"
# 一時フォルダを
rm -rf $(basename ${EXE_DIR})

############################################################################
# 終了処理:戻り値を返却
############################################################################
END_FUNC 0
# RET_CODEが1以上の場合
if [ "${RET_CODE}" -ge "1" ]; then
    END_FUNC "${RET_CODE}"
elif [ "${RET_CODE}" == "0" ]; then
    END_FUNC "${RET_CODE}"
fi

実行結果

結果のフォルダ構成

ログ内容サンプル

・前のログ(0時)

・後ろのログ(23時)

参考

AWS公式より(get-log-events)

==================================================

-start-from-head | –no-start-from-head (boolean)

値がtrueの場合、最も早いログイベントが最初に返されます。 値がfalseの場合、最新のログイベントが最初に返されます。 デフォルト値はfalseです。

この操作でnextTokenとして前のnextForwardToken値を使用している場合は、startFromHeadにtrueを指定する必要があります。

==================================================

まとめ

get-log-eventsを使う時に、古いログを取得したい場合、nextForwardTokenを、新しいのログを取得したい場合、nextBackwardToken、を使ったほうが効率よく取得できので、分けて使った方がいいと思います。

Last modified: 2021-12-10

Author