Search code examples
laravelaws-lambdaamazon-cloudwatchamazon-cloudwatchlogslaravel-vapor

Laravel Vapor Custom Logs to Amazon AWS Cloudwatch as JSON


By default Laravel Vapor pushes the laravel.log file to strerr output. Which is the picked up by Lambda and thrown to Cloudwatch. Quite hard to look through unless you are looking via the Vapor UI. Look for an easy way to do this and push them directly to Cloudwatch (with multiple files).


Solution

  • Firstly added this awesome Library

    composer require maxbanton/cwh 
    

    Then add this to your log config...

            'cloudwatch' => [
                'driver' => 'custom',
                'via' => \App\Logging\CloudWatchLoggerFactory::class,
                'formatter' => Monolog\Formatter\JsonFormatter::class,
                'cloudwatch_stream_name' => 'laravel',
                'sdk' => [
                    'region' => 'eu-west-1',
                    'version' => 'latest',
                    'credentials' => [
                        'key' => env('AWS_CW_ACCESS'),
                        'secret' => env('AWS_CW_SECRET')
                    ]
                ],
                'retention' => 730,
                'level' => 'debug',
            ],
    

    You'll need to add AWS_CW_ACCESS and AWS_CW_SECRET keys for an IAM user with access to Cloudwatch.

    Then add App/Logging/CloudWatchLoggerFactory.php with the following contents..

    <?php
    
    namespace App\Logging;
    
    use Aws\CloudWatchLogs\CloudWatchLogsClient;
    use Maxbanton\Cwh\Handler\CloudWatch;
    use Monolog\Formatter\JsonFormatter;
    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 = config('app.name') . '-' . config('app.env');
    
            // Log stream name, will be created if none
            // $streamName = config('app.hostname');
            $streamName = $config["cloudwatch_stream_name"];
    
            // 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);
            $handler->setFormatter(new JsonFormatter());
            // Create a log channel
            $logger = new Logger($name);
            // Set handler
            $logger->pushHandler($handler);
            //$logger->pushProcessor(new CompanyLogProcessor()); //Use this if you want to adjust the JSON output using a log processor
            return $logger;
        }
    }
    

    You can then use that as any log... Ie Log::channel('cloudwatch')->info('hey');

    To force the default laravel.log to here AND show in vapor just add this as a stack

            'vapor' => [
                'driver' => 'stack',
                'channels' => ['stderr', 'cloudwatch'],
                'ignore_exceptions' => false,
            ],
    

    Then set logging.default setting to vapor in your envvars.

    If you want additional logging channels just copy the cloudwatch channel setting with a new one and make sure you adjust the cloudwatch_stream_name.

    Thanks to the other answer I found on Stackoverflow helping me get to here. I wanted to log this directly under answer for Laravel Vapor as I imagine many others will get stuck trying to do this!