ddtrace 0.39.0 → 0.40.0

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