EC2上のアプリケーション(Node.js)のログをpinoを使って、CloudWatch Logsに出力してみた

EC2上のアプリケーション(Node.js)のログをpinoを使って、CloudWatch Logsに出力してみた

Clock Icon2024.08.26

こんにちは、ゲームソリューション部のsoraです。
今回は、EC2上のアプリケーション(Node.js)のログをpinoを使って、CloudWatch Logsに出力してみたことについて書いていきます。

CloudWatch AgentとNode.jsのインストール

EC2にCloudWatch AgentとNode.jsをインストールします。
今回はユーザデータを使ってインストールします。
AMIはAmazon Linux 2023を使用します。

CloudWatch Agentの設定として、システムログとアプリケーションログの2つを出力するように設定しています。

#!/bin/bash

# Update the package list and install necessary packages
dnf update -y

# タイムゾーンの設定
timedatectl set-timezone Asia/Tokyo

# rsyslogのインストール(システムログがjournalで保存され文字化けするため)
dnf install -y rsyslog
systemctl enable rsyslog
systemctl start rsyslog

# CloudWatch Agentのインストール
dnf install -y amazon-cloudwatch-agent
# Create the CloudWatch Agent configuration file for logging
bash -c 'cat <<EOL > /opt/aws/amazon-cloudwatch-agent/bin/config.json
{
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/messages",
            "log_group_name": "/api-server-system",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 30
          },
          {
            "file_path": "/var/log/api-app/*.log",
            "log_group_name": "/api-server-app",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 30
          }
        ]
      }
    }
  }
}
EOL'

# Start the CloudWatch Agent
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s

# デフォルトでenableだが念のため明示的にenableしておく
systemctl enable amazon-cloudwatch-agent

# nodejsのインストール
dnf update -y
dnf install -y gcc-c++ make

# マルチパート形式
Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud-init directives

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
# nodejsのインストール
if ! su - ec2-user -c "command -v node" > /dev/null 2>&1; then
  su - ec2-user -c "curl -sL https://rpm.nodesource.com/setup_20.x | sudo -E bash -"
fi
--//--

dnf install -y nodejs

# ディレクトリ作成
mkdir /home/ec2-user/myexpressapi
chown -R ec2-user:ec2-user /home/ec2-user/myexpressapi

EC2インスタンスのロール変更

EC2からCloudWatch Logsに出力するため、以下ポリシーをアタッチしたロールをEC2で使用します。
今回はメトリクスも取得しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudwatch:PutMetricData",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams",
                "logs:DescribeLogGroups"
            ],
            "Resource": "*"
        }
    ]
}

また、今回はパブリックサブネットに配置していますが、プライベートサブネットに配置してインターネットに出るルートがない場合は、以下エンドポイントも必要です。

  • com.amazonaws.{region}.logs
  • com.amazonaws.{region}.monitoring
  • com.amazonaws.{region}.ec2

プロジェクト初期化・パッケージのインストール

プロジェクトを初期化して、必要なパッケージをインストールします。

cd myexpressapi

# プロジェクトの初期化
npm init
# entry pointだけ入力しました。
package name: (myexpressapi)
version: (1.0.0)
description:
entry point: (index.js) index.mjs
test command:
git repository:
keywords:
author:
license: (ISC)

# Expressとpinoのインストール
npm install express pino

ソースコード

ソースコードは以下です。
ログ出力のためのパッケージであるpinoを使って、/var/log/api-app/app.logに出力しています。

index.mjs
import express from 'express';
import fs from 'fs';
import pino from 'pino';

// ログファイルを指定
const logFile = fs.createWriteStream('/var/log/api-app/app.log', { flags: 'a' });
const logger = pino(logFile);

const app = express();

app.get('/api', (req, res) => {
    logger.info({
        method: req.method,
        url: req.url,
        headers: req.headers
    }, 'GET /api called');
    res.json({ message: 'Hello, World!' });
});

app.use((req, res) => {
    logger.warn({
        method: req.method,
        url: req.url,
        headers: req.headers
    }, `404 Not Found: ${req.originalUrl}`);
    res.status(404).json({ message: 'Not Found' });
});

app.listen(80, () => {
    logger.info('Server is running on port 80');
});

サーバの起動・動作確認

最後にコードを実行して、APIサーバを起動します。

sudo node index.mjs

実行後にHTTP接続してみると、CloudWatch Logsに以下のようにログが出力できていました。
sr-ec2-cloudwatch-applog01
sr-ec2-cloudwatch-applog02

参考

https://zenn.dev/itte/articles/ce93b081048691

最後に

今回は、EC2上のアプリケーション(Node.js)のログをpinoを使って、CloudWatch Logsに出力してみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.