LoginSignup
8
11

More than 5 years have passed since last update.

CloudWatch Logsにロギングするための簡易クラスを作る

Posted at

動機

AWS上でシステムを構築する際、諸々のログはCloudWatchLogsやS3に集めるのがベターというのが一般的な考え方かと思います。
ところが、AWSのサービスがCloudWatchLogsに出してくれるログについては

  • CloudWatchLogsのロググループ/ログストリームの分割単位がAWS側で決められている(例えばLambdaだと毎回の実行ごとにログストリームが切られてしまう)
  • サービスによってはログストリーム名がイケておらず、確認がつらいときが多々ある(Lambdaのログストリーム名に含まれる${LATEST}がAWS CLIの操作時に予想外の挙動をしたり。。。)
  • サービスによってはログへの出力が「最後にまとめて出力」となるケースがあり、リアルタイムに状況が確認できないこともある
  • watchtowerなどのLoggerライブラリはあるが、サービスによってはboto3以外のライブラリ導入が至極めんどくさいことがある

といったつらみを抱えるケースがありました。自分の職場での業務上の制約等も鑑みて、

  • 自分で好きに決めたロググループ/ログストリームに
  • boto3やビルトインのもの以外のライブラリを使わずに
  • CloudWatchLogsにログを投げ込む

という簡易クラスを作成することにしました。
(Logger.infoのようなログレベルを作るまで至らなかったのでLoggerではない気がする)

実装したコード

cwlogs.py
import boto3
from datetime import datetime


class cwlogger:
    def __init__(self,
                 log_group_name,
                 log_stream_name,
                 region="ap-northeast-1",
                 session=None,
                 force_new_log_stream=False):
        self.log_stream_name = log_stream_name
        self.log_group_name = log_group_name
        self.region = region
        self.token = None

        # get logs client
        self.client = cwlogger.get_client(session=session)

        # get existing logstream
        streams = self.client.describe_log_streams(
            logGroupName=self.log_group_name)
        existing = False
        for stream in streams["logStreams"]:
            if stream["logStreamName"] == self.log_stream_name:
                existing = True
                self.stream = stream
                if "uploadSequenceToken" in stream:
                    self.token = stream['uploadSequenceToken']
        if force_new_log_stream or not existing:
            self.client.create_log_stream(
                logGroupName=self.log_group_name,
                logStreamName=self.log_stream_name)

    def log(self, message):
        if self.token is None:
            result = self.client.put_log_events(
                logGroupName=self.log_group_name,
                logStreamName=self.log_stream_name,
                logEvents=[{
                    "message": message,
                    "timestamp": int(datetime.now().strftime("%s%f")[:-3])
                }]
            )
        else:
            result = self.client.put_log_events(
                logGroupName=self.log_group_name,
                logStreamName=self.log_stream_name,
                logEvents=[{
                    "message": message,
                    "timestamp": int(datetime.now().strftime("%s%f")[:-3])
                }],
                sequenceToken=self.token
            )
        self.token = result["nextSequenceToken"]

    @classmethod
    def get_client(cls, session=None):
        if session is not None:
            if not isinstance(session, boto3.session.Session):
                error_str = "'session' argument must be {}".format(
                    boto3.session.Session)
                raise TypeError(error_str)
            return session.client("logs")
        else:
            return boto3.client("logs")

    @classmethod
    def create_log_group(cls, log_group_name, region="ap-northeast-1",
                         session=None):
        client = cwlogger.get_client(session=session)
        client.create_log_group(logGroupName=log_group_name)

使い方

# create log group
cwlogger.create_log_group("/MyCustomLogs")

logger = cwlogger("/MyCustomLogs", "MainStream")
logger.log("hoge1")
logger.log("fuga2")
logger.log("piyo3")
8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11