Skip to content

Commit f420a1a

Browse files
authored
ActiveSupport::Notifications instrumentation of aws sdk client calls (#37)
1 parent d10edf4 commit f420a1a

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,32 @@ message = MyMailer.send_email(options)
119119
message['X-SES-FROM-ARN'] = 'arn:aws:ses:us-west-2:012345678910:identity/[email protected]'
120120
message.deliver
121121
```
122+
123+
## Active Support Notification Instrumentation for AWS SDK calls
124+
To add `ActiveSupport::Notifications` Instrumentation to all AWS SDK client
125+
operations call `Aws::Rails.instrument_sdk_operations` before you construct any
126+
SDK clients.
127+
128+
Example usage in `config/initializers/instrument_aws_sdk.rb`
129+
```ruby
130+
Aws::Rails.instrument_sdk_operations
131+
```
132+
133+
Events are published for each client operation call with the following event
134+
name: <operation>.<serviceId>.aws. For example, S3's put_object has an event
135+
name of: `put_object.S3.aws`. The payload of the event is the
136+
[request context](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Seahorse/Client/RequestContext.html).
137+
138+
You can subscribe to these events as you would other
139+
`ActiveSupport::Notifications`:
140+
141+
```ruby
142+
ActiveSupport::Notifications.subscribe('put_object.s3.aws') do |name, start, finish, id, payload|
143+
# process event
144+
end
145+
146+
# Or use a regex to subscribe to all service notifications
147+
ActiveSupport::Notifications.subscribe(/s3[.]aws/) do |name, start, finish, id, payload|
148+
# process event
149+
end
150+
```

lib/aws-sdk-rails.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# frozen_string_literal: true
22

33
require_relative 'aws/rails/mailer'
4+
require_relative 'aws/rails/notifications_instrument_plugin'
45

56
module Aws
7+
68
# Use the Rails namespace.
79
module Rails
10+
811
# @api private
912
class Railtie < ::Rails::Railtie
1013
initializer 'aws-sdk-rails.initialize',
@@ -47,5 +50,20 @@ def self.use_rails_encrypted_credentials
4750
.to_h.slice(*aws_credential_keys)
4851
)
4952
end
53+
54+
# Adds ActiveSupport Notifications instrumentation to AWS SDK
55+
# client operations. Each operation will produce an event with a name:
56+
# <operation>.<service>.aws. For example, S3's put_object has an event
57+
# name of: put_object.S3.aws
58+
def self.instrument_sdk_operations
59+
Aws.constants.each do|c|
60+
m = Aws.const_get(c)
61+
if m.is_a?(Module) &&
62+
m.const_defined?(:Client) &&
63+
m.const_get(:Client).superclass == Seahorse::Client::Base
64+
m.const_get(:Client).add_plugin(Aws::Rails::Notifications)
65+
end
66+
end
67+
end
5068
end
5169
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require 'aws-sdk-core'
4+
require 'active_support/notifications'
5+
6+
module Aws
7+
module Rails
8+
9+
# Instruments client operation calls for ActiveSupport::Notifications
10+
# Each client operation will produce an event with name:
11+
# <operation>.<service>.aws
12+
# @api private
13+
class Notifications < Seahorse::Client::Plugin
14+
15+
def add_handlers(handlers, config)
16+
# This plugin needs to be first
17+
# which means it is called first in the stack, to start recording time,
18+
# and returns last
19+
handlers.add(Handler, step: :initialize, priority: 99)
20+
end
21+
22+
class Handler < Seahorse::Client::Handler
23+
24+
def call(context)
25+
event_name = "#{context.operation_name}.#{context.config.api.metadata['serviceId']}.aws"
26+
ActiveSupport::Notifications.instrument(event_name, context: context) do
27+
@handler.call(context)
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end

spec/aws/rails/notifications_spec.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require_relative '../../spec_helper'
2+
3+
module Aws
4+
5+
# Test namespaces
6+
module Service1; end
7+
module Service2; end
8+
9+
module Rails
10+
describe 'NotificationsInstrument' do
11+
12+
let(:base_client) { Aws::SES::Client }
13+
14+
describe '.instrument_sdk_operations' do
15+
it 'adds the plugin to each AWS Client' do
16+
Aws::Service1::Client = base_client.dup
17+
Aws::Service2::Client = base_client.dup
18+
19+
expect(Aws::Service1::Client).to receive(:add_plugin).with(Aws::Rails::Notifications)
20+
expect(Aws::Service2::Client).to receive(:add_plugin).with(Aws::Rails::Notifications)
21+
22+
# Ensure other Clients don't get plugin added
23+
allow_any_instance_of(Class).to receive(:add_plugin)
24+
25+
Aws::Rails.instrument_sdk_operations
26+
end
27+
end
28+
29+
describe 'NotificationsInstrument Plugin' do
30+
let(:client) do
31+
Client = base_client.dup
32+
Client.add_plugin(Aws::Rails::Notifications)
33+
Client.new(stub_responses: true, logger: nil)
34+
end
35+
36+
it 'adds instrumentation on each call' do
37+
out = {}
38+
ActiveSupport::Notifications.subscribe(/aws/) do |name, start, finish, id, payload|
39+
out[:name] = name
40+
out[:payload] = payload
41+
end
42+
client.get_send_quota
43+
expect(out[:name]).to eq('get_send_quota.SES.aws')
44+
expect(out[:payload][:context]).to be_a(Seahorse::Client::RequestContext)
45+
end
46+
end
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)