@ppihus Have you read the AWS documentation on streaming logs?
Correct way to stream logs to CloudWatch
Is there any correct/nice way to stream Laravel logs to CloudWatch? Assume that the Laravel application is hosted on Elastic Beanstalk.
I found this https://github.com/maxbanton/cwh , but I haven't had much luck with it on Laravel 5.6.
Thanks @martinbean. Yes, I've seen it and this is actually something that I am currently using, but I am not very happy about the solution.
- It's bunch of added configurations for each application
- I would like to see a solution where Laravel itself is able to stream the logs, without saving them on disk.
If it turns out that this indeed is the correct and nice way then we'll probably keep using it, but at the moment I am wondering if there is something better.
This is a very simple guide for integrating maxbanton/cwh with laravel:
https://stackoverflow.com/questions/50814388/laravel-5-6-aws-cloudwatch-log/51790656#51790656
It is a good idea to create an IAM user with programmatic access and set the minimum permissions as described in package docs
My only consideration is that this package uses synchronous (=blocking) API calls, thus it is not recomennted for logging inside http requests... Here is a proposed solution about this problem: https://github.com/maxbanton/cwh/issues/25#issuecomment-353573180
I'm looking into this also. You should note that sending logs via monolog to CloudWatch comes with a small time penalty while the code sends logs to CLoudWatch via it's HTTP end point (potentially on every web request, but it depends on how much logging you do).
I'm currently looking into the AWS CloudWatch agent, which is a program that runs on the server and can tail the log files / send them to CloudWatch (in addition to collect other metrics, altho I plan on only using it for collecting logs).
The benefit is that it doesn't add time to a web request (runs outside of PHP), but the drawbacks that I haven't yet determined might be:
- Server setup / configuration setup (alleviated if you automate setting up your infrastructure)
- I haven't yet determined if I need to adjust the log format for laravel/nginx logs to be parsed by CloudWatch
@ppihus Laravel 6 has a log channel for Cloudwatch. I'd suggest just installing a fresh copy of Laravel 6 and A/B that setup to your application... I'm pretty sure it just uses the logging config and a CloudWatchLogger Factory... you may just need to pull that into Laravel 5.6, but I'm not sure.
5.6 to 6.9 is not that painful. Definitely just upgrade if you can. I host a few apps on ElasticBeanstalk and log to CloudWatch, it works great... but all my apps run Laravel v6.9.0 (latest) and I keep them up to date.
LTS is a trap! Keep your apps updated.
Here is the Laravel 6 implementation... in case that is helpful...
config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'cloudwatch' => [
'driver' => 'custom',
'via' => \App\Logging\CloudWatchLoggerFactory::class,
'sdk' => [
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
]
],
'retention' => 365,
'level' => 'info',
'group' => env('CLOUDWATCH_GROUP'),
'stream' => env('CLOUDWATCH_STREAM'),
],
app/Logging/CloudWatchLoggerFactory.php
<?php
namespace App\Logging;
use Aws\CloudWatchLogs\CloudWatchLogsClient;
use Maxbanton\Cwh\Handler\CloudWatch;
use Monolog\Logger;
class CloudWatchLoggerFactory
{
/**
* Create a custom Monolog instance.
*
* @param array $config
* @return \Monolog\Logger
*/
public function __invoke(array $config)
{
$sdkParams = $config["sdk"];
$tags = $config["tags"] ?? [ ];
$name = $config["name"] ?? 'cloudwatch';
// Instantiate AWS SDK CloudWatch Logs Client
$client = new CloudWatchLogsClient($sdkParams);
// Log group name, will be created if none
$groupName = env('CLOUDWATCH_GROUP');
// Log stream name, will be created if none
$streamName = env('CLOUDWATCH_STREAM');
// Days to keep logs, 14 by default. Set to `null` to allow indefinite retention.
$retentionDays = $config["retention"];
// Instantiate handler (tags are optional)
$handler = new CloudWatch($client, $groupName, $streamName, $retentionDays, 10000, $tags);
// Create a log channel
$logger = new Logger($name);
// Set handler
$logger->pushHandler($handler);
return $logger;
}
}
It works off the https://packagist.org/packages/maxbanton/cwh package.
...and https://github.com/aws/aws-sdk-php/
...and https://packagist.org/packages/monolog/monolog
I would imagine if you pull in those three packages and implement the files/edits above... you might be able to get it to work in an older version of Laravel.
This old thread needed an update due to some very relevant and recent factors:
- At the time of this writing, the
maxbanton/cwhpackage is lagging in maintenance and is not Laravel 10 compatible - Somewhat related, Amazon Linux 2 has a newer approach to jumping on the existing CloudWatch Agent log stream bandwagon
As I was in the process of upgrading an older Laravel app up to 10 I ran into the problem of the outdated package. Log visibility to the ElasticBeanstalk instances is critical for awareness. Searching around for solution one will quickly find lots of guides and advice showing how to use .ebextensions scripts to pile on to the /etc/aws... configurations, but beware: that's now out of date.
If you're using the latest PHP 8.1 ElasticBeanstalk platform, the managed CloudWatch Logs Agent has a config directory at /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/ you can drop your own file into that will instruct the agent to stream your Laravel logs to CloudWatch. The minimal file is pretty simple and would look like this:
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/app/current/storage/logs/*.log",
"log_group_name": "laravel_logs",
"log_stream_name": "laravel",
"retention_in_days": 30
}
]
}
}
}
}
For multiple environments and multiple instances those values for log_group_name and log_stream_name will not be helpful. You can write those dynamically via shell script by way of the .platform scripts.
A post-deploy hook that uses a template file stored in the code repo and the jq CLI tool (handily available on the EC2 already) did the trick. I needed the platform script tools to get the environment name, and the IMDSv2 resources to get the instance-id. This allows me to write to unique groups and streams per environment and per instance. I'll leave it at that, without script samples, to protect company IP.
The end result here is that Laravel is not synchronously bound to CloudWatch Logs thanks to the CloudWatch Logs Agent, and we've decoupled the app from the telemetry solution entirely using tools that are already there. Really hope this helps someone in the year 2023 and beyond trying to figure out what to do given the changes I cited at the top. Sorry for lack of links, I just signed up today so I could post this follow up and I'm not allowed to post links yet.
These were very helpful:
- https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/AWSHowTo.cloudwatchlogs.html#AWSHowTo.cloudwatchlogs.streaming.custom
- https://github.com/awsdocs/elastic-beanstalk-samples/blob/a0b8442802a68ede98fbb30d75913113ba65712e/configuration-files/aws-provided/instance-configuration/logs-streamtocloudwatch-linux.config
Here's a quick example based on that example that I haven't tested yet :):
###############################################################################
##
## Configure CloudWatch to ingest Laravel logs
##
## Based on: "https://github.com/awsdocs/elastic-beanstalk-samples/blob/a0b8442802a68ede98fbb30d75913113ba65712e/configuration-files/aws-provided/instance-configuration/logs-streamtocloudwatch-linux.config"
##
###############################################################################
files:
"/opt/aws/amazon-cloudwatch-agent/etc/laravel_logs.json":
mode: "0644"
owner: root
group: root
content: |
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/app/current/storage/logs/*.log",
"log_group_name": "`{"Fn::Join":["/", ["/aws/elasticbeanstalk", { "Ref":"AWSEBEnvironmentName" }, "Laravel-Logs"]]}`",
"log_stream_name": "{instance_id}",
"timezone": "UTC"
}
]
}
}
}
}
container_commands:
01_append_cloudwatch_agent_config:
command: /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a append-config -s -c file:/opt/aws/amazon-cloudwatch-agent/etc/laravel_logs.json
02_remove_backup_file:
command: rm -f /opt/aws/amazon-cloudwatch-agent/etc/laravel_logs.json.bak
ignoreErrors: true
Looking for solution that supports Laravel 12 easily.
Please or to participate in this conversation.