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.
Files changed (53) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -7
  3. data/.rubocop.yml +42 -0
  4. data/Appraisals +16 -13
  5. data/Gemfile +3 -7
  6. data/Gemfile.lock +173 -0
  7. data/LICENSE +2 -2
  8. data/README.md +118 -164
  9. data/Rakefile +6 -6
  10. data/bin/console +1 -1
  11. data/lib/rails/instrumentation.rb +60 -0
  12. data/lib/rails/instrumentation/patch.rb +38 -0
  13. data/lib/rails/instrumentation/subscriber.rb +45 -0
  14. data/lib/rails/instrumentation/subscribers/action_cable_subscriber.rb +69 -0
  15. data/lib/rails/instrumentation/subscribers/action_controller_subscriber.rb +172 -0
  16. data/lib/rails/instrumentation/subscribers/action_mailer_subscriber.rb +63 -0
  17. data/lib/rails/instrumentation/subscribers/action_view_subscriber.rb +48 -0
  18. data/lib/rails/instrumentation/subscribers/active_job_subscriber.rb +58 -0
  19. data/lib/rails/instrumentation/subscribers/active_record_subscriber.rb +45 -0
  20. data/lib/rails/instrumentation/subscribers/active_storage_subscriber.rb +91 -0
  21. data/lib/rails/instrumentation/subscribers/active_support_subscriber.rb +74 -0
  22. data/lib/rails/instrumentation/utils.rb +44 -0
  23. data/lib/rails/instrumentation/version.rb +5 -0
  24. data/rails-instrumentation.gemspec +32 -0
  25. metadata +54 -192
  26. data/.rspec +0 -2
  27. data/.ruby-version +0 -1
  28. data/.travis.yml +0 -25
  29. data/CHANGELOG.md +0 -47
  30. data/docker-compose.yml +0 -4
  31. data/gemfiles/.bundle/config +0 -2
  32. data/gemfiles/rails_32.gemfile +0 -11
  33. data/gemfiles/rails_4.gemfile +0 -10
  34. data/gemfiles/rails_40.gemfile +0 -10
  35. data/gemfiles/rails_41.gemfile +0 -10
  36. data/gemfiles/rails_42.gemfile +0 -10
  37. data/gemfiles/rails_5.gemfile +0 -10
  38. data/gemfiles/rails_50.gemfile +0 -10
  39. data/gemfiles/rails_51.gemfile +0 -10
  40. data/lib/rails-tracer.rb +0 -1
  41. data/lib/rails/action_controller/tracer.rb +0 -100
  42. data/lib/rails/action_view/tracer.rb +0 -105
  43. data/lib/rails/active_record/tracer.rb +0 -89
  44. data/lib/rails/active_support/cache/core_ext.rb +0 -11
  45. data/lib/rails/active_support/cache/dalli_tracer.rb +0 -106
  46. data/lib/rails/active_support/cache/manual_tracer.rb +0 -24
  47. data/lib/rails/active_support/cache/subscriber.rb +0 -62
  48. data/lib/rails/active_support/cache/tracer.rb +0 -55
  49. data/lib/rails/defer_notifications.rb +0 -78
  50. data/lib/rails/rack/tracer.rb +0 -61
  51. data/lib/rails/span_helpers.rb +0 -24
  52. data/lib/rails/tracer.rb +0 -38
  53. data/rails-tracer.gemspec +0 -44
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rubocop/rake_task'
5
3
 
6
- RSpec::Core::RakeTask.new(:spec)
4
+ RuboCop::RakeTask.new do |task|
5
+ task.requires << 'rubocop-rspec'
6
+ end
7
7
 
8
- task :default => :spec
8
+ task default: :spec
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "rails/tracer"
4
+ require "rails/instrumentation"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -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