signalfx-rails-instrumentation 0.1.0

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