signalfx-rails-instrumentation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +25 -0
- data/Appraisals +24 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +10 -0
- data/LICENSE +201 -0
- data/README.md +198 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +4 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_32.gemfile +11 -0
- data/gemfiles/rails_4.gemfile +10 -0
- data/gemfiles/rails_40.gemfile +10 -0
- data/gemfiles/rails_41.gemfile +10 -0
- data/gemfiles/rails_42.gemfile +10 -0
- data/gemfiles/rails_5.gemfile +10 -0
- data/gemfiles/rails_50.gemfile +10 -0
- data/gemfiles/rails_51.gemfile +10 -0
- data/lib/rails-tracer.rb +1 -0
- data/lib/rails/action_controller/tracer.rb +100 -0
- data/lib/rails/action_view/tracer.rb +105 -0
- data/lib/rails/active_record/tracer.rb +89 -0
- data/lib/rails/active_support/cache/core_ext.rb +11 -0
- data/lib/rails/active_support/cache/dalli_tracer.rb +106 -0
- data/lib/rails/active_support/cache/manual_tracer.rb +24 -0
- data/lib/rails/active_support/cache/subscriber.rb +62 -0
- data/lib/rails/active_support/cache/tracer.rb +55 -0
- data/lib/rails/defer_notifications.rb +78 -0
- data/lib/rails/rack/tracer.rb +61 -0
- data/lib/rails/span_helpers.rb +24 -0
- data/lib/rails/tracer.rb +38 -0
- data/rails-tracer.gemspec +44 -0
- metadata +330 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rails/tracer"
|
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
data/docker-compose.yml
ADDED
data/lib/rails-tracer.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rails/tracer'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
|
2
|
+
module ActionController
|
3
|
+
module Tracer
|
4
|
+
COMPONENT = "ActionController".freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil)
|
8
|
+
@subscribers = []
|
9
|
+
@subscribers << ::ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |*args|
|
10
|
+
ActionController::Tracer.start_processing(tracer: tracer, active_span: active_span, args: args)
|
11
|
+
end
|
12
|
+
@subscribers << ::ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
|
13
|
+
ActionController::Tracer.process_action(tracer: tracer, active_span: active_span, args: args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def disable
|
18
|
+
@subscribers.each do |subscriber|
|
19
|
+
::ActiveSupport::Notifications.unsubscribe(subscriber)
|
20
|
+
end
|
21
|
+
@subscribers.clear
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_processing(tracer: OpenTracing.global_tracer, active_span: nil, args: {})
|
26
|
+
event, start, finish, id, payload = *args
|
27
|
+
|
28
|
+
# extract the rack context, if it exists
|
29
|
+
# it seems like this might be the earliest place env is available
|
30
|
+
rack_span = Rails::Tracer::SpanHelpers.rack_span(payload)
|
31
|
+
Rails::Tracer::Defer.add_parent(id, rack_span)
|
32
|
+
|
33
|
+
path = payload.fetch(:path)
|
34
|
+
name = "#{payload.fetch(:controller)}##{payload.fetch(:action)} #{event} #{path}"
|
35
|
+
tags = {
|
36
|
+
'component' => COMPONENT,
|
37
|
+
'span.kind' => 'client',
|
38
|
+
'http.method' => payload.fetch(:method),
|
39
|
+
'http.path' => path,
|
40
|
+
}
|
41
|
+
|
42
|
+
if !Rails::Tracer::Defer.enabled
|
43
|
+
span = tracer.start_span(name,
|
44
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
45
|
+
start_time: start,
|
46
|
+
tags: tags)
|
47
|
+
|
48
|
+
span.finish(end_time: finish)
|
49
|
+
else
|
50
|
+
spaninfo = {
|
51
|
+
'event' => event,
|
52
|
+
'name' => name,
|
53
|
+
'start' => start,
|
54
|
+
'finish' => finish,
|
55
|
+
'tags' => tags,
|
56
|
+
}
|
57
|
+
|
58
|
+
Rails::Tracer::Defer.defer_span(id: id, spaninfo: spaninfo)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_action(tracer: OpenTracing.global_tracer, active_span: nil, args: {})
|
63
|
+
event, start, finish, id, payload = *args
|
64
|
+
|
65
|
+
path = payload.fetch(:path)
|
66
|
+
name = "#{payload.fetch(:controller)}##{payload.fetch(:action)} #{path}"
|
67
|
+
tags = {
|
68
|
+
'component' => COMPONENT,
|
69
|
+
'span.kind' => 'client',
|
70
|
+
'http.method' => payload.fetch(:method),
|
71
|
+
'http.status_code' => payload.fetch(:status),
|
72
|
+
'http.path' => path,
|
73
|
+
'view.runtime' => payload.fetch(:view_runtime),
|
74
|
+
'db.runtime' => payload.fetch(:db_runtime),
|
75
|
+
}
|
76
|
+
|
77
|
+
if !Rails::Tracer::Defer.enabled
|
78
|
+
# write out the span
|
79
|
+
span = tracer.start_span(name,
|
80
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
81
|
+
start_time: start,
|
82
|
+
tags: tags)
|
83
|
+
|
84
|
+
span.finish(end_time: finish)
|
85
|
+
else
|
86
|
+
# defer the spans if full_trace is configured
|
87
|
+
spaninfo = {
|
88
|
+
'event' => event,
|
89
|
+
'name' => name,
|
90
|
+
'start' => start,
|
91
|
+
'finish' => finish,
|
92
|
+
'tags' => tags,
|
93
|
+
}
|
94
|
+
|
95
|
+
Rails::Tracer::Defer.defer_span(id: id, spaninfo: spaninfo)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
module ActionView
|
3
|
+
module Tracer
|
4
|
+
COMPONENT = "ActionView".freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil)
|
8
|
+
@subscribers = []
|
9
|
+
@subscribers << ::ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
|
10
|
+
ActionView::Tracer.render_template(tracer: tracer, active_span: active_span, args: args)
|
11
|
+
end
|
12
|
+
@subscribers << ::ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
|
13
|
+
ActionView::Tracer.render_partial(tracer: tracer, active_span: active_span, args: args)
|
14
|
+
end
|
15
|
+
@subscribers << ::ActiveSupport::Notifications.subscribe('render_collection.action_view') do |*args|
|
16
|
+
ActionView::Tracer.render_collection(tracer: tracer, active_span: active_span, args: args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def disable
|
21
|
+
@subscribers.each do |subscriber|
|
22
|
+
::ActiveSupport::Notifications.unsubscribe(subscriber)
|
23
|
+
end
|
24
|
+
@subscribers = []
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_template(tracer: OpenTracing.global_tracer, active_span: nil, args: {})
|
29
|
+
event, start, finish, id, payload = *args
|
30
|
+
|
31
|
+
tags = {
|
32
|
+
'component' => COMPONENT,
|
33
|
+
'span.kind' => 'client',
|
34
|
+
'template_path' => payload.fetch(:identifier),
|
35
|
+
'layout' => payload.fetch(:layout),
|
36
|
+
}
|
37
|
+
|
38
|
+
handle_notification(tracer: tracer,
|
39
|
+
active_span: active_span,
|
40
|
+
id: id,
|
41
|
+
name: event,
|
42
|
+
tags: tags,
|
43
|
+
start: start,
|
44
|
+
finish: finish)
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_partial(tracer: OpenTracing.global_tracer, active_span: nil, args: {})
|
48
|
+
event, start, finish, id, payload = *args
|
49
|
+
|
50
|
+
tags = {
|
51
|
+
'component' => COMPONENT,
|
52
|
+
'span.kind' => 'client',
|
53
|
+
'template_path' => payload.fetch(:identifier),
|
54
|
+
}
|
55
|
+
|
56
|
+
handle_notification(tracer: tracer,
|
57
|
+
active_span: active_span,
|
58
|
+
id: id,
|
59
|
+
name: event,
|
60
|
+
tags: tags,
|
61
|
+
start: start,
|
62
|
+
finish: finish)
|
63
|
+
end
|
64
|
+
|
65
|
+
def render_collection(tracer: OpenTracing.global_tracer, active_span: nil, args: {})
|
66
|
+
event, start, finish, id, payload = *args
|
67
|
+
|
68
|
+
tags = {
|
69
|
+
'component' => COMPONENT,
|
70
|
+
'span.kind' => 'client',
|
71
|
+
'template_path' => payload.fetch(:identifier),
|
72
|
+
'collection_size' => payload.fetch(:count),
|
73
|
+
}
|
74
|
+
|
75
|
+
handle_notification(tracer: tracer,
|
76
|
+
active_span: active_span,
|
77
|
+
id: id,
|
78
|
+
name: event,
|
79
|
+
tags: tags,
|
80
|
+
start: start,
|
81
|
+
finish: finish)
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_notification(tracer:, active_span:, id:, name:, tags:, start:, finish:)
|
85
|
+
if !Rails::Tracer::Defer.enabled
|
86
|
+
span = tracer.start_span(name,
|
87
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
88
|
+
start_time: start,
|
89
|
+
tags: tags)
|
90
|
+
|
91
|
+
span.finish(end_time: finish)
|
92
|
+
else
|
93
|
+
spaninfo = {
|
94
|
+
'event' => name,
|
95
|
+
'name' => name,
|
96
|
+
'start' => start,
|
97
|
+
'finish' => finish,
|
98
|
+
'tags' => tags,
|
99
|
+
}
|
100
|
+
Rails::Tracer::Defer.defer_span(id: id, spaninfo: spaninfo)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Tracer
|
3
|
+
DEFAULT_OPERATION_NAME = "sql.query".freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil)
|
7
|
+
clear_subscribers
|
8
|
+
@subscriber = ::ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
9
|
+
ActiveRecord::Tracer.sql(tracer: tracer, active_span: active_span, args: args)
|
10
|
+
end
|
11
|
+
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def disable
|
16
|
+
if @subscriber
|
17
|
+
ActiveSupport::Notifications.unsubscribe(@subscriber)
|
18
|
+
@subscriber = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
alias :clear_subscribers :disable
|
24
|
+
|
25
|
+
def sql(tracer: OpenTracing.global_tracer, active_span: nil, args:)
|
26
|
+
event, start, finish, id, payload = *args
|
27
|
+
|
28
|
+
connection_config = ::ActiveRecord::Base.connection_config
|
29
|
+
name = payload.fetch(:name)
|
30
|
+
tags = {
|
31
|
+
'component' => 'ActiveRecord',
|
32
|
+
'span.kind' => 'client',
|
33
|
+
'db.user' => connection_config.fetch(:username, 'unknown'),
|
34
|
+
'db.instance' => connection_config.fetch(:database),
|
35
|
+
'db.vendor' => connection_config.fetch(:adapter),
|
36
|
+
'db.connection_id' => payload.fetch(:connection_id, 'unknown'),
|
37
|
+
'db.cached' => payload.fetch(:cached, false),
|
38
|
+
'db.statement' => payload.fetch(:sql),
|
39
|
+
'db.type' => 'sql'
|
40
|
+
}
|
41
|
+
|
42
|
+
if !Rails::Tracer::Defer.enabled
|
43
|
+
span = tracer.start_span(name || DEFAULT_OPERATION_NAME,
|
44
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
45
|
+
start_time: start,
|
46
|
+
tags: tags)
|
47
|
+
|
48
|
+
if payload[:exception]
|
49
|
+
Rails::Tracer::SpanHelpers.set_error(span, payload[:exception_object] || payload[:exception])
|
50
|
+
end
|
51
|
+
|
52
|
+
span.finish(end_time: finish)
|
53
|
+
else
|
54
|
+
spaninfo = {
|
55
|
+
'event' => event,
|
56
|
+
'name' => name || DEFAULT_OPERATION_NAME,
|
57
|
+
'start' => start,
|
58
|
+
'finish' => finish,
|
59
|
+
'tags' => tags,
|
60
|
+
}
|
61
|
+
|
62
|
+
# errors aren't being propagated yet this way...
|
63
|
+
|
64
|
+
Rails::Tracer::Defer.defer_span(id: id, spaninfo: spaninfo)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_span(operation_name, tracer: OpenTracing.global_tracer, active_span: nil, start_time: Time.now, **fields)
|
69
|
+
connection_config = ::ActiveRecord::Base.connection_config
|
70
|
+
|
71
|
+
span = tracer.start_span(operation_name || DEFAULT_OPERATION_NAME,
|
72
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
73
|
+
start_time: start_time,
|
74
|
+
tags: {
|
75
|
+
'component' => 'ActiveRecord',
|
76
|
+
'span.kind' => 'client',
|
77
|
+
'db.user' => connection_config.fetch(:username, 'unknown'),
|
78
|
+
'db.instance' => connection_config.fetch(:database),
|
79
|
+
'db.vendor' => connection_config.fetch(:adapter),
|
80
|
+
'db.connection_id' => fields.fetch(:connection_id, 'unknown'),
|
81
|
+
'db.cached' => fields.fetch(:cached, false),
|
82
|
+
'db.statement' => fields.fetch(:sql),
|
83
|
+
'db.type' => 'sql'
|
84
|
+
})
|
85
|
+
span
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Cache
|
3
|
+
class Store
|
4
|
+
# See the PR https://github.com/rails/rails/pull/15943/files
|
5
|
+
# In order to make the instrumentation to work we need to override the original implementation
|
6
|
+
def self.instrument
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|