signalfx-rails-instrumentation 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -7
- data/.rubocop.yml +42 -0
- data/Appraisals +16 -13
- data/Gemfile +3 -7
- data/Gemfile.lock +173 -0
- data/LICENSE +2 -2
- data/README.md +118 -164
- data/Rakefile +6 -6
- data/bin/console +1 -1
- data/lib/rails/instrumentation.rb +60 -0
- data/lib/rails/instrumentation/patch.rb +38 -0
- data/lib/rails/instrumentation/subscriber.rb +45 -0
- data/lib/rails/instrumentation/subscribers/action_cable_subscriber.rb +69 -0
- data/lib/rails/instrumentation/subscribers/action_controller_subscriber.rb +172 -0
- data/lib/rails/instrumentation/subscribers/action_mailer_subscriber.rb +63 -0
- data/lib/rails/instrumentation/subscribers/action_view_subscriber.rb +48 -0
- data/lib/rails/instrumentation/subscribers/active_job_subscriber.rb +58 -0
- data/lib/rails/instrumentation/subscribers/active_record_subscriber.rb +45 -0
- data/lib/rails/instrumentation/subscribers/active_storage_subscriber.rb +91 -0
- data/lib/rails/instrumentation/subscribers/active_support_subscriber.rb +74 -0
- data/lib/rails/instrumentation/utils.rb +44 -0
- data/lib/rails/instrumentation/version.rb +5 -0
- data/rails-instrumentation.gemspec +32 -0
- metadata +54 -192
- data/.rspec +0 -2
- data/.ruby-version +0 -1
- data/.travis.yml +0 -25
- data/CHANGELOG.md +0 -47
- data/docker-compose.yml +0 -4
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/rails_32.gemfile +0 -11
- data/gemfiles/rails_4.gemfile +0 -10
- data/gemfiles/rails_40.gemfile +0 -10
- data/gemfiles/rails_41.gemfile +0 -10
- data/gemfiles/rails_42.gemfile +0 -10
- data/gemfiles/rails_5.gemfile +0 -10
- data/gemfiles/rails_50.gemfile +0 -10
- data/gemfiles/rails_51.gemfile +0 -10
- data/lib/rails-tracer.rb +0 -1
- data/lib/rails/action_controller/tracer.rb +0 -100
- data/lib/rails/action_view/tracer.rb +0 -105
- data/lib/rails/active_record/tracer.rb +0 -89
- data/lib/rails/active_support/cache/core_ext.rb +0 -11
- data/lib/rails/active_support/cache/dalli_tracer.rb +0 -106
- data/lib/rails/active_support/cache/manual_tracer.rb +0 -24
- data/lib/rails/active_support/cache/subscriber.rb +0 -62
- data/lib/rails/active_support/cache/tracer.rb +0 -55
- data/lib/rails/defer_notifications.rb +0 -78
- data/lib/rails/rack/tracer.rb +0 -61
- data/lib/rails/span_helpers.rb +0 -24
- data/lib/rails/tracer.rb +0 -38
- data/rails-tracer.gemspec +0 -44
data/Rakefile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
require "rspec/core/rake_task"
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
5
3
|
|
6
|
-
|
4
|
+
RuboCop::RakeTask.new do |task|
|
5
|
+
task.requires << 'rubocop-rspec'
|
6
|
+
end
|
7
7
|
|
8
|
-
task :
|
8
|
+
task default: :spec
|
data/bin/console
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rails/instrumentation/version'
|
2
|
+
require 'rails/instrumentation/patch'
|
3
|
+
require 'rails/instrumentation/subscriber'
|
4
|
+
require 'rails/instrumentation/subscribers/action_controller_subscriber'
|
5
|
+
require 'rails/instrumentation/subscribers/action_view_subscriber'
|
6
|
+
require 'rails/instrumentation/subscribers/active_record_subscriber'
|
7
|
+
require 'rails/instrumentation/subscribers/active_support_subscriber'
|
8
|
+
require 'rails/instrumentation/subscribers/action_mailer_subscriber'
|
9
|
+
require 'rails/instrumentation/subscribers/active_job_subscriber'
|
10
|
+
require 'rails/instrumentation/subscribers/action_cable_subscriber'
|
11
|
+
require 'rails/instrumentation/subscribers/active_storage_subscriber'
|
12
|
+
require 'rails/instrumentation/utils'
|
13
|
+
|
14
|
+
require 'opentracing'
|
15
|
+
|
16
|
+
module Rails
|
17
|
+
module Instrumentation
|
18
|
+
class Error < StandardError; end
|
19
|
+
|
20
|
+
TAGS = {
|
21
|
+
'component' => 'ruby-rails',
|
22
|
+
'instrumentation.version' => Rails::Instrumentation::VERSION
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def self.instrument(tracer: OpenTracing.global_tracer,
|
26
|
+
exclude_events: [])
|
27
|
+
@tracer = tracer
|
28
|
+
|
29
|
+
add_subscribers(exclude_events: exclude_events)
|
30
|
+
Patch.patch_process_action
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.tracer
|
34
|
+
@tracer
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.add_subscribers(exclude_events: [])
|
38
|
+
ActiveRecordSubscriber.subscribe(exclude_events: exclude_events)
|
39
|
+
ActionControllerSubscriber.subscribe(exclude_events: exclude_events)
|
40
|
+
ActionViewSubscriber.subscribe(exclude_events: exclude_events)
|
41
|
+
ActiveSupportSubscriber.subscribe(exclude_events: exclude_events)
|
42
|
+
ActionMailerSubscriber.subscribe(exclude_events: exclude_events)
|
43
|
+
ActiveJobSubscriber.subscribe(exclude_events: exclude_events)
|
44
|
+
ActionCableSubscriber.subscribe(exclude_events: exclude_events)
|
45
|
+
ActiveStorageSubscriber.subscribe(exclude_events: exclude_events)
|
46
|
+
end
|
47
|
+
private_class_method :add_subscribers
|
48
|
+
|
49
|
+
def self.uninstrument
|
50
|
+
ActiveRecordSubscriber.unsubscribe
|
51
|
+
ActionControllerSubscriber.unsubscribe
|
52
|
+
ActionViewSubscriber.unsubscribe
|
53
|
+
ActiveSupportSubscriber.unsubscribe
|
54
|
+
ActionMailerSubscriber.unsubscribe
|
55
|
+
ActiveJobSubscriber.unsubscribe
|
56
|
+
ActionCableSubscriber.unsubscribe
|
57
|
+
ActiveStorageSubscriber.unsubscribe
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rails
|
2
|
+
module Instrumentation
|
3
|
+
module Patch
|
4
|
+
def self.patch_process_action(klass: ::ActionController::Instrumentation)
|
5
|
+
klass.class_eval do
|
6
|
+
alias_method :process_action_original, :process_action
|
7
|
+
|
8
|
+
def process_action(method_name, *args)
|
9
|
+
# this naming scheme 'class.method' is how we ensure that the notification in the
|
10
|
+
# subscriber is the same one
|
11
|
+
name = "#{self.class.name}.#{method_name}"
|
12
|
+
scope = ::Rails::Instrumentation.tracer.start_active_span(name)
|
13
|
+
|
14
|
+
# skip adding tags here. Getting the complete set of information is
|
15
|
+
# easiest in the notification
|
16
|
+
process_action_original(method_name, *args)
|
17
|
+
rescue Error => error
|
18
|
+
if scope
|
19
|
+
scope.span.record_exception(error)
|
20
|
+
end
|
21
|
+
|
22
|
+
raise
|
23
|
+
ensure
|
24
|
+
scope.close
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.restore_process_action(klass: ::ActionController::Instrumentation)
|
30
|
+
klass.class_eval do
|
31
|
+
remove_method :process_action
|
32
|
+
alias_method :process_action, :process_action_original
|
33
|
+
remove_method :process_action_original
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rails
|
2
|
+
module Instrumentation
|
3
|
+
module Subscriber
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def subscribe(exclude_events: [])
|
10
|
+
@subscriber_mutex = Mutex.new if @subscriber_mutex.nil?
|
11
|
+
|
12
|
+
# clear
|
13
|
+
unsubscribe
|
14
|
+
@subscribers = []
|
15
|
+
|
16
|
+
@subscriber_mutex.synchronize do
|
17
|
+
self::EVENTS.each do |event_name|
|
18
|
+
full_name = "#{event_name}.#{self::EVENT_NAMESPACE}"
|
19
|
+
|
20
|
+
next if exclude_events.include? full_name
|
21
|
+
|
22
|
+
@subscribers << Utils.register_subscriber(full_name: full_name,
|
23
|
+
event_name: event_name,
|
24
|
+
handler_module: self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def unsubscribe
|
30
|
+
return if @subscribers.nil? || @subscriber_mutex.nil?
|
31
|
+
|
32
|
+
@subscriber_mutex.synchronize do
|
33
|
+
@subscribers.each do |subscriber|
|
34
|
+
::ActiveSupport::Notifications.unsubscribe(subscriber)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def span_tags(tags)
|
40
|
+
self::BASE_TAGS.clone.merge(tags)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Rails
|
2
|
+
module Instrumentation
|
3
|
+
module ActionCableSubscriber
|
4
|
+
include Subscriber
|
5
|
+
|
6
|
+
EVENT_NAMESPACE = 'action_cable'.freeze
|
7
|
+
|
8
|
+
EVENTS = %w[
|
9
|
+
perform_action
|
10
|
+
transmit
|
11
|
+
transmit_subscription_confirmation
|
12
|
+
transmit_subscription_rejection
|
13
|
+
broadcast
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
# rubocop:disable Style/MutableConstant
|
17
|
+
BASE_TAGS = { 'component' => 'ActionCable' }
|
18
|
+
# rubocop:enable Style/MutableConstant.
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def perform_action(event)
|
22
|
+
tags = span_tags(
|
23
|
+
'channel_class' => event.payload[:channel_class],
|
24
|
+
'action' => event.payload[:action],
|
25
|
+
'data' => event.payload[:data]
|
26
|
+
)
|
27
|
+
|
28
|
+
Utils.trace_notification(event: event, tags: tags)
|
29
|
+
end
|
30
|
+
|
31
|
+
def transmit(event)
|
32
|
+
tags = span_tags(
|
33
|
+
'channel_class' => event.payload[:channel_class],
|
34
|
+
'data' => event.payload[:data],
|
35
|
+
'via' => event.payload[:via]
|
36
|
+
)
|
37
|
+
|
38
|
+
Utils.trace_notification(event: event, tags: tags)
|
39
|
+
end
|
40
|
+
|
41
|
+
def transmit_subscription_confirmation(event)
|
42
|
+
tags = span_tags(
|
43
|
+
'channel_class' => event.payload[:channel_class]
|
44
|
+
)
|
45
|
+
|
46
|
+
Utils.trace_notification(event: event, tags: tags)
|
47
|
+
end
|
48
|
+
|
49
|
+
def transmit_subscription_rejection(event)
|
50
|
+
tags = span_tags(
|
51
|
+
'channel_class' => event.payload[:channel_class]
|
52
|
+
)
|
53
|
+
|
54
|
+
Utils.trace_notification(event: event, tags: tags)
|
55
|
+
end
|
56
|
+
|
57
|
+
def broadcast(event)
|
58
|
+
tags = span_tags(
|
59
|
+
'broadcasting' => event.payload[:broadcasting],
|
60
|
+
'message' => event.payload[:message],
|
61
|
+
'coder' => event.payload[:coder]
|
62
|
+
)
|
63
|
+
|
64
|
+
Utils.trace_notification(event: event, tags: tags)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Rails
|
2
|
+
module Instrumentation
|
3
|
+
module ActionControllerSubscriber
|
4
|
+
include Subscriber
|
5
|
+
|
6
|
+
EVENT_NAMESPACE = 'action_controller'.freeze
|
7
|
+
|
8
|
+
EVENTS = %w[
|
9
|
+
write_fragment
|
10
|
+
read_fragment
|
11
|
+
expire_fragment
|
12
|
+
exist_fragment?
|
13
|
+
write_page
|
14
|
+
expire_page
|
15
|
+
start_processing
|
16
|
+
process_action
|
17
|
+
send_file
|
18
|
+
send_data
|
19
|
+
redirect_to
|
20
|
+
halted_callback
|
21
|
+
unpermitted_parameters
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
# rubocop:disable Style/MutableConstant
|
25
|
+
BASE_TAGS = { 'component' => 'ActionController' }
|
26
|
+
# rubocop:enable Style/MutableConstant.
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def write_fragment(event)
|
30
|
+
tags = span_tags(
|
31
|
+
'key.write' => event.payload[:key]
|
32
|
+
)
|
33
|
+
|
34
|
+
Utils.trace_notification(event: event, tags: tags)
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_fragment(event)
|
38
|
+
tags = span_tags(
|
39
|
+
'key.read' => event.payload[:key]
|
40
|
+
)
|
41
|
+
|
42
|
+
Utils.trace_notification(event: event, tags: tags)
|
43
|
+
end
|
44
|
+
|
45
|
+
def expire_fragment(event)
|
46
|
+
tags = span_tags(
|
47
|
+
'key.expire' => event.payload[:key]
|
48
|
+
)
|
49
|
+
|
50
|
+
Utils.trace_notification(event: event, tags: tags)
|
51
|
+
end
|
52
|
+
|
53
|
+
def exist_fragment?(event)
|
54
|
+
tags = span_tags(
|
55
|
+
'key.exist' => event.payload[:key]
|
56
|
+
)
|
57
|
+
|
58
|
+
Utils.trace_notification(event: event, tags: tags)
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_page(event)
|
62
|
+
tags = span_tags(
|
63
|
+
'path.write' => event.payload[:path]
|
64
|
+
)
|
65
|
+
|
66
|
+
Utils.trace_notification(event: event, tags: tags)
|
67
|
+
end
|
68
|
+
|
69
|
+
def expire_page(event)
|
70
|
+
tags = span_tags(
|
71
|
+
'path.expire' => event.payload[:path]
|
72
|
+
)
|
73
|
+
|
74
|
+
Utils.trace_notification(event: event, tags: tags)
|
75
|
+
end
|
76
|
+
|
77
|
+
def start_processing(event)
|
78
|
+
tags = span_tags(
|
79
|
+
'controller' => event.payload[:controller],
|
80
|
+
'controller.action' => event.payload[:action],
|
81
|
+
'request.params' => event.payload[:params],
|
82
|
+
'request.format' => event.payload[:format],
|
83
|
+
'http.method' => event.payload[:method],
|
84
|
+
'http.url' => event.payload[:path]
|
85
|
+
)
|
86
|
+
|
87
|
+
Utils.trace_notification(event: event, tags: tags)
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_action(event)
|
91
|
+
span_name = "#{event.payload[:controller]}.#{event.payload[:action]}"
|
92
|
+
|
93
|
+
tags = span_tags(
|
94
|
+
'controller' => event.payload[:controller],
|
95
|
+
'controller.action' => event.payload[:action],
|
96
|
+
'request.params' => event.payload[:params],
|
97
|
+
'request.format' => event.payload[:format],
|
98
|
+
'http.method' => event.payload[:method],
|
99
|
+
'http.url' => event.payload[:path],
|
100
|
+
'http.status_code' => event.payload[:status],
|
101
|
+
'view.runtime.ms' => event.payload[:view_runtime],
|
102
|
+
'db.runtime.ms' => event.payload[:db_runtime],
|
103
|
+
'span.kind' => 'server'
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
status_code = event.payload[:status]
|
108
|
+
if status_code.is_a? Integer and status_code >= 500
|
109
|
+
tags['error'] = true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Only append these tags onto the active span created by the patched 'process_action'
|
113
|
+
# Otherwise, create a new span for this notification as usual
|
114
|
+
active_span = ::Rails::Instrumentation.tracer.active_span
|
115
|
+
if active_span && active_span.operation_name == span_name
|
116
|
+
tags.each do |key, value|
|
117
|
+
::Rails::Instrumentation.tracer.active_span.set_tag(key, value)
|
118
|
+
end
|
119
|
+
if event.payload.key? :exception
|
120
|
+
Utils.tag_error(active_span, event.payload)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
Utils.trace_notification(event: event, tags: tags)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def send_file(event)
|
128
|
+
tags = span_tags(
|
129
|
+
'path.send' => event.payload[:path]
|
130
|
+
)
|
131
|
+
|
132
|
+
# there may be additional keys in the payload. It might be worth
|
133
|
+
# trying to tag them too
|
134
|
+
|
135
|
+
Utils.trace_notification(event: event, tags: tags)
|
136
|
+
end
|
137
|
+
|
138
|
+
def send_data(event)
|
139
|
+
# no defined keys, but user keys may be passed in. Might want to add
|
140
|
+
# them at some point
|
141
|
+
|
142
|
+
Utils.trace_notification(event: event, tags: BASE_TAGS)
|
143
|
+
end
|
144
|
+
|
145
|
+
def redirect_to(event)
|
146
|
+
tags = span_tags(
|
147
|
+
'http.status_code' => event.payload[:status],
|
148
|
+
'redirect.url' => event.payload[:location]
|
149
|
+
)
|
150
|
+
|
151
|
+
Utils.trace_notification(event: event, tags: tags)
|
152
|
+
end
|
153
|
+
|
154
|
+
def halted_callback(event)
|
155
|
+
tags = span_tags(
|
156
|
+
'filter' => event.payload[:filter]
|
157
|
+
)
|
158
|
+
|
159
|
+
Utils.trace_notification(event: event, tags: tags)
|
160
|
+
end
|
161
|
+
|
162
|
+
def unpermitted_parameters(event)
|
163
|
+
tags = span_tags(
|
164
|
+
'unpermitted_keys' => event.payload[:keys]
|
165
|
+
)
|
166
|
+
|
167
|
+
Utils.trace_notification(event: event, tags: tags)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Rails
|
2
|
+
module Instrumentation
|
3
|
+
module ActionMailerSubscriber
|
4
|
+
include Subscriber
|
5
|
+
|
6
|
+
EVENT_NAMESPACE = 'action_mailer'.freeze
|
7
|
+
|
8
|
+
EVENTS = %w[
|
9
|
+
receive
|
10
|
+
deliver
|
11
|
+
process
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# rubocop:disable Style/MutableConstant
|
15
|
+
BASE_TAGS = { 'component' => 'ActionMailer' }
|
16
|
+
# rubocop:enable Style/MutableConstant.
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def receive(event)
|
20
|
+
tags = span_tags(
|
21
|
+
'mailer' => event.payload[:mailer],
|
22
|
+
'message.id' => event.payload[:message_id],
|
23
|
+
'message.subject' => event.payload[:subject],
|
24
|
+
'message.to' => event.payload[:to],
|
25
|
+
'message.from' => event.payload[:from],
|
26
|
+
'message.bcc' => event.payload[:bcc],
|
27
|
+
'message.cc' => event.payload[:cc],
|
28
|
+
'message.date' => event.payload[:date],
|
29
|
+
'message.body' => event.payload[:mail]
|
30
|
+
)
|
31
|
+
|
32
|
+
Utils.trace_notification(event: event, tags: tags)
|
33
|
+
end
|
34
|
+
|
35
|
+
def deliver(event)
|
36
|
+
tags = span_tags(
|
37
|
+
'mailer' => event.payload[:mailer],
|
38
|
+
'message.id' => event.payload[:message_id],
|
39
|
+
'message.subject' => event.payload[:subject],
|
40
|
+
'message.to' => event.payload[:to],
|
41
|
+
'message.from' => event.payload[:from],
|
42
|
+
'message.bcc' => event.payload[:bcc],
|
43
|
+
'message.cc' => event.payload[:cc],
|
44
|
+
'message.date' => event.payload[:date],
|
45
|
+
'message.body' => event.payload[:mail]
|
46
|
+
)
|
47
|
+
|
48
|
+
Utils.trace_notification(event: event, tags: tags)
|
49
|
+
end
|
50
|
+
|
51
|
+
def process(event)
|
52
|
+
tags = span_tags(
|
53
|
+
'mailer' => event.payload[:mailer],
|
54
|
+
'action' => event.payload[:action],
|
55
|
+
'args' => event.payload[:args]
|
56
|
+
)
|
57
|
+
|
58
|
+
Utils.trace_notification(event: event, tags: tags)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|