rails-instrumentation 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +42 -0
- data/Appraisals +19 -0
- data/Gemfile +6 -0
- data/LICENSE +201 -0
- data/README.md +152 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rails/instrumentation.rb +60 -0
- data/lib/rails/instrumentation/patch.rb +42 -0
- data/lib/rails/instrumentation/subscriber.rb +41 -0
- data/lib/rails/instrumentation/subscribers/action_cable_subscriber.rb +65 -0
- data/lib/rails/instrumentation/subscribers/action_controller_subscriber.rb +158 -0
- data/lib/rails/instrumentation/subscribers/action_mailer_subscriber.rb +59 -0
- data/lib/rails/instrumentation/subscribers/action_view_subscriber.rb +44 -0
- data/lib/rails/instrumentation/subscribers/active_job_subscriber.rb +54 -0
- data/lib/rails/instrumentation/subscribers/active_record_subscriber.rb +39 -0
- data/lib/rails/instrumentation/subscribers/active_storage_subscriber.rb +87 -0
- data/lib/rails/instrumentation/subscribers/active_support_subscriber.rb +70 -0
- data/lib/rails/instrumentation/utils.rb +47 -0
- data/lib/rails/instrumentation/version.rb +5 -0
- data/rails-instrumentation.gemspec +32 -0
- metadata +191 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rails/instrumentation"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -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,42 @@
|
|
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 'method.class' is how we ensure that the notification in the
|
10
|
+
# subscriber is the same one
|
11
|
+
name = "#{method_name}.#{self.class.name}"
|
12
|
+
puts ::Rails::Instrumentation.tracer
|
13
|
+
scope = ::Rails::Instrumentation.tracer.start_active_span(name)
|
14
|
+
|
15
|
+
# skip adding tags here. Getting the complete set of information is
|
16
|
+
# easiest in the notification
|
17
|
+
|
18
|
+
process_action_original(method_name, *args)
|
19
|
+
rescue Error => error
|
20
|
+
if scope
|
21
|
+
scope.span.set_tag('error', true)
|
22
|
+
scope.span.log_kv(key: 'message', value: error.message)
|
23
|
+
end
|
24
|
+
|
25
|
+
raise
|
26
|
+
ensure
|
27
|
+
scope.close
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.restore_process_action(klass: ::ActionController::Instrumentation)
|
33
|
+
puts klass.respond_to? :process_action_original, true
|
34
|
+
klass.class_eval do
|
35
|
+
remove_method :process_action
|
36
|
+
alias_method :process_action, :process_action_original
|
37
|
+
remove_method :process_action_original
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
class << self
|
17
|
+
def perform_action(event)
|
18
|
+
tags = {
|
19
|
+
'channel_class' => event.payload[:channel_class],
|
20
|
+
'action' => event.payload[:action],
|
21
|
+
'data' => event.payload[:data]
|
22
|
+
}
|
23
|
+
|
24
|
+
Utils.trace_notification(event: event, tags: tags)
|
25
|
+
end
|
26
|
+
|
27
|
+
def transmit(event)
|
28
|
+
tags = {
|
29
|
+
'channel_class' => event.payload[:channel_class],
|
30
|
+
'data' => event.payload[:data],
|
31
|
+
'via' => event.payload[:via]
|
32
|
+
}
|
33
|
+
|
34
|
+
Utils.trace_notification(event: event, tags: tags)
|
35
|
+
end
|
36
|
+
|
37
|
+
def transmit_subscription_confirmation(event)
|
38
|
+
tags = {
|
39
|
+
'channel_class' => event.payload[:channel_class]
|
40
|
+
}
|
41
|
+
|
42
|
+
Utils.trace_notification(event: event, tags: tags)
|
43
|
+
end
|
44
|
+
|
45
|
+
def transmit_subscription_rejection(event)
|
46
|
+
tags = {
|
47
|
+
'channel_class' => event.payload[:channel_class]
|
48
|
+
}
|
49
|
+
|
50
|
+
Utils.trace_notification(event: event, tags: tags)
|
51
|
+
end
|
52
|
+
|
53
|
+
def broadcast(event)
|
54
|
+
tags = {
|
55
|
+
'broadcasting' => event.payload[:broadcasting],
|
56
|
+
'message' => event.payload[:message],
|
57
|
+
'coder' => event.payload[:coder]
|
58
|
+
}
|
59
|
+
|
60
|
+
Utils.trace_notification(event: event, tags: tags)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,158 @@
|
|
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
|
+
class << self
|
25
|
+
def write_fragment(event)
|
26
|
+
tags = {
|
27
|
+
'key.write' => event.payload[:key]
|
28
|
+
}
|
29
|
+
|
30
|
+
Utils.trace_notification(event: event, tags: tags)
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_fragment(event)
|
34
|
+
tags = {
|
35
|
+
'key.read' => event.payload[:key]
|
36
|
+
}
|
37
|
+
|
38
|
+
Utils.trace_notification(event: event, tags: tags)
|
39
|
+
end
|
40
|
+
|
41
|
+
def expire_fragment(event)
|
42
|
+
tags = {
|
43
|
+
'key.expire' => event.payload[:key]
|
44
|
+
}
|
45
|
+
|
46
|
+
Utils.trace_notification(event: event, tags: tags)
|
47
|
+
end
|
48
|
+
|
49
|
+
def exist_fragment?(event)
|
50
|
+
tags = {
|
51
|
+
'key.exist' => event.payload[:key]
|
52
|
+
}
|
53
|
+
|
54
|
+
Utils.trace_notification(event: event, tags: tags)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write_page(event)
|
58
|
+
tags = {
|
59
|
+
'path.write' => event.payload[:path]
|
60
|
+
}
|
61
|
+
|
62
|
+
Utils.trace_notification(event: event, tags: tags)
|
63
|
+
end
|
64
|
+
|
65
|
+
def expire_page(event)
|
66
|
+
tags = {
|
67
|
+
'path.expire' => event.payload[:path]
|
68
|
+
}
|
69
|
+
|
70
|
+
Utils.trace_notification(event: event, tags: tags)
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_processing(event)
|
74
|
+
tags = {
|
75
|
+
'controller' => event.payload[:controller],
|
76
|
+
'controller.action' => event.payload[:action],
|
77
|
+
'request.params' => event.payload[:params],
|
78
|
+
'request.format' => event.payload[:format],
|
79
|
+
'http.method' => event.payload[:method],
|
80
|
+
'http.url' => event.payload[:path]
|
81
|
+
}
|
82
|
+
|
83
|
+
Utils.trace_notification(event: event, tags: tags)
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_action(event)
|
87
|
+
span_name = "#{event.payload[:action]}.#{event.payload[:controller]}"
|
88
|
+
|
89
|
+
tags = {
|
90
|
+
'controller' => event.payload[:controller],
|
91
|
+
'controller.action' => event.payload[:action],
|
92
|
+
'request.params' => event.payload[:params],
|
93
|
+
'request.format' => event.payload[:format],
|
94
|
+
'http.method' => event.payload[:method],
|
95
|
+
'http.url' => event.payload[:path],
|
96
|
+
'http.status_code' => event.payload[:status],
|
97
|
+
'view.runtime.ms' => event.payload[:view_runtime],
|
98
|
+
'db.runtime.ms' => event.payload[:db_runtime]
|
99
|
+
}
|
100
|
+
|
101
|
+
# Only append these tags onto the active span created by the patched 'process_action'
|
102
|
+
# Otherwise, create a new span for this notification as usual
|
103
|
+
active_span = ::Rails::Instrumentation.tracer.active_span
|
104
|
+
if active_span && active_span.operation_name == span_name
|
105
|
+
tags.each do |key, value|
|
106
|
+
::Rails::Instrumentation.tracer.active_span.set_tag(key, value)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
Utils.trace_notification(event: event, tags: tags)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def send_file(event)
|
114
|
+
tags = {
|
115
|
+
'path.send' => event.payload[:path]
|
116
|
+
}
|
117
|
+
|
118
|
+
# there may be additional keys in the payload. It might be worth
|
119
|
+
# trying to tag them too
|
120
|
+
|
121
|
+
Utils.trace_notification(event: event, tags: tags)
|
122
|
+
end
|
123
|
+
|
124
|
+
def send_data(event)
|
125
|
+
# no defined keys, but user keys may be passed in. Might want to add
|
126
|
+
# them at some point
|
127
|
+
|
128
|
+
Utils.trace_notification(event: event, tags: tags)
|
129
|
+
end
|
130
|
+
|
131
|
+
def redirect_to(event)
|
132
|
+
tags = {
|
133
|
+
'http.status_code' => event.payload[:status],
|
134
|
+
'redirect.url' => event.payload[:location]
|
135
|
+
}
|
136
|
+
|
137
|
+
Utils.trace_notification(event: event, tags: tags)
|
138
|
+
end
|
139
|
+
|
140
|
+
def halted_callback(event)
|
141
|
+
tags = {
|
142
|
+
'filter' => event.payload[:filter]
|
143
|
+
}
|
144
|
+
|
145
|
+
Utils.trace_notification(event: event, tags: tags)
|
146
|
+
end
|
147
|
+
|
148
|
+
def unpermitted_parameters(event)
|
149
|
+
tags = {
|
150
|
+
'unpermitted_keys' => event.payload[:keys]
|
151
|
+
}
|
152
|
+
|
153
|
+
Utils.trace_notification(event: event, tags: tags)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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
|
+
class << self
|
15
|
+
def receive(event)
|
16
|
+
tags = {
|
17
|
+
'mailer' => event.payload[:mailer],
|
18
|
+
'message.id' => event.payload[:message_id],
|
19
|
+
'message.subject' => event.payload[:subject],
|
20
|
+
'message.to' => event.payload[:to],
|
21
|
+
'message.from' => event.payload[:from],
|
22
|
+
'message.bcc' => event.payload[:bcc],
|
23
|
+
'message.cc' => event.payload[:cc],
|
24
|
+
'message.date' => event.payload[:date],
|
25
|
+
'message.body' => event.payload[:mail]
|
26
|
+
}
|
27
|
+
|
28
|
+
Utils.trace_notification(event: event, tags: tags)
|
29
|
+
end
|
30
|
+
|
31
|
+
def deliver(event)
|
32
|
+
tags = {
|
33
|
+
'mailer' => event.payload[:mailer],
|
34
|
+
'message.id' => event.payload[:message_id],
|
35
|
+
'message.subject' => event.payload[:subject],
|
36
|
+
'message.to' => event.payload[:to],
|
37
|
+
'message.from' => event.payload[:from],
|
38
|
+
'message.bcc' => event.payload[:bcc],
|
39
|
+
'message.cc' => event.payload[:cc],
|
40
|
+
'message.date' => event.payload[:date],
|
41
|
+
'message.body' => event.payload[:mail]
|
42
|
+
}
|
43
|
+
|
44
|
+
Utils.trace_notification(event: event, tags: tags)
|
45
|
+
end
|
46
|
+
|
47
|
+
def process(event)
|
48
|
+
tags = {
|
49
|
+
'mailer' => event.payload[:mailer],
|
50
|
+
'action' => event.payload[:action],
|
51
|
+
'args' => event.payload[:args]
|
52
|
+
}
|
53
|
+
|
54
|
+
Utils.trace_notification(event: event, tags: tags)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|