signalfx-rails-instrumentation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,4 @@
1
+ memcached:
2
+ image: memcached
3
+ ports:
4
+ - 11211:11211
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 3.2.0"
9
+ gem "test-unit", "~> 3.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 5.1.3"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 4.0.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 4.1.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 4.2.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 5.1.3"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 5.0.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pry"
6
+ gem "pry-stack_explorer"
7
+ gem "pry-byebug"
8
+ gem "rails", "~> 5.1.0"
9
+
10
+ gemspec path: "../"
@@ -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