fair-ddtrace 0.8.2.a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.env +11 -0
  3. data/.gitignore +59 -0
  4. data/.rubocop.yml +61 -0
  5. data/.yardopts +5 -0
  6. data/Appraisals +136 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +24 -0
  9. data/README.md +156 -0
  10. data/Rakefile +176 -0
  11. data/circle.yml +61 -0
  12. data/ddtrace.gemspec +44 -0
  13. data/docker-compose.yml +42 -0
  14. data/docs/GettingStarted.md +735 -0
  15. data/gemfiles/contrib.gemfile +16 -0
  16. data/gemfiles/contrib_old.gemfile +15 -0
  17. data/gemfiles/rails30_postgres.gemfile +10 -0
  18. data/gemfiles/rails30_postgres_sidekiq.gemfile +11 -0
  19. data/gemfiles/rails32_mysql2.gemfile +11 -0
  20. data/gemfiles/rails32_postgres.gemfile +10 -0
  21. data/gemfiles/rails32_postgres_redis.gemfile +11 -0
  22. data/gemfiles/rails32_postgres_sidekiq.gemfile +11 -0
  23. data/gemfiles/rails4_mysql2.gemfile +9 -0
  24. data/gemfiles/rails4_postgres.gemfile +9 -0
  25. data/gemfiles/rails4_postgres_redis.gemfile +10 -0
  26. data/gemfiles/rails4_postgres_sidekiq.gemfile +11 -0
  27. data/gemfiles/rails5_mysql2.gemfile +8 -0
  28. data/gemfiles/rails5_postgres.gemfile +8 -0
  29. data/gemfiles/rails5_postgres_redis.gemfile +9 -0
  30. data/gemfiles/rails5_postgres_sidekiq.gemfile +10 -0
  31. data/lib/ddtrace.rb +73 -0
  32. data/lib/ddtrace/buffer.rb +52 -0
  33. data/lib/ddtrace/context.rb +145 -0
  34. data/lib/ddtrace/contrib/active_record/patcher.rb +94 -0
  35. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +108 -0
  36. data/lib/ddtrace/contrib/elasticsearch/quantize.rb +22 -0
  37. data/lib/ddtrace/contrib/grape/endpoint.rb +164 -0
  38. data/lib/ddtrace/contrib/grape/patcher.rb +73 -0
  39. data/lib/ddtrace/contrib/http/patcher.rb +156 -0
  40. data/lib/ddtrace/contrib/rack/middlewares.rb +150 -0
  41. data/lib/ddtrace/contrib/rails/action_controller.rb +81 -0
  42. data/lib/ddtrace/contrib/rails/action_view.rb +110 -0
  43. data/lib/ddtrace/contrib/rails/active_record.rb +56 -0
  44. data/lib/ddtrace/contrib/rails/active_support.rb +113 -0
  45. data/lib/ddtrace/contrib/rails/core_extensions.rb +137 -0
  46. data/lib/ddtrace/contrib/rails/framework.rb +171 -0
  47. data/lib/ddtrace/contrib/rails/middlewares.rb +32 -0
  48. data/lib/ddtrace/contrib/rails/utils.rb +43 -0
  49. data/lib/ddtrace/contrib/redis/patcher.rb +118 -0
  50. data/lib/ddtrace/contrib/redis/quantize.rb +30 -0
  51. data/lib/ddtrace/contrib/redis/tags.rb +19 -0
  52. data/lib/ddtrace/contrib/sidekiq/tracer.rb +103 -0
  53. data/lib/ddtrace/contrib/sinatra/tracer.rb +169 -0
  54. data/lib/ddtrace/distributed.rb +38 -0
  55. data/lib/ddtrace/encoding.rb +65 -0
  56. data/lib/ddtrace/error.rb +37 -0
  57. data/lib/ddtrace/ext/app_types.rb +10 -0
  58. data/lib/ddtrace/ext/cache.rb +7 -0
  59. data/lib/ddtrace/ext/distributed.rb +10 -0
  60. data/lib/ddtrace/ext/errors.rb +10 -0
  61. data/lib/ddtrace/ext/http.rb +11 -0
  62. data/lib/ddtrace/ext/net.rb +8 -0
  63. data/lib/ddtrace/ext/redis.rb +11 -0
  64. data/lib/ddtrace/ext/sql.rb +8 -0
  65. data/lib/ddtrace/logger.rb +39 -0
  66. data/lib/ddtrace/monkey.rb +84 -0
  67. data/lib/ddtrace/pin.rb +63 -0
  68. data/lib/ddtrace/provider.rb +21 -0
  69. data/lib/ddtrace/sampler.rb +49 -0
  70. data/lib/ddtrace/span.rb +222 -0
  71. data/lib/ddtrace/tracer.rb +310 -0
  72. data/lib/ddtrace/transport.rb +162 -0
  73. data/lib/ddtrace/utils.rb +16 -0
  74. data/lib/ddtrace/version.rb +9 -0
  75. data/lib/ddtrace/workers.rb +108 -0
  76. data/lib/ddtrace/writer.rb +118 -0
  77. metadata +208 -0
@@ -0,0 +1,156 @@
1
+ # requirements should be kept minimal as Patcher is a shared requirement.
2
+
3
+ module Datadog
4
+ module Contrib
5
+ # Datadog Net/HTTP integration.
6
+ module HTTP
7
+ URL = 'http.url'.freeze
8
+ METHOD = 'http.method'.freeze
9
+ BODY = 'http.body'.freeze
10
+
11
+ NAME = 'http.request'.freeze
12
+ APP = 'net/http'.freeze
13
+ SERVICE = 'net/http'.freeze
14
+
15
+ @distributed_tracing_enabled = false
16
+
17
+ class << self
18
+ attr_accessor :distributed_tracing_enabled
19
+ end
20
+
21
+ module_function
22
+
23
+ def should_skip_tracing?(req, address, port, transport, pin)
24
+ # we don't want to trace our own call to the API (they use net/http)
25
+ # when we know the host & port (from the URI) we use it, else (most-likely
26
+ # called with a block) rely on the URL at the end.
27
+ if req.respond_to?(:uri) && req.uri
28
+ if req.uri.host.to_s == transport.hostname.to_s &&
29
+ req.uri.port.to_i == transport.port.to_i
30
+ return true
31
+ end
32
+ elsif address && port &&
33
+ address.to_s == transport.hostname.to_s &&
34
+ port.to_i == transport.port.to_i
35
+ return true
36
+ end
37
+ # we don't want a "shotgun" effect with two nested traces for one
38
+ # logical get, and request is likely to call itself recursively
39
+ active = pin.tracer.active_span()
40
+ return true if active && (active.name == NAME)
41
+ false
42
+ end
43
+
44
+ def should_skip_distributed_tracing?(pin)
45
+ unless pin.config.nil?
46
+ return !pin.config.fetch(:distributed_tracing_enabled, @distributed_tracing_enabled)
47
+ end
48
+ !@distributed_tracing_enabled
49
+ end
50
+
51
+ # Patcher enables patching of 'net/http' module.
52
+ # This is used in monkey.rb to automatically apply patches
53
+ module Patcher
54
+ @patched = false
55
+
56
+ module_function
57
+
58
+ # patch applies our patch if needed
59
+ def patch
60
+ unless @patched
61
+ begin
62
+ require 'uri'
63
+ require 'ddtrace/pin'
64
+ require 'ddtrace/monkey'
65
+ require 'ddtrace/ext/app_types'
66
+ require 'ddtrace/ext/http'
67
+ require 'ddtrace/ext/net'
68
+ require 'ddtrace/ext/distributed'
69
+
70
+ patch_http()
71
+
72
+ @patched = true
73
+ rescue StandardError => e
74
+ Datadog::Tracer.log.error("Unable to apply net/http integration: #{e}")
75
+ end
76
+ end
77
+ @patched
78
+ end
79
+
80
+ # patched? tells wether patch has been successfully applied
81
+ def patched?
82
+ @patched
83
+ end
84
+
85
+ # rubocop:disable Metrics/MethodLength
86
+ # rubocop:disable Metrics/BlockLength
87
+ # rubocop:disable Metrics/AbcSize
88
+ def patch_http
89
+ ::Net::HTTP.class_eval do
90
+ alias_method :initialize_without_datadog, :initialize
91
+ Datadog::Monkey.without_warnings do
92
+ remove_method :initialize
93
+ end
94
+
95
+ def initialize(*args)
96
+ pin = Datadog::Pin.new(SERVICE, app: APP, app_type: Datadog::Ext::AppTypes::WEB)
97
+ pin.onto(self)
98
+ initialize_without_datadog(*args)
99
+ end
100
+
101
+ alias_method :request_without_datadog, :request
102
+ remove_method :request
103
+
104
+ def request(req, body = nil, &block) # :yield: +response+
105
+ pin = Datadog::Pin.get_from(self)
106
+ return request_without_datadog(req, body, &block) unless pin && pin.tracer
107
+
108
+ transport = pin.tracer.writer.transport
109
+ return request_without_datadog(req, body, &block) if
110
+ Datadog::Contrib::HTTP.should_skip_tracing?(req, @address, @port, transport, pin)
111
+
112
+ pin.tracer.trace(NAME) do |span|
113
+ begin
114
+ span.service = pin.service
115
+ span.span_type = Datadog::Ext::HTTP::TYPE
116
+
117
+ span.resource = req.method
118
+ # Using the method as a resource, as URL/path can trigger
119
+ # a possibly infinite number of resources.
120
+ span.set_tag(Datadog::Ext::HTTP::URL, req.path)
121
+ span.set_tag(Datadog::Ext::HTTP::METHOD, req.method)
122
+
123
+ unless Datadog::Contrib::HTTP.should_skip_distributed_tracing?(pin)
124
+ req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID, span.trace_id)
125
+ req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID, span.span_id)
126
+ end
127
+ rescue StandardError => e
128
+ Datadog::Tracer.log.error("error preparing span for http request: #{e}")
129
+ ensure
130
+ response = request_without_datadog(req, body, &block)
131
+ end
132
+ span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.code)
133
+ if req.respond_to?(:uri) && req.uri
134
+ span.set_tag(Datadog::Ext::NET::TARGET_HOST, req.uri.host)
135
+ span.set_tag(Datadog::Ext::NET::TARGET_PORT, req.uri.port.to_s)
136
+ else
137
+ span.set_tag(Datadog::Ext::NET::TARGET_HOST, @address)
138
+ span.set_tag(Datadog::Ext::NET::TARGET_PORT, @port.to_s)
139
+ end
140
+
141
+ case response.code.to_i / 100
142
+ when 4
143
+ span.set_error(response)
144
+ when 5
145
+ span.set_error(response)
146
+ end
147
+
148
+ response
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,150 @@
1
+ require 'ddtrace/ext/app_types'
2
+ require 'ddtrace/ext/http'
3
+ require 'ddtrace/distributed'
4
+
5
+ module Datadog
6
+ module Contrib
7
+ # Rack module includes middlewares that are required to trace any framework
8
+ # and application built on top of Rack.
9
+ module Rack
10
+ # RACK headers to test when doing distributed tracing.
11
+ # They are slightly different from real headers as Rack uppercases everything
12
+
13
+ # Header used to transmit the trace ID.
14
+ HTTP_HEADER_TRACE_ID = 'HTTP_X_DATADOG_TRACE_ID'.freeze
15
+
16
+ # Header used to transmit the parent ID.
17
+ HTTP_HEADER_PARENT_ID = 'HTTP_X_DATADOG_PARENT_ID'.freeze
18
+
19
+ # TraceMiddleware ensures that the Rack Request is properly traced
20
+ # from the beginning to the end. The middleware adds the request span
21
+ # in the Rack environment so that it can be retrieved by the underlying
22
+ # application. If request tags are not set by the app, they will be set using
23
+ # information available at the Rack level.
24
+ class TraceMiddleware
25
+ DEFAULT_CONFIG = {
26
+ tracer: Datadog.tracer,
27
+ default_service: 'rack',
28
+ distributed_tracing_enabled: false
29
+ }.freeze
30
+
31
+ def initialize(app, options = {})
32
+ # update options with our configuration, unless it's already available
33
+ [:tracer, :default_service, :distributed_tracing_enabled].each do |k|
34
+ options[k] ||= DEFAULT_CONFIG[k]
35
+ end
36
+
37
+ @app = app
38
+ @options = options
39
+ end
40
+
41
+ def configure
42
+ # ensure that the configuration is executed only once
43
+ return clean_context if @tracer && @service
44
+
45
+ # retrieve the current tracer and service
46
+ @tracer = @options.fetch(:tracer)
47
+ @service = @options.fetch(:default_service)
48
+ @distributed_tracing_enabled = @options.fetch(:distributed_tracing_enabled)
49
+
50
+ # configure the Rack service
51
+ @tracer.set_service_info(
52
+ @service,
53
+ 'rack',
54
+ Datadog::Ext::AppTypes::WEB
55
+ )
56
+ end
57
+
58
+ # rubocop:disable Metrics/MethodLength
59
+ def call(env)
60
+ # configure the Rack middleware once
61
+ configure()
62
+
63
+ trace_options = {
64
+ service: @service,
65
+ resource: nil,
66
+ span_type: Datadog::Ext::HTTP::TYPE
67
+ }
68
+
69
+ # start a new request span and attach it to the current Rack environment;
70
+ # we must ensure that the span `resource` is set later
71
+ request_span = @tracer.trace('rack.request', trace_options)
72
+
73
+ if @distributed_tracing_enabled
74
+ # Merge distributed trace ids if present
75
+ #
76
+ # Use integer values for tests, as it will catch both
77
+ # a non-existing header or a badly formed one.
78
+ trace_id, parent_id = Datadog::Distributed.parse_trace_headers(
79
+ env[Datadog::Contrib::Rack::HTTP_HEADER_TRACE_ID],
80
+ env[Datadog::Contrib::Rack::HTTP_HEADER_PARENT_ID]
81
+ )
82
+ request_span.trace_id = trace_id unless trace_id.nil?
83
+ request_span.parent_id = parent_id unless parent_id.nil?
84
+ end
85
+
86
+ env[:datadog_rack_request_span] = request_span
87
+
88
+ # call the rest of the stack
89
+ status, headers, response = @app.call(env)
90
+
91
+ # rubocop:disable Lint/RescueException
92
+ # Here we really want to catch *any* exception, not only StandardError,
93
+ # as we really have no clue of what is in the block,
94
+ # and it is user code which should be executed no matter what.
95
+ # It's not a problem since we re-raise it afterwards so for example a
96
+ # SignalException::Interrupt would still bubble up.
97
+ rescue Exception => e
98
+ # catch exceptions that may be raised in the middleware chain
99
+ # Note: if a middleware catches an Exception without re raising,
100
+ # the Exception cannot be recorded here.
101
+ request_span.set_error(e)
102
+ raise e
103
+ ensure
104
+ # the source of truth in Rack is the PATH_INFO key that holds the
105
+ # URL for the current request; some framework may override that
106
+ # value, especially during exception handling and because of that
107
+ # we prefer using the `REQUEST_URI` if this is available.
108
+ # NOTE: `REQUEST_URI` is Rails specific and may not apply for other frameworks
109
+ url = env['REQUEST_URI'] || env['PATH_INFO']
110
+
111
+ # Rack is a really low level interface and it doesn't provide any
112
+ # advanced functionality like routers. Because of that, we assume that
113
+ # the underlying framework or application has more knowledge about
114
+ # the result for this request; `resource` and `tags` are expected to
115
+ # be set in another level but if they're missing, reasonable defaults
116
+ # are used.
117
+ request_span.resource = "#{env['REQUEST_METHOD']} #{status}".strip unless request_span.resource
118
+ if request_span.get_tag(Datadog::Ext::HTTP::METHOD).nil?
119
+ request_span.set_tag(Datadog::Ext::HTTP::METHOD, env['REQUEST_METHOD'])
120
+ end
121
+ if request_span.get_tag(Datadog::Ext::HTTP::URL).nil?
122
+ request_span.set_tag(Datadog::Ext::HTTP::URL, url)
123
+ end
124
+ if request_span.get_tag(Datadog::Ext::HTTP::STATUS_CODE).nil? && status
125
+ request_span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, status)
126
+ end
127
+
128
+ # detect if the status code is a 5xx and flag the request span as an error
129
+ # unless it has been already set by the underlying framework
130
+ if status.to_s.start_with?('5') && request_span.status.zero?
131
+ request_span.status = 1
132
+ end
133
+
134
+ request_span.finish()
135
+
136
+ [status, headers, response]
137
+ end
138
+
139
+ private
140
+
141
+ # TODO: Remove this once we change how context propagation works. This
142
+ # ensures we clean thread-local variables on each HTTP request avoiding
143
+ # memory leaks.
144
+ def clean_context
145
+ @tracer.provider.context = Datadog::Context.new
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,81 @@
1
+ require 'ddtrace/ext/http'
2
+ require 'ddtrace/ext/errors'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Rails
7
+ # Code used to create and handle 'rails.action_controller' spans.
8
+ module ActionController
9
+ KEY = 'datadog_actioncontroller'.freeze
10
+
11
+ def self.instrument
12
+ # subscribe when the request processing starts
13
+ ::ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |*args|
14
+ start_processing(*args)
15
+ end
16
+
17
+ # subscribe when the request processing has been completed
18
+ ::ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
19
+ process_action(*args)
20
+ end
21
+ end
22
+
23
+ def self.start_processing(*)
24
+ return if Thread.current[KEY]
25
+
26
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
27
+ service = ::Rails.configuration.datadog_trace.fetch(:default_controller_service)
28
+ type = Datadog::Ext::HTTP::TYPE
29
+ tracer.trace('rails.action_controller', service: service, span_type: type)
30
+
31
+ Thread.current[KEY] = true
32
+ rescue StandardError => e
33
+ Datadog::Tracer.log.error(e.message)
34
+ end
35
+
36
+ def self.process_action(_name, start, finish, _id, payload)
37
+ return unless Thread.current[KEY]
38
+ Thread.current[KEY] = false
39
+
40
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
41
+ span = tracer.active_span()
42
+ return unless span
43
+
44
+ begin
45
+ resource = "#{payload.fetch(:controller)}##{payload.fetch(:action)}"
46
+ span.resource = resource
47
+
48
+ # set the parent resource if it's a `rack.request` span
49
+ if !span.parent.nil? && span.parent.name == 'rack.request'
50
+ span.parent.resource = resource
51
+ end
52
+
53
+ span.set_tag('rails.route.action', payload.fetch(:action))
54
+ span.set_tag('rails.route.controller', payload.fetch(:controller))
55
+
56
+ if payload[:exception].nil?
57
+ # [christian] in some cases :status is not defined,
58
+ # rather than firing an error, simply acknowledge we don't know it.
59
+ status = payload.fetch(:status, '?').to_s
60
+ span.status = 1 if status.starts_with?('5')
61
+ else
62
+ error = payload[:exception]
63
+ if defined?(::ActionDispatch::ExceptionWrapper)
64
+ status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(error[0])
65
+ status = status ? status.to_s : '?'
66
+ else
67
+ status = '500'
68
+ end
69
+ span.set_error(error) if status.starts_with?('5')
70
+ end
71
+ ensure
72
+ span.start_time = start
73
+ span.finish(finish)
74
+ end
75
+ rescue StandardError => e
76
+ Datadog::Tracer.log.error(e.message)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,110 @@
1
+ require 'ddtrace/contrib/rails/utils'
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module Rails
6
+ # Code used to create and handle 'rails.render_template' and 'rails.render_partial' spans.
7
+ module ActionView
8
+ def self.instrument
9
+ # patch Rails core components
10
+ Datadog::RailsRendererPatcher.patch_renderer()
11
+
12
+ # subscribe when the template rendering starts
13
+ ::ActiveSupport::Notifications.subscribe('start_render_template.action_view') do |*args|
14
+ start_render_template(*args)
15
+ end
16
+
17
+ # subscribe when the partial rendering starts
18
+ ::ActiveSupport::Notifications.subscribe('start_render_partial.action_view') do |*args|
19
+ start_render_partial(*args)
20
+ end
21
+
22
+ # subscribe when the template rendering has been processed
23
+ ::ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
24
+ render_template(*args)
25
+ end
26
+
27
+ # subscribe when the partial rendering has been processed
28
+ ::ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
29
+ render_partial(*args)
30
+ end
31
+ end
32
+
33
+ def self.get_key(f)
34
+ 'datadog_actionview_' + f
35
+ end
36
+
37
+ def self.start_render_template(*)
38
+ key = get_key('render_template')
39
+ return if Thread.current[key]
40
+
41
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
42
+ type = Datadog::Ext::HTTP::TEMPLATE
43
+ tracer.trace('rails.render_template', span_type: type)
44
+
45
+ Thread.current[key] = true
46
+ rescue StandardError => e
47
+ Datadog::Tracer.log.error(e.message)
48
+ end
49
+
50
+ def self.start_render_partial(*)
51
+ key = get_key('render_partial')
52
+ return if Thread.current[key]
53
+
54
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
55
+ type = Datadog::Ext::HTTP::TEMPLATE
56
+ tracer.trace('rails.render_partial', span_type: type)
57
+
58
+ Thread.current[key] = true
59
+ rescue StandardError => e
60
+ Datadog::Tracer.log.error(e.message)
61
+ end
62
+
63
+ def self.render_template(_name, start, finish, _id, payload)
64
+ key = get_key('render_template')
65
+ return unless Thread.current[key]
66
+ Thread.current[key] = false
67
+
68
+ # finish the tracing and update the execution time
69
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
70
+ span = tracer.active_span()
71
+ return unless span
72
+
73
+ begin
74
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
75
+ span.set_tag('rails.template_name', template_name)
76
+ span.set_tag('rails.layout', payload.fetch(:layout))
77
+ span.set_error(payload[:exception]) if payload[:exception]
78
+ ensure
79
+ span.start_time = start
80
+ span.finish(finish)
81
+ end
82
+ rescue StandardError => e
83
+ Datadog::Tracer.log.error(e.message)
84
+ end
85
+
86
+ def self.render_partial(_name, start, finish, _id, payload)
87
+ key = get_key('render_partial')
88
+ return unless Thread.current[key]
89
+ Thread.current[key] = false
90
+
91
+ # finish the tracing and update the execution time
92
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
93
+ span = tracer.active_span()
94
+ return unless span
95
+
96
+ begin
97
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
98
+ span.set_tag('rails.template_name', template_name)
99
+ span.set_error(payload[:exception]) if payload[:exception]
100
+ ensure
101
+ span.start_time = start
102
+ span.finish(finish)
103
+ end
104
+ rescue StandardError => e
105
+ Datadog::Tracer.log.error(e.message)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end