Easily log custom metrics to AWS CloudWatch
Recently, I wanted to track the amount of time that Sidekiq jobs were taking to execute on a production Rails environment. I wanted an easy way to be able to track the arguments for the Sidekiq job, the user that initiated the job, and of course the duration that the job took to complete.
Since this is a Rails application, the code provided is Ruby but you can look up the put_data
method on the AWS docs for your respective programming language.
Generate the user and key #
We need to head over to the AWS dashboard and create a new IAM policy with the following JSON (provided in this blog post):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricData"
],
"Resource": "*"
}
]
}
I called this policy put-custom-metric
.
Then, create an IAM user with programmatic access. Attach the custom put-custom-metric
policy to this user. AWS will provide you with an access and secret key. Take note of these two strings as you’ll need them next.
Define the environment variables #
In your .bash_profile
export the two new variables:
export cloudwatch_access_key_id='##your key here##'
export cloudwatch_secret_access_key='##your secret here##'
Save the file. Note: In your production environment, you’ll also have to define these two environment variables.
The code #
Insert the following in your Gemfile
:
gem "aws-sdk-cloudwatch", require: false
And then execute bundle install
to install the new package.
For my Rails application, I created a CloudWatchHelper
module which is able to save the custom metrics to CloudWatch. I called the file cloudwatch.rb
and placed it inside the /lib
folder.
require "aws-sdk-cloudwatch"
module CloudWatchHelper
def self.create_metric(some_arg, duration)
options = {
:access_key_id => ENV["cloudwatch_access_key_id"],
:secret_access_key => ENV["cloudwatch_secret_access_key"]
}
client = Aws::CloudWatch::Client.new({
:region => "us-west-1",
:access_key_id => options[:access_key_id],
:secret_access_key => options[:secret_access_key]
})
# Get the environment based on ENV variables
env = "development" if Rails.env.development?
env = "production" if Rails.env.production?
timestamp = Time.now.utc
res = client.put_metric_data(
namespace: "SidekiqJobTimes",
metric_data: [
{
metric_name: "SidekiqDuration",
dimensions: [{
name: "Environment",
value: env
}, {
name: "Arg",
value: some_arg
}],
timestamp: timestamp,
value: duration,
unit: "Seconds"
}
]
)
return true
end
end
If you have other arguments you would like to send to CloudWatch along with the metric, simply add them into the dimensions
array.
Next, I modified the application_job.rb
file which is the parent class of all the Sidekiq jobs which get executed. The modification allows us to keep track of the start and end time of a job.
class ApplicationJob < ActiveJob::Base
attr_accessor :before, :after, :duration
def start_timer
@before = Time.now
end
def end_timer
@after = Time.now
@duration = @after - @before
end
end
The last thing to do is utilize the start_timer
, end_timer
, and create_metric
functions within the actual job code.
require 'cloudwatch'
class HistoricalDataJob < ApplicationJob
include CloudWatchHelper
queue_as :default
def perform(arg)
# Start the timer for as soon as a job is created
start_timer()
# Do some work in your job
some_work(arg)
# End the timer after the job executes
end_timer()
# Create the metric in CloudWatch
CloudWatchHelper.create_metric(arg, @duration)
end
end
Testing your work #
Execute your job and within a minute of your Sidekiq job completing, you should start seeing your custom CloudWatch metrics coming into the CloudWatch AWS dashboard:
Feel free to let me know on twitter (@shahzebdev) if you found this helpful 😊.