【AWS】LaravelでCloudWatchへログを送る方法

今回はLaravelでCloudWatchへログを送る方法の紹介です。
目次
CloudWatchへログを送る方法でやること
- LaravelのログをAWS CloudWatchに送るためのカスタムロガーの作成
- config/logging.phpにCloudWatchカスタムロガーを使用する記述
- .env に AWS 関連の環境変数を設定す
AWS CloudWatchに送るためのカスタムロガーの作成
- AWSの設定(リージョン・認証情報)を $awsConfig に構築
- CloudWatchLogsClient を生成
- .env の値を元にロググループ名とストリーム名を設定
- ロググループが存在するか確認、なければ作成(保持期間も設定)
- ログストリームが存在するか確認、なければ作成
- CloudWatchLogsHandler を生成し、バッチサイズ・保持期間を指定
- JSONフォーマッタを適用(構造化ログ)
- Monolog Logger を作成し、ハンドラを登録
- 各種プロセッサ(Web情報、メモリ、ファイル行番号)を追加
- 環境情報 app_env を extra に追加するカスタムプロセッサを追加
- 最終的に Logger を返却
__invokeメソッド
CloudWatch 用のロガーを作成する本体です。
public function __invoke(array $config) {
// 以下省略
}
ensureLogGroupメソッド
ロググループを確認・作成します。
private function ensureLogGroup(CloudWatchLogsClient $client, string $groupName, $retentionDays) {
// 以下省略
}ensureLogStreamメソッド
ログストリームを確認・作成します。
private function ensureLogStream(CloudWatchLogsClient $client, string $groupName, string $streamName) {
// 以下省略
}CloudWatchLoggerの全体のコード
<?php
namespace App\Logging;
use Aws\CloudWatchLogs\CloudWatchLogsClient;
use Aws\CloudWatchLogs\Exception\CloudWatchLogsException;
use Maxbanton\Cwh\Handler\CloudWatch as CloudWatchLogsHandler;
use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Illuminate\Support\Facades\Log;
use Exception;
class CloudWatchLogger
{
/**
* Create a custom Monolog instance.
*
* @param array $config
* @return \Monolog\Logger
*/
public function __invoke(array $config)
{
try {
$awsConfig = [
'region' => $config['region'], // AWS_DEFAULT_REGION
'version' => 'latest'
];
// 認証情報を設定
$awsConfig['credentials'] = [
'key' => $config['key'], // AWS_ACCESS_KEY_ID
'secret' => $config['secret'] // AWS_SECRET_ACCESS_KEY
];
// CloudWatchLogsクライアントの生成
$client = new CloudWatchLogsClient($awsConfig);
// ロググループとストリーム名の設定
$groupName = $config['group'];
$streamName = $config['stream'];
// ロググループの確認と作成
$this->ensureLogGroup($client, $groupName, $config['retention']);
// ログストリームの確認と作成
$this->ensureLogStream($client, $groupName, $streamName);
// ハンドラの設定
$handler = new CloudWatchLogsHandler(
$client,
$groupName,
$streamName,
$config['retention'],
$config['batch_size']
);
// JSONフォーマッタを適用(構造化ログ)
$handler->setFormatter(new JsonFormatter());
// Loggerインスタンスの作成と追加情報の設定
$logger = new Logger('cloudwatch');
$logger->pushHandler($handler);
// 追加のプロセッサで有用な情報を含める
$logger->pushProcessor(new WebProcessor());
$logger->pushProcessor(new IntrospectionProcessor());
$logger->pushProcessor(new MemoryUsageProcessor());
// カスタムプロセッサでアプリケーション情報を追加
$logger->pushProcessor(function ($record) {
$record['extra']['app_env'] = env('APP_ENV');
return $record;
});
return $logger;
} catch (Exception $e) {
$today = date('Y-m-d');
$logPath = storage_path('logs/laravel-' . $today . '.log');
$logger = new Logger('cloudwatch-error');
$handler = new StreamHandler($logPath);
$logger->pushHandler($handler);
return $logger;
}
}
/**
* ロググループの確認と作成
* @param CloudWatchLogsClient $client
* @param string $groupName
* @param int $retentionDays
* @return bool
*/
private function ensureLogGroup(CloudWatchLogsClient $client, string $groupName, $retentionDays)
{
try {
// 既存のロググループを検索
$response = $client->describeLogGroups([
'logGroupNamePrefix' => $groupName
]);
$exists = false;
foreach ($response['logGroups'] as $group) {
if ($group['logGroupName'] === $groupName) {
$exists = true;
break;
}
}
// 存在しない場合は作成
if (!$exists) {
$client->createLogGroup([
'logGroupName' => $groupName
]);
// 保持期間が失効しない場合は設定しない
if ($retentionDays !== null) {
// 保持期間を設定
$client->putRetentionPolicy([
'logGroupName' => $groupName,
'retentionInDays' => $retentionDays
]);
}
}
return true;
} catch (CloudWatchLogsException $e) {
// 既に存在する場合は無視(競合状態の対応)
if (strpos($e->getMessage(), 'ResourceAlreadyExistsException') !== false) {
return true;
}
throw $e;
}
}
/**
* ログストリームの確認と作成
* @param CloudWatchLogsClient $client
* @param string $groupName
* @param string $streamName
* @return bool
private function ensureLogStream(CloudWatchLogsClient $client, string $groupName, string $streamName)
{
try {
// 既存のログストリームを検索
$response = $client->describeLogStreams([
'logGroupName' => $groupName,
'logStreamNamePrefix' => $streamName
]);
$exists = false;
foreach ($response['logStreams'] as $stream) {
if ($stream['logStreamName'] === $streamName) {
$exists = true;
break;
}
}
// 存在しない場合は作成
if (!$exists) {
$client->createLogStream([
'logGroupName' => $groupName,
'logStreamName' => $streamName
]);
}
return true;
} catch (CloudWatchLogsException $e) {
// 既に存在する場合は無視(競合状態の対応)
if (strpos($e->getMessage(), 'ResourceAlreadyExistsException') !== false) {
return true;
}
throw $e;
}
}
}
スポンサードサーチ
config/logging.phpの設定
default
- アプリ全体のデフォルトログチャネルを指定します。
- .env で LOG_CHANNEL を指定しなければ、stack が使われます。
'default' => env('LOG_CHANNEL', 'stack'),
channels
ログチャネル(出力先)を一覧で定義します。
この例ではcloudwatchのみにしていますが、複数のチャネルを記述すると、まとめてログを送ることができます。
'stack' => [
'driver' => 'stack',
'channels' => ['cloudwatch'],
'ignore_exceptions' => false,
],config/logging.phpの全体のコード
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['cloudwatch'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'info',
'days' => 5,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'papertrail' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('APP_DEBUG', false) ? 'debug' : 'info',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
'cloudwatch' => [
'driver' => 'custom',
'via' => \App\Logging\CloudWatchLogger::class,
'region' => env('AWS_DEFAULT_REGION'),
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'group' => '/ecs/' . env('APP_ENV') . '-logs',
'stream' => date('Y-m-d'),
'retention' => null,
'batch_size' => 10000,
],
],
];
.env
.envで設定内容を記述します。
LOG_CHANNEL=stack
APP_ENV=production
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=your_aws_access_key_id
AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key
以上です。







