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
@@ -0,0 +1,106 @@
|
|
1
|
+
require "method/tracer"
|
2
|
+
|
3
|
+
module Dalli
|
4
|
+
class Tracer
|
5
|
+
class << self
|
6
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil)
|
7
|
+
::Dalli::Server.class_eval do
|
8
|
+
@tracer = tracer
|
9
|
+
@active_span = active_span
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :tracer, :active_span
|
13
|
+
end
|
14
|
+
|
15
|
+
def tracer
|
16
|
+
self.class.tracer
|
17
|
+
end
|
18
|
+
|
19
|
+
def active_span
|
20
|
+
self.class.active_span
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :request_without_instrumentation, :request
|
24
|
+
|
25
|
+
def request(op, *args)
|
26
|
+
tags = {
|
27
|
+
'component' => 'Dalli::Server',
|
28
|
+
'span.kind' => 'client',
|
29
|
+
'db.statement' => op.to_s,
|
30
|
+
'db.type' => 'memcached',
|
31
|
+
'peer.hostname' => hostname,
|
32
|
+
'peer.port' => port,
|
33
|
+
'peer.weight' => weight
|
34
|
+
}
|
35
|
+
# Method::Tracer.trace("Dalli::Server#request", tracer: tracer, child_of: active_span, tags: tags) do
|
36
|
+
# request_without_instrumentation(op, *args)
|
37
|
+
# end
|
38
|
+
parent_span = active_span.respond_to?(:call) ? active_span.call : active_span
|
39
|
+
span = tracer.start_span("Dalli::Server#request", child_of: parent_span, tags: tags)
|
40
|
+
|
41
|
+
begin
|
42
|
+
request_without_instrumentation(op, *args)
|
43
|
+
rescue => e
|
44
|
+
if span
|
45
|
+
span.set_tag("error", true)
|
46
|
+
span.log_kv(key: "message", value: error.message)
|
47
|
+
end
|
48
|
+
ensure
|
49
|
+
span.finish() if span
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
::Dalli::Client.class_eval do
|
55
|
+
@tracer = tracer
|
56
|
+
@active_span = active_span
|
57
|
+
|
58
|
+
class << self
|
59
|
+
attr_reader :tracer, :active_span
|
60
|
+
end
|
61
|
+
|
62
|
+
def tracer
|
63
|
+
self.class.tracer
|
64
|
+
end
|
65
|
+
|
66
|
+
def active_span
|
67
|
+
self.class.active_span
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :perform_without_instrumentation, :perform
|
71
|
+
|
72
|
+
def perform(*args)
|
73
|
+
parent_span = active_span.respond_to?(:call) ? active_span.call : active_span
|
74
|
+
span = tracer.start_span("Dalli::Client#perform", child_of: active_span, tags: {})
|
75
|
+
|
76
|
+
begin
|
77
|
+
perform_without_instrumentation(*args)
|
78
|
+
rescue => error
|
79
|
+
if span
|
80
|
+
span.set_tag("error", true)
|
81
|
+
span.log_kv(key: "message", value: error.message)
|
82
|
+
end
|
83
|
+
ensure
|
84
|
+
span.finish() if span
|
85
|
+
end
|
86
|
+
# Method::Tracer.trace("Dalli::Client#perform", tracer: tracer, child_of: active_span) do
|
87
|
+
# perform_without_instrumentation(*args)
|
88
|
+
# end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove_instrumentation
|
94
|
+
::Dalli::Server.class_eval do
|
95
|
+
alias_method :request, :request_without_instrumentation
|
96
|
+
remove_method :request_without_instrumentation
|
97
|
+
end
|
98
|
+
|
99
|
+
::Dalli::Client.class_eval do
|
100
|
+
alias_method :perform, :perform_without_instrumentation
|
101
|
+
remove_method :perform_without_instrumentation
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Cache
|
3
|
+
module Tracer
|
4
|
+
class << self
|
5
|
+
def start_span(operation_name, tracer: OpenTracing.global_tracer, active_span: nil, start_time: Time.now, event:, **fields)
|
6
|
+
span = tracer.start_span(operation_name,
|
7
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
8
|
+
start_time: start_time,
|
9
|
+
tags: {
|
10
|
+
'component' => 'ActiveSupport::Cache',
|
11
|
+
'span.kind' => 'client',
|
12
|
+
'cache.key' => fields.fetch(:key, 'unknown')
|
13
|
+
})
|
14
|
+
|
15
|
+
if event == 'read'
|
16
|
+
span.set_tag('cache.hit', fields.fetch(:hit, false))
|
17
|
+
end
|
18
|
+
|
19
|
+
span
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Cache
|
3
|
+
module Tracer
|
4
|
+
class Subscriber
|
5
|
+
attr_reader :tracer, :active_span, :event, :operation_name
|
6
|
+
|
7
|
+
def initialize(tracer: OpenTracing.global_tracer, active_span: nil, event:)
|
8
|
+
@tracer = tracer
|
9
|
+
@active_span = active_span
|
10
|
+
@event = event
|
11
|
+
@operation_name = "cache.#{event}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# For compatibility with Rails 3.2
|
15
|
+
def call(*args)
|
16
|
+
_, start, finish, _, payload = *args
|
17
|
+
|
18
|
+
span = Tracer.start_span(operation_name,
|
19
|
+
event: event,
|
20
|
+
tracer: tracer,
|
21
|
+
active_span: active_span,
|
22
|
+
start_time: start,
|
23
|
+
**payload)
|
24
|
+
|
25
|
+
if payload[:exception]
|
26
|
+
Rails::Tracer::SpanHelpers.set_error(span, payload[:exception_object] || payload[:exception])
|
27
|
+
end
|
28
|
+
|
29
|
+
span.finish(end_time: finish)
|
30
|
+
end
|
31
|
+
|
32
|
+
def start(name, _, payload)
|
33
|
+
span = tracer.start_span(operation_name,
|
34
|
+
child_of: active_span.respond_to?(:call) ? active_span.call : active_span,
|
35
|
+
tags: {
|
36
|
+
'component' => 'ActiveSupport::Cache',
|
37
|
+
'span.kind' => 'client'
|
38
|
+
})
|
39
|
+
|
40
|
+
payload[:__OT_SPAN__] = span
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish(name, _, payload)
|
44
|
+
span = payload[:__OT_SPAN__]
|
45
|
+
return unless span
|
46
|
+
|
47
|
+
span.set_tag('cache.key', payload.fetch(:key, 'unknown'))
|
48
|
+
|
49
|
+
if event == 'read'
|
50
|
+
span.set_tag('cache.hit', payload.fetch(:hit, false))
|
51
|
+
end
|
52
|
+
|
53
|
+
if payload[:exception]
|
54
|
+
Rails::Tracer::SpanHelpers.set_error(span, payload[:exception_object] || payload[:exception])
|
55
|
+
end
|
56
|
+
|
57
|
+
span.finish
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails/active_support/cache/core_ext'
|
2
|
+
require 'rails/active_support/cache/manual_tracer'
|
3
|
+
require 'rails/active_support/cache/subscriber'
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module Cache
|
7
|
+
module Tracer
|
8
|
+
class << self
|
9
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil, dalli: false)
|
10
|
+
clear_subscribers
|
11
|
+
events = %w(read write generate delete clear)
|
12
|
+
@subscribers = events.map do |event|
|
13
|
+
subscriber = ActiveSupport::Cache::Tracer::Subscriber.new(tracer: tracer,
|
14
|
+
active_span: active_span,
|
15
|
+
event: event)
|
16
|
+
ActiveSupport::Notifications.subscribe("cache_#{event}.active_support", subscriber)
|
17
|
+
end
|
18
|
+
|
19
|
+
instrument_dalli(tracer: tracer, active_span: active_span) if dalli
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def disable
|
25
|
+
if @subscribers
|
26
|
+
@subscribers.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) }
|
27
|
+
@subscribers.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :clear_subscribers :disable
|
34
|
+
|
35
|
+
def instrument_dalli(tracer:, active_span:)
|
36
|
+
return unless defined?(ActiveSupport::Cache::DalliStore)
|
37
|
+
require 'rails/active_support/cache/dalli_tracer'
|
38
|
+
|
39
|
+
Dalli::Tracer.instrument(tracer: tracer, active_span: active_span)
|
40
|
+
instrument_dalli_logger(active_span: active_span)
|
41
|
+
end
|
42
|
+
|
43
|
+
def instrument_dalli_logger(active_span:, level: Logger::ERROR)
|
44
|
+
return unless defined?(Tracing::Logger)
|
45
|
+
return unless active_span
|
46
|
+
return if [Tracing::Logger, Tracing::CompositeLogger].any? { |t| Dalli.logger.is_a?(t) }
|
47
|
+
|
48
|
+
tracing_logger = Tracing::Logger.new(active_span: active_span, level: level)
|
49
|
+
loggers = [tracing_logger, Dalli.logger].compact
|
50
|
+
Dalli.logger = Tracing::CompositeLogger.new(*loggers)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
module Rails
|
3
|
+
module Tracer
|
4
|
+
module Defer
|
5
|
+
class << self
|
6
|
+
attr_reader :enabled
|
7
|
+
|
8
|
+
def enable
|
9
|
+
@enabled = true
|
10
|
+
@requests = {}
|
11
|
+
@parent_spans = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def requests
|
15
|
+
@requests
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_parent(id, span)
|
19
|
+
@parent_spans[id] = span
|
20
|
+
end
|
21
|
+
|
22
|
+
def defer_span(id:, spaninfo:, tracer: OpenTracing.global_tracer)
|
23
|
+
if @requests[id].nil?
|
24
|
+
@requests[id] = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# if this is a process_action then the request is complete, so we can write out the span
|
28
|
+
if spaninfo['event'] == 'process_action.action_controller'
|
29
|
+
|
30
|
+
# check if we've registered a parent span
|
31
|
+
# at this point, only a rack span
|
32
|
+
parent_span = @parent_spans[id]
|
33
|
+
|
34
|
+
if parent_span.nil?
|
35
|
+
# use process_action as the parent span for this request
|
36
|
+
parent_span = tracer.start_span(spaninfo['name'],
|
37
|
+
start_time: spaninfo['start'],
|
38
|
+
tags: spaninfo['tags'])
|
39
|
+
else
|
40
|
+
# if we have another parent span and process_action will
|
41
|
+
# not need to be used as a parent span, add it to the list
|
42
|
+
# to write out with all the others
|
43
|
+
@requests[id] << spaninfo
|
44
|
+
end
|
45
|
+
|
46
|
+
# each of the stored notifications with the current request id will
|
47
|
+
# be written out as spans
|
48
|
+
write_spans(notifications: @requests[id], parent_span: parent_span)
|
49
|
+
|
50
|
+
# the rack span will finish on its own, but we need to finish if we
|
51
|
+
# started a parent span using the process_action notification
|
52
|
+
parent_span.finish(end_time: spaninfo['finish']) if @parent_spans[id].nil?
|
53
|
+
|
54
|
+
# now that all spans are written, these can be deleted to free up space
|
55
|
+
@requests.delete(id)
|
56
|
+
@parent_spans.delete(id)
|
57
|
+
else
|
58
|
+
# this isn't a span that specifically means anything, so
|
59
|
+
# just save the span info
|
60
|
+
@requests[id] << spaninfo
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_spans(notifications: [], parent_span: nil, tracer: OpenTracing.global_tracer)
|
66
|
+
notifications.each do |spaninfo|
|
67
|
+
span = tracer.start_span(spaninfo['name'],
|
68
|
+
child_of: parent_span,
|
69
|
+
start_time: spaninfo['start'],
|
70
|
+
tags: spaninfo['tags'])
|
71
|
+
|
72
|
+
span.finish(end_time: spaninfo['finish'])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rails
|
2
|
+
module Rack
|
3
|
+
class Tracer
|
4
|
+
class << self
|
5
|
+
def instrument(tracer: OpenTracing.global_tracer, middlewares: Rails.configuration.middleware)
|
6
|
+
return unless defined?(::Rack::Tracer)
|
7
|
+
@owns_all_middlewares = false
|
8
|
+
unless middlewares.include?(::Rack::Tracer)
|
9
|
+
middlewares.use(::Rack::Tracer, tracer: tracer)
|
10
|
+
@owns_all_middlewares = true
|
11
|
+
end
|
12
|
+
middlewares.insert_after(::Rack::Tracer, Rails::Rack::Tracer)
|
13
|
+
end
|
14
|
+
|
15
|
+
def disable(middlewares: Rails.configuration.middleware)
|
16
|
+
middlewares.delete(Rails::Rack::Tracer)
|
17
|
+
if @owns_all_middlewares
|
18
|
+
middlewares.delete(::Rack::Tracer)
|
19
|
+
@owns_all_middlewares = false
|
20
|
+
end
|
21
|
+
rescue
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(app)
|
26
|
+
@app = app
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
@app.call(env)
|
31
|
+
ensure
|
32
|
+
enhance_rack_span(env)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def enhance_rack_span(env)
|
38
|
+
span = extract_span(env)
|
39
|
+
if span && route_found?(env)
|
40
|
+
span.operation_name = operation_name(env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_span(env)
|
45
|
+
env['rack.span']
|
46
|
+
end
|
47
|
+
|
48
|
+
def route_found?(env)
|
49
|
+
env["action_dispatch.request.path_parameters"]
|
50
|
+
end
|
51
|
+
|
52
|
+
def operation_name(env)
|
53
|
+
path_parameters = env["action_dispatch.request.path_parameters"]
|
54
|
+
action_controller = env["action_controller.instance"]
|
55
|
+
controller = action_controller ? action_controller.class.to_s : path_parameters[:controller]
|
56
|
+
action = path_parameters[:action]
|
57
|
+
"#{controller}##{action}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rails
|
2
|
+
module Tracer
|
3
|
+
module SpanHelpers
|
4
|
+
class << self
|
5
|
+
def set_error(span, exception)
|
6
|
+
span.set_tag('error', true)
|
7
|
+
|
8
|
+
case exception
|
9
|
+
when Array
|
10
|
+
exception_class, exception_message = exception
|
11
|
+
span.log(event: 'error', :'error.kind' => exception_class, message: exception_message)
|
12
|
+
when Exception
|
13
|
+
span.log(event: 'error', :'error.object' => exception)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def rack_span(payload)
|
17
|
+
# if rack tracing is disabled, this will just be nil
|
18
|
+
headers = payload.fetch(:headers, nil)
|
19
|
+
headers.env['rack.span'] if !headers.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/rails/tracer.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "rails/span_helpers"
|
2
|
+
require "rails/defer_notifications"
|
3
|
+
require "rails/rack/tracer"
|
4
|
+
require "rails/active_record/tracer"
|
5
|
+
require "rails/active_support/cache/tracer"
|
6
|
+
require "rails/action_controller/tracer"
|
7
|
+
require "rails/action_view/tracer"
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
module Tracer
|
11
|
+
class << self
|
12
|
+
def instrument(tracer: OpenTracing.global_tracer, active_span: nil,
|
13
|
+
rack: false, middlewares: Rails.configuration.middleware,
|
14
|
+
active_record: true,
|
15
|
+
active_support_cache: true, dalli: false,
|
16
|
+
action_controller: true,
|
17
|
+
action_view: true,
|
18
|
+
full_trace: false)
|
19
|
+
Rails::Rack::Tracer.instrument(tracer: tracer, middlewares: middlewares) if rack
|
20
|
+
ActiveRecord::Tracer.instrument(tracer: tracer, active_span: active_span) if active_record
|
21
|
+
ActiveSupport::Cache::Tracer.instrument(tracer: tracer, active_span: active_span, dalli: dalli) if active_support_cache
|
22
|
+
ActionController::Tracer.instrument(tracer: tracer, active_span: active_span) if action_controller
|
23
|
+
ActionView::Tracer.instrument(tracer: tracer, active_span: active_span) if action_view
|
24
|
+
|
25
|
+
# hold the requests until they can be written
|
26
|
+
Rails::Tracer::Defer.enable if full_trace
|
27
|
+
end
|
28
|
+
|
29
|
+
def disable
|
30
|
+
ActiveRecord::Tracer.disable
|
31
|
+
ActiveSupport::Cache::Tracer.disable
|
32
|
+
Rails::Rack::Tracer.disable
|
33
|
+
ActionController::Tracer.disable
|
34
|
+
ActionView::Tracer.disable
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|