ddtrace 0.39.0 → 0.40.0

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +56 -0
  3. data/.gitlab-ci.yml +1 -0
  4. data/.simplecov +38 -0
  5. data/Appraisals +87 -1
  6. data/CHANGELOG.md +63 -1
  7. data/Rakefile +500 -465
  8. data/ddtrace.gemspec +1 -0
  9. data/docs/DevelopmentGuide.md +16 -0
  10. data/docs/GettingStarted.md +49 -32
  11. data/lib/ddtrace.rb +1 -0
  12. data/lib/ddtrace/configuration.rb +36 -5
  13. data/lib/ddtrace/configuration/components.rb +4 -7
  14. data/lib/ddtrace/contrib/action_pack/action_controller/instrumentation.rb +0 -7
  15. data/lib/ddtrace/contrib/que/configuration/settings.rb +42 -0
  16. data/lib/ddtrace/contrib/que/ext.rb +30 -0
  17. data/lib/ddtrace/contrib/que/integration.rb +42 -0
  18. data/lib/ddtrace/contrib/que/patcher.rb +24 -0
  19. data/lib/ddtrace/contrib/que/tracer.rb +56 -0
  20. data/lib/ddtrace/contrib/racecar/events.rb +2 -0
  21. data/lib/ddtrace/contrib/racecar/events/consume.rb +27 -0
  22. data/lib/ddtrace/contrib/racecar/ext.rb +1 -0
  23. data/lib/ddtrace/contrib/rack/middlewares.rb +2 -0
  24. data/lib/ddtrace/contrib/rails/configuration/settings.rb +5 -0
  25. data/lib/ddtrace/contrib/rails/ext.rb +1 -0
  26. data/lib/ddtrace/contrib/rails/log_injection.rb +81 -0
  27. data/lib/ddtrace/contrib/rails/middlewares.rb +7 -2
  28. data/lib/ddtrace/contrib/rails/patcher.rb +15 -0
  29. data/lib/ddtrace/contrib/sinatra/env.rb +5 -4
  30. data/lib/ddtrace/contrib/sinatra/tracer.rb +21 -42
  31. data/lib/ddtrace/contrib/sinatra/tracer_middleware.rb +50 -23
  32. data/lib/ddtrace/version.rb +1 -1
  33. data/lib/ddtrace/workers/async.rb +2 -2
  34. data/lib/ddtrace/workers/loop.rb +1 -1
  35. data/lib/ddtrace/workers/polling.rb +1 -1
  36. metadata +67 -31
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ddtrace/contrib/analytics'
4
+
5
+ module Datadog
6
+ module Contrib
7
+ module Que
8
+ # Tracer is a Que's server-side middleware which traces executed jobs
9
+ class Tracer
10
+ def call(job)
11
+ trace_options = {
12
+ service: configuration[:service_name],
13
+ span_type: Datadog::Ext::AppTypes::WORKER
14
+ }
15
+
16
+ tracer.trace(Ext::SPAN_JOB, trace_options) do |request_span|
17
+ request_span.resource = job.class.name.to_s
18
+ request_span.set_tag(Ext::TAG_JOB_QUEUE, job.que_attrs[:queue])
19
+ request_span.set_tag(Ext::TAG_JOB_ID, job.que_attrs[:id])
20
+ request_span.set_tag(Ext::TAG_JOB_PRIORITY, job.que_attrs[:priority])
21
+ request_span.set_tag(Ext::TAG_JOB_ERROR_COUNT, job.que_attrs[:error_count])
22
+ request_span.set_tag(Ext::TAG_JOB_RUN_AT, job.que_attrs[:run_at])
23
+ request_span.set_tag(Ext::TAG_JOB_EXPIRED_AT, job.que_attrs[:expired_at])
24
+ request_span.set_tag(Ext::TAG_JOB_FINISHED_AT, job.que_attrs[:finished_at])
25
+ request_span.set_tag(Ext::TAG_JOB_ARGS, job.que_attrs[:args]) if configuration[:tag_args]
26
+ request_span.set_tag(Ext::TAG_JOB_DATA, job.que_attrs[:data]) if configuration[:tag_data]
27
+
28
+ set_sample_rate(request_span)
29
+ Contrib::Analytics.set_measured(request_span)
30
+
31
+ yield
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def set_sample_rate(request_span)
38
+ if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
39
+ Contrib::Analytics.set_sample_rate(
40
+ request_span,
41
+ configuration[:analytics_sample_rate]
42
+ )
43
+ end
44
+ end
45
+
46
+ def tracer
47
+ configuration[:tracer]
48
+ end
49
+
50
+ def configuration
51
+ Datadog.configuration[Datadog::Contrib::Que::Ext::APP.to_sym]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,5 +1,6 @@
1
1
  require 'ddtrace/contrib/racecar/events/batch'
2
2
  require 'ddtrace/contrib/racecar/events/message'
3
+ require 'ddtrace/contrib/racecar/events/consume'
3
4
 
4
5
  module Datadog
5
6
  module Contrib
@@ -7,6 +8,7 @@ module Datadog
7
8
  # Defines collection of instrumented Racecar events
8
9
  module Events
9
10
  ALL = [
11
+ Events::Consume,
10
12
  Events::Batch,
11
13
  Events::Message
12
14
  ].freeze
@@ -0,0 +1,27 @@
1
+ require 'ddtrace/contrib/racecar/ext'
2
+ require 'ddtrace/contrib/racecar/event'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Racecar
7
+ module Events
8
+ # Defines instrumentation for main_loop.racecar event
9
+ module Consume
10
+ include Racecar::Event
11
+
12
+ EVENT_NAME = 'main_loop.racecar'.freeze
13
+
14
+ module_function
15
+
16
+ def event_name
17
+ self::EVENT_NAME
18
+ end
19
+
20
+ def span_name
21
+ Ext::SPAN_CONSUME
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -10,6 +10,7 @@ module Datadog
10
10
  ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_RACECAR_ANALYTICS_SAMPLE_RATE'.freeze
11
11
  ENV_ANALYTICS_SAMPLE_RATE_OLD = 'DD_RACECAR_ANALYTICS_SAMPLE_RATE'.freeze
12
12
  SERVICE_NAME = 'racecar'.freeze
13
+ SPAN_CONSUME = 'racecar.consume'.freeze
13
14
  SPAN_BATCH = 'racecar.batch'.freeze
14
15
  SPAN_MESSAGE = 'racecar.message'.freeze
15
16
  TAG_CONSUMER = 'kafka.consumer'.freeze
@@ -4,6 +4,8 @@ require 'ddtrace/propagation/http_propagator'
4
4
  require 'ddtrace/contrib/analytics'
5
5
  require 'ddtrace/contrib/rack/ext'
6
6
  require 'ddtrace/contrib/rack/request_queue'
7
+ require 'ddtrace/environment'
8
+ require 'date'
7
9
 
8
10
  module Datadog
9
11
  module Contrib
@@ -81,6 +81,11 @@ module Datadog
81
81
  Datadog.configuration[:action_view][:template_base_path] = value
82
82
  end
83
83
  end
84
+
85
+ option :log_injection do |o|
86
+ o.default { env_to_bool(Ext::ENV_LOGS_INJECTION_ENABLED, false) }
87
+ o.lazy
88
+ end
84
89
  end
85
90
  end
86
91
  end
@@ -10,6 +10,7 @@ module Datadog
10
10
  ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_RAILS_ANALYTICS_SAMPLE_RATE'.freeze
11
11
  ENV_ANALYTICS_SAMPLE_RATE_OLD = 'DD_RAILS_ANALYTICS_SAMPLE_RATE'.freeze
12
12
  ENV_DISABLE = 'DISABLE_DATADOG_RAILS'.freeze
13
+ ENV_LOGS_INJECTION_ENABLED = 'DD_LOGS_INJECTION'.freeze
13
14
  end
14
15
  end
15
16
  end
@@ -0,0 +1,81 @@
1
+ module Datadog
2
+ module Contrib
3
+ # Instrument Rails.
4
+ module Rails
5
+ # Rails log injection helper methods
6
+ module LogInjection
7
+ module_function
8
+
9
+ def add_lograge_logger(app)
10
+ # custom_options defaults to nil and can be either a hash or a lambda which returns a hash
11
+ # https://github.com/roidrage/lograge/blob/1729eab7956bb95c5992e4adab251e4f93ff9280/lib/lograge.rb#L28
12
+ if (custom_options = app.config.lograge.custom_options).nil?
13
+ # if it's not set, we set to a lambda that returns DD tracing context
14
+ app.config.lograge.custom_options = lambda do |_event|
15
+ # Retrieves trace information for current thread
16
+ correlation = Datadog.tracer.active_correlation
17
+
18
+ datadog_trace_log_hash(correlation)
19
+ end
20
+ # check if lambda, if so then define a new lambda which invokes the original lambda and
21
+ # merges the returned hash with the the DD tracing context hash.
22
+ elsif custom_options.respond_to?(:call)
23
+ app.config.lograge.custom_options = lambda do |event|
24
+ # invoke original lambda
25
+ result = custom_options.call(event)
26
+ # Retrieves trace information for current thread
27
+ correlation = Datadog.tracer.active_correlation
28
+ # merge original lambda with datadog context
29
+ result.merge(datadog_trace_log_hash(correlation))
30
+ end
31
+ # otherwise if it's just a static hash, we have to wrap that hash in a lambda to retrieve
32
+ # the DD tracing context, then merge the tracing context with the original static hash.
33
+ # don't modify if custom_options is not an accepted format.
34
+ elsif custom_options.is_a?(Hash)
35
+ app.config.lograge.custom_options = lambda do |_event|
36
+ # Retrieves trace information for current thread
37
+ correlation = Datadog.tracer.active_correlation
38
+
39
+ # merge original lambda with datadog context
40
+ custom_options.merge(datadog_trace_log_hash(correlation))
41
+ end
42
+ end
43
+ rescue StandardError => e
44
+ # TODO: can we use Datadog.logger at this point?
45
+ Datadog.logger.warn("Unable to add Datadog Trace context to Lograge: #{e.message}")
46
+ false
47
+ end
48
+
49
+ def add_as_tagged_logging_logger(app)
50
+ # we want to check if the current logger is a tagger logger instance
51
+ # log_tags defaults to nil so we have to set as an array if nothing exists yet
52
+ if (log_tags = app.config.log_tags).nil?
53
+ app.config.log_tags = [proc { Datadog.tracer.active_correlation.to_s }]
54
+ # if existing log_tags configuration exists, append to the end of the array
55
+ elsif log_tags.is_a?(Array)
56
+ app.config.log_tags << proc { Datadog.tracer.active_correlation.to_s }
57
+ end
58
+ rescue StandardError => e
59
+ # TODO: can we use Datadog.logger at this point?
60
+ Datadog.logger.warn("Unable to add Datadog Trace context to ActiveSupport::TaggedLogging: #{e.message}")
61
+ false
62
+ end
63
+
64
+ def datadog_trace_log_hash(correlation)
65
+ {
66
+ # Adds IDs as tags to log output
67
+ dd: {
68
+ # To preserve precision during JSON serialization, use strings for large numbers
69
+ trace_id: correlation.trace_id.to_s,
70
+ span_id: correlation.span_id.to_s,
71
+ env: correlation.env.to_s,
72
+ service: correlation.service.to_s,
73
+ version: correlation.version.to_s
74
+ },
75
+ ddsource: ['ruby']
76
+ }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -24,11 +24,16 @@ module Datadog
24
24
  rescue Exception => e
25
25
  tracer = Datadog.configuration[:rails][:tracer]
26
26
  span = tracer.active_span
27
- unless span.nil?
27
+ if !span.nil? && ActionPack::Utils.exception_is_error?(e)
28
28
  # Only set error if it's supposed to be flagged as such
29
29
  # e.g. we don't want to flag 404s.
30
30
  # You can add custom errors via `config.action_dispatch.rescue_responses`
31
- span.set_error(e) if ActionPack::Utils.exception_is_error?(e)
31
+ span.set_error(e)
32
+
33
+ # Some exception gets handled by Rails middleware before it can be set on Rack middleware
34
+ # The rack span is the root span of the request and should make sure it has the full exception
35
+ # set on it.
36
+ env[:datadog_rack_request_span].set_error(e) if env[:datadog_rack_request_span]
32
37
  end
33
38
  raise e
34
39
  end
@@ -1,6 +1,7 @@
1
1
  require 'ddtrace/contrib/rails/utils'
2
2
  require 'ddtrace/contrib/rails/framework'
3
3
  require 'ddtrace/contrib/rails/middlewares'
4
+ require 'ddtrace/contrib/rails/log_injection'
4
5
  require 'ddtrace/contrib/rack/middlewares'
5
6
 
6
7
  module Datadog
@@ -33,6 +34,7 @@ module Datadog
33
34
  # Otherwise the middleware stack will be frozen.
34
35
  # Sometimes we don't want to activate middleware e.g. OpenTracing, etc.
35
36
  add_middleware(app) if Datadog.configuration[:rails][:middleware]
37
+ add_logger(app) if Datadog.configuration[:rails][:log_injection]
36
38
  end
37
39
  end
38
40
 
@@ -50,6 +52,19 @@ module Datadog
50
52
  )
51
53
  end
52
54
 
55
+ def add_logger(app)
56
+ # check if lograge key exists
57
+ if app.config.respond_to?(:lograge) && app.config.lograge.enabled
58
+ Datadog::Contrib::Rails::LogInjection.add_lograge_logger(app)
59
+ # if lograge isn't set, check if tagged logged is enabed.
60
+ # if so, add proc that injects trace identifiers for tagged logging.
61
+ elsif (logger = app.config.logger) && logger.is_a?(::ActiveSupport::TaggedLogging)
62
+ Datadog::Contrib::Rails::LogInjection.add_as_tagged_logging_logger(app)
63
+ else
64
+ Datadog.logger.warn("Unabe to enable Datadog Trace context, Logger #{logger} is not supported")
65
+ end
66
+ end
67
+
53
68
  def patch_after_intialize
54
69
  ::ActiveSupport.on_load(:after_initialize) do
55
70
  Datadog::Contrib::Rails::Patcher.after_intialize(self)
@@ -8,12 +8,13 @@ module Datadog
8
8
  module Env
9
9
  module_function
10
10
 
11
- def datadog_span(env)
12
- env[Ext::RACK_ENV_REQUEST_SPAN]
11
+ def datadog_span(env, app)
12
+ env[Ext::RACK_ENV_REQUEST_SPAN][app]
13
13
  end
14
14
 
15
- def set_datadog_span(env, span)
16
- env[Ext::RACK_ENV_REQUEST_SPAN] = span
15
+ def set_datadog_span(env, app, span)
16
+ hash = (env[Ext::RACK_ENV_REQUEST_SPAN] ||= {})
17
+ hash[app] = span
17
18
  end
18
19
 
19
20
  def request_header_tags(env, headers)
@@ -36,21 +36,32 @@ module Datadog
36
36
  end
37
37
 
38
38
  def self.registered(app)
39
- app.use TracerMiddleware
39
+ app.use TracerMiddleware, app_instance: app
40
40
 
41
41
  app.after do
42
42
  configuration = Datadog.configuration[:sinatra]
43
43
  next unless configuration[:tracer].enabled
44
44
 
45
- # Ensures we only create a span for the top-most Sinatra middleware.
46
- #
47
- # If we traced all Sinatra middleware apps, we would have a chain
48
- # of nested spans that were not responsible for handling this request,
49
- # adding little value to users.
50
- next if Sinatra::Env.middleware_traced?(env)
51
-
52
- span = Sinatra::Env.datadog_span(env) || Sinatra::Tracer.create_middleware_span(env, configuration)
53
-
45
+ span = Sinatra::Env.datadog_span(env, app)
46
+
47
+ # TODO: `route` should *only* be populated if @datadog_route is defined.
48
+ # TODO: If @datadog_route is not defined, then this Sinatra app is not responsible
49
+ # TODO: for handling this request.
50
+ # TODO:
51
+ # TODO: This change would be BREAKING for any Sinatra app (classic or modular),
52
+ # TODO: as it affects the `resource` value for requests not handled by the Sinatra app.
53
+ # TODO: Currently we use "#{method} #{path}" in such aces, but `path` is the raw,
54
+ # TODO: high-cardinality HTTP path, and can contain PII.
55
+ # TODO:
56
+ # TODO: The value we should use as the `resource` when the Sinatra app is not
57
+ # TODO: responsible for the request is a tricky subject.
58
+ # TODO: The best option is a value that clearly communicates that this app did not
59
+ # TODO: handle this request. It's important to keep in mind that an unhandled request
60
+ # TODO: by this Sinatra app might still be handled by another Rack middleware (which can
61
+ # TODO: be a Sinatra app itself) or it might just 404 if not handled at all.
62
+ # TODO:
63
+ # TODO: A possible value for `resource` could set a high level description, e.g.
64
+ # TODO: `request.request_method`, given we don't have the response object available yet.
54
65
  route = if defined?(@datadog_route)
55
66
  @datadog_route
56
67
  else
@@ -59,36 +70,10 @@ module Datadog
59
70
  end
60
71
 
61
72
  span.resource = "#{request.request_method} #{route}"
62
-
63
- span.set_tag(Datadog::Ext::HTTP::URL, request.path)
64
- span.set_tag(Datadog::Ext::HTTP::METHOD, request.request_method)
65
- span.set_tag(Ext::TAG_APP_NAME, app.settings.name)
66
73
  span.set_tag(Ext::TAG_ROUTE_PATH, route)
67
- if request.script_name && !request.script_name.empty?
68
- span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
69
- end
70
-
71
- span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status)
72
- span.set_error(env['sinatra.error']) if response.server_error?
73
-
74
- Sinatra::Env.set_middleware_traced(env, true)
75
74
  end
76
75
  end
77
76
 
78
- # Initializes a span for the top-most Sinatra middleware.
79
- def self.create_middleware_span(env, configuration)
80
- tracer = configuration[:tracer]
81
- span = tracer.trace(
82
- Ext::SPAN_REQUEST,
83
- service: configuration[:service_name],
84
- span_type: Datadog::Ext::HTTP::TYPE_INBOUND,
85
- start_time: Sinatra::Env.middleware_start_time(env)
86
- )
87
-
88
- Sinatra::Env.set_datadog_span(env, span)
89
- span
90
- end
91
-
92
77
  # Method overrides for Sinatra::Base
93
78
  module Base
94
79
  def render(engine, data, *)
@@ -114,14 +99,8 @@ module Datadog
114
99
  def route_eval
115
100
  configuration = Datadog.configuration[:sinatra]
116
101
  tracer = configuration[:tracer]
117
-
118
102
  return super unless tracer.enabled
119
103
 
120
- # For initialization of Sinatra middleware span in order
121
- # guarantee that the Ext::SPAN_ROUTE span being created below
122
- # has the middleware span as its parent.
123
- Sinatra::Tracer.create_middleware_span(env, configuration) unless Sinatra::Env.datadog_span(env)
124
-
125
104
  tracer.trace(
126
105
  Ext::SPAN_ROUTE,
127
106
  service: configuration[:service_name],
@@ -8,10 +8,13 @@ module Datadog
8
8
  module Sinatra
9
9
  # Middleware used for automatically tagging configured headers and handle request span
10
10
  class TracerMiddleware
11
- def initialize(app)
11
+ def initialize(app, app_instance: nil)
12
12
  @app = app
13
+ @app_instance = app_instance
13
14
  end
14
15
 
16
+ # rubocop:disable Metrics/AbcSize
17
+ # rubocop:disable Metrics/MethodLength
15
18
  def call(env)
16
19
  # Set the trace context (e.g. distributed tracing)
17
20
  if configuration[:distributed_tracing] && tracer.provider.context.trace_id.nil?
@@ -19,35 +22,59 @@ module Datadog
19
22
  tracer.provider.context = context if context.trace_id
20
23
  end
21
24
 
22
- Sinatra::Env.set_middleware_start_time(env)
25
+ tracer.trace(
26
+ Ext::SPAN_REQUEST,
27
+ service: configuration[:service_name],
28
+ span_type: Datadog::Ext::HTTP::TYPE_INBOUND,
29
+ resource: env['REQUEST_METHOD']
30
+ ) do |span|
31
+ begin
32
+ Sinatra::Env.set_datadog_span(env, @app_instance, span)
23
33
 
24
- # Run application stack
25
- response = @app.call(env)
26
- ensure
27
- # Augment current Sinatra middleware span if we are the top-most Sinatra app on the Rack stack.
28
- span = Sinatra::Env.datadog_span(env)
29
- if span
30
- Sinatra::Env.request_header_tags(env, configuration[:headers][:request]).each do |name, value|
31
- span.set_tag(name, value) if span.get_tag(name).nil?
32
- end
33
-
34
- if response && (headers = response[1])
35
- Sinatra::Headers.response_header_tags(headers, configuration[:headers][:response]).each do |name, value|
34
+ response = @app.call(env)
35
+ ensure
36
+ Sinatra::Env.request_header_tags(env, configuration[:headers][:request]).each do |name, value|
36
37
  span.set_tag(name, value) if span.get_tag(name).nil?
37
38
  end
38
- end
39
39
 
40
- # Set analytics sample rate
41
- Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
40
+ request = ::Sinatra::Request.new(env)
41
+ span.set_tag(Datadog::Ext::HTTP::URL, request.path)
42
+ span.set_tag(Datadog::Ext::HTTP::METHOD, request.request_method)
43
+ if request.script_name && !request.script_name.empty?
44
+ span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
45
+ end
46
+
47
+ span.set_tag(Ext::TAG_APP_NAME, @app_instance.settings.name)
42
48
 
43
- # Measure service stats
44
- Contrib::Analytics.set_measured(span)
49
+ # TODO: This backfills the non-matching Sinatra app with a "#{method} #{path}"
50
+ # TODO: resource name. This shouldn't be the case, as that app has never handled
51
+ # TODO: the response with that resource.
52
+ # TODO: We should replace this backfill code with a clear `resource` that signals
53
+ # TODO: that this Sinatra span was *not* responsible for processing the current request.
54
+ rack_request_span = env[Datadog::Contrib::Rack::TraceMiddleware::RACK_REQUEST_SPAN]
55
+ span.resource = rack_request_span.resource if rack_request_span && rack_request_span.resource
45
56
 
46
- span.finish
57
+ if response
58
+ if (status = response[0])
59
+ sinatra_response = ::Sinatra::Response.new([], status) # Build object to use status code helpers
47
60
 
48
- # Remove span from env, so other Sinatra apps mounted on this same
49
- # Rack stack do not modify it with their own information.
50
- Sinatra::Env.set_datadog_span(env, nil)
61
+ span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, sinatra_response.status)
62
+ span.set_error(env['sinatra.error']) if sinatra_response.server_error?
63
+ end
64
+
65
+ if (headers = response[1])
66
+ Sinatra::Headers.response_header_tags(headers, configuration[:headers][:response]).each do |name, value|
67
+ span.set_tag(name, value) if span.get_tag(name).nil?
68
+ end
69
+ end
70
+ end
71
+
72
+ # Set analytics sample rate
73
+ Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
74
+
75
+ # Measure service stats
76
+ Contrib::Analytics.set_measured(span)
77
+ end
51
78
  end
52
79
  end
53
80