signalfx-rails-instrumentation 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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