How to enable CloudWatch logging with Symfony on AWS

Luke Jennings
4 min readSep 30, 2021

--

I’ve recently found myself needing to send application logs to AWS CloudWatch via a Symfony API container running in AWS Fargate. After some research and some trial and error I found a solution that is easy to get setup.

Getting Started

Before you can successfully send logs to CloudWatch you will need an AWS Key and AWS Secret token pair for an account that has the following IAM Roles enabled:

"logs:PutLogEvents",
"logs:DescribeLogStreams",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:CreateLogGroup",

Ideally a new user would be created specifically with these roles enabled.

Requirements

Creating a new Monolog config

By default Symfony sends applications logs to /var/log/dev.log however we can adjust this by providing a new Monolog config file.

Create a new file named monolog.yaml under config/packages/staging/

You could create distinct configurations for dev and prod if you wanted to, by creating a monolog.yaml file in the appropriate directories. For this example I am setting a config file specifically for our staging environment.

Once created add the following example configuration:

monolog:
handlers:
main:
type: fingers_crossed
action_level: warning
handler: cloudWatch
cloudWatch:
type: service
id: cloudwatch_handler

Here we are replacing the default Monolog handler with our own named cloudWatch

Note: The cloudwatch_handler does not exist yet, but we are about to create it.

This will stop applications logs being written to files, if you want to still have the ability to write to logs, as well as CloudWatch you can try the following

monolog:
handlers:
main:
type: fingers_crossed
level: debug
handler: grouped
grouped:
type: group
members: [cloudWatch, local]
local:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
cloudWatch:
type: service
id: cloudwatch_handler

Here we set a grouped handler which details multiple handlers, one for CloudWatch and the other for local, file based logging.

Creating the logging service

Now we’ve told Monolog that we want to use a custom handler called cloudwatch_handler we need to create it. To do this I used registered a new logging service

Within service/config/services.yaml add the following configurations:

cloudwatch_client:
class: Aws\CloudWatchLogs\CloudWatchLogsClient
arguments:
-
credentials: {
key: "%env(resolve:AWS_CLOUD_WATCH_KEY)%",
secret: "%env(resolve:AWS_CLOUD_WATCH_SECRET)%"
}
region: "eu-west-1"
version: "latest"

Here we register an instance of the AWS CloudWatchLogsClient. This is where we need to insert our AWS user key and secret. In this example I am getting the details from the .env file.

Adjust the region key here to match your requirements. If you have no specific regional requirements you can leave it as eu-west-1.

Leave the version key set to latest, unless you have specific version requirements.

In the same file add the following:

cloudwatch_handler:
class: Maxbanton\Cwh\Handler\CloudWatch
configurator: ['@json_log_configurator', configure]
arguments:
- "@cloudwatch_client"
- "my-log-group" # groupName
- "my-log-stream" # streamName
- 30 # retentionDays
- 1 # logsInBatch
- { mytag: "tag" } # tags
- WARNING # logLevel

Here we register the custom Monolog handler we referenced in the monolog.yaml config earlier. The CloudWatch class listed above extends the Monolog AbstractProcessingHandler.

The configurator option is used to overwrite or augment the formatter used by Monolog. In the example above I have supplied a custom method which I will discuss below.

The first argument provided must be an instance of the AWS CloudWatchLogsClient which we setup above. The arguments here are largely down to your own project requirements, adjust the log group name and the stream name as required.

Note: If using a specific IAM user for CloudWatch it may be tied to a specific log group. If that is the case make sure the log group name and stream name arguments are correct and match the IAM users permissions

In the same file add the following:

json_log_configurator:
class: App\Utils\CloudWatchJsonConfigurator

By default Monolog will send any errors in plain text to CloudWatch. It can be nice to have these errors sent as JSON for ease of searching and filtering of logs.

Here we pass in a custom configurator which sets the Monolog formatter to default to JSON. I have put this custom class within Utils but feel free to move this if required, just ensure you update the class path.

Creating the custom configurator

It’s possible to augment or override the formatter used by Monolog. in this example we are simply setting the formatter used to be that of JSON, however you could make any other adjustments here that you needed.

<?php

namespace App\Utils;

use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\AbstractProcessingHandler;

/**
* Configure a given Monolog handler to format messages as JSON
*/
class CloudWatchJsonConfigurator
{
/**
* Format logs being sent to CloudWatch to JSON
*
*
@param AbstractProcessingHandler $handler
**/
public function configure(AbstractProcessingHandler $handler)
{
$handler->setFormatter(new JsonFormatter());
}
}

Thats it! That’s all you need to start sending applications logs to CloudWatch. Deploy these changes to your infrastructure and generate some logs.

I’m sure there a few ways of doing this. Please do let me know any other solutions you know of that I could play around with.

--

--

Luke Jennings

Senior Software Engineer @chip with a passion for art and music. I love what I do would love to share that with you