sinemacula/laravel-log-cloudwatch
A Laravel logging driver that sends application logs directly to AWS CloudWatch. Integrate CloudWatch Logs with Laravel’s logging stack to centralize, monitor, and retain logs in AWS with simple configuration.
Install the driver
composer require sinemacula/laravel-log-cloudwatch
As of v1.1.0 the package declares support for Laravel 13 (added in #22). If you are upgrading an existing app to Laravel 13, bump this package at the same time — older 1.0.x constraints may block composer update against laravel/framework:^13.0. Confirm the resolved version with composer show sinemacula/laravel-log-cloudwatch.
Provide AWS credentials. The driver uses the AWS SDK for PHP, so the standard resolution chain applies: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, optional AWS_SESSION_TOKEN), a shared credentials file, or an instance/task role. On EC2, ECS, or Lambda, prefer the role — do not hardcode keys in config/logging.php.
Register a channel in config/logging.php:
'cloudwatch' => [
'driver' => 'cloudwatch',
'region' => env('AWS_DEFAULT_REGION'),
'log_group' => env('CLOUDWATCH_LOG_GROUP', '/app/' . env('APP_NAME')),
'log_stream' => env('CLOUDWATCH_LOG_STREAM', gethostname()),
// credentials, retention, batch/tap options per package README
],
The exact key names are defined in the package's config file — publish it (php artisan vendor:publish) and treat that as the source of truth rather than copying snippets from blog posts.
First use case: send a single low-risk event to prove the whole chain works before wiring it into the exception handler.
Log::channel('cloudwatch')->info('cloudwatch smoke test', ['user_id' => 1]);
Then confirm the log group, stream, and event actually appear in the CloudWatch console. A green composer require tells you nothing about IAM.
Keep it as a named channel, not the default driver. The pragmatic production setup is stack → daily (local disk) + cloudwatch, so you retain a machine-local copy for fast grepping and survive an AWS outage:
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'cloudwatch'],
'ignore_exceptions' => false,
],
Environment-specific group and stream naming. Derive the group from the app and environment, and the stream from the host/container plus a date or build identifier. This is what makes CloudWatch usable later — you can filter to a single deploy or a single pod without scanning everything.
Prefer roles over keys in every deployed environment. Wire AWS_* env vars only for local debugging. In ECS/Lambda the SDK picks up credentials automatically and you avoid rotating secrets in your config.
Log structured context, not strings. CloudWatch Logs Insights can only query JSON you actually sent. Pass arrays as the context argument (Log::channel('cloudwatch')->error($msg, $context)) and keep messages stable so you can group by message and filter on context.order_id. If the package's formatter lets you choose the output shape, use JSON rather than the default line format — it is the difference between a searchable log and an expensive text dump.
Route exceptions deliberately. In bootstrap/app.php (Laravel 11+) or App\Exceptions\Handler, call report() on the cloudwatch channel for exceptions only, not for every debug line. You get durable alerting without shipping your entire debug firehose to a metered service.
Use CloudWatch Logs Insights as the query layer. Once logs are there, a saved query per incident type (5xx by route, slow jobs by duration, auth failures by IP) is more valuable than any local tail -f. Treat the saved queries as part of the integration, not an afterthought.
logs:CreateLogGroup, logs:CreateLogStream, logs:DescribeLogStreams, and logs:PutLogEvents — plus logs:PutRetentionPolicy if the package manages retention. A missing DescribeLogStreams often surfaces as an apparently empty group rather than an error, so verify in the console, not just in laravel.log.aws logs describe-log-groups) rather than debugging through the logging stack.SIGKILL, or an abrupt deploy can drop the tail of your logs — exactly the lines you need. Keep the second channel in the stack, and don't tune batching down to zero-and-only-cloudwatch for anything critical.LOG_LEVEL=warning for the cloudwatch channel specifically (per-channel levels via the stack's channel config) while keeping debug on the local file channel.aws logs put-retention-policy — and treat it as required infrastructure, not a tuning knob.PutLogEvents calls due to sequence-token conflicts. Include the hostname, container ID, or task ID in the stream name.AWS_DEFAULT_REGION alone. Keep that override in a non-production env branch so it never leaks into production config.How can I help you explore Laravel packages today?