signalfx-rails-instrumentation 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|