ddtrace 0.37.0 → 0.38.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Appraisals +15 -0
  4. data/CHANGELOG.md +33 -1
  5. data/Rakefile +11 -10
  6. data/docker-compose.yml +2 -2
  7. data/docs/GettingStarted.md +55 -0
  8. data/lib/ddtrace.rb +2 -0
  9. data/lib/ddtrace/configuration/settings.rb +18 -0
  10. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +1 -1
  11. data/lib/ddtrace/contrib/extensions.rb +10 -0
  12. data/lib/ddtrace/contrib/faraday/middleware.rb +5 -3
  13. data/lib/ddtrace/contrib/faraday/patcher.rb +3 -0
  14. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +1 -3
  15. data/lib/ddtrace/contrib/httprb/configuration/settings.rb +27 -0
  16. data/lib/ddtrace/contrib/httprb/ext.rb +14 -0
  17. data/lib/ddtrace/contrib/httprb/instrumentation.rb +163 -0
  18. data/lib/ddtrace/contrib/httprb/integration.rb +43 -0
  19. data/lib/ddtrace/contrib/httprb/patcher.rb +35 -0
  20. data/lib/ddtrace/contrib/kafka/configuration/settings.rb +25 -0
  21. data/lib/ddtrace/contrib/kafka/consumer_event.rb +14 -0
  22. data/lib/ddtrace/contrib/kafka/consumer_group_event.rb +14 -0
  23. data/lib/ddtrace/contrib/kafka/event.rb +51 -0
  24. data/lib/ddtrace/contrib/kafka/events.rb +44 -0
  25. data/lib/ddtrace/contrib/kafka/events/connection/request.rb +34 -0
  26. data/lib/ddtrace/contrib/kafka/events/consumer/process_batch.rb +41 -0
  27. data/lib/ddtrace/contrib/kafka/events/consumer/process_message.rb +39 -0
  28. data/lib/ddtrace/contrib/kafka/events/consumer_group/heartbeat.rb +39 -0
  29. data/lib/ddtrace/contrib/kafka/events/consumer_group/join_group.rb +29 -0
  30. data/lib/ddtrace/contrib/kafka/events/consumer_group/leave_group.rb +29 -0
  31. data/lib/ddtrace/contrib/kafka/events/consumer_group/sync_group.rb +29 -0
  32. data/lib/ddtrace/contrib/kafka/events/produce_operation/send_messages.rb +32 -0
  33. data/lib/ddtrace/contrib/kafka/events/producer/deliver_messages.rb +35 -0
  34. data/lib/ddtrace/contrib/kafka/ext.rb +38 -0
  35. data/lib/ddtrace/contrib/kafka/integration.rb +39 -0
  36. data/lib/ddtrace/contrib/kafka/patcher.rb +26 -0
  37. data/lib/ddtrace/contrib/rack/middlewares.rb +15 -12
  38. data/lib/ddtrace/contrib/rest_client/request_patch.rb +2 -2
  39. data/lib/ddtrace/contrib/sidekiq/ext.rb +1 -0
  40. data/lib/ddtrace/contrib/sidekiq/patcher.rb +8 -1
  41. data/lib/ddtrace/contrib/sidekiq/server_tracer.rb +1 -0
  42. data/lib/ddtrace/diagnostics/environment_logger.rb +278 -0
  43. data/lib/ddtrace/environment.rb +5 -1
  44. data/lib/ddtrace/ext/diagnostics.rb +2 -0
  45. data/lib/ddtrace/ext/environment.rb +2 -0
  46. data/lib/ddtrace/pipeline/span_filter.rb +15 -15
  47. data/lib/ddtrace/sampler.rb +2 -0
  48. data/lib/ddtrace/span.rb +10 -0
  49. data/lib/ddtrace/tracer.rb +13 -6
  50. data/lib/ddtrace/transport/http/adapters/net.rb +8 -0
  51. data/lib/ddtrace/transport/http/adapters/test.rb +4 -0
  52. data/lib/ddtrace/transport/http/adapters/unix_socket.rb +4 -0
  53. data/lib/ddtrace/transport/response.rb +11 -0
  54. data/lib/ddtrace/version.rb +1 -1
  55. data/lib/ddtrace/workers/trace_writer.rb +3 -0
  56. data/lib/ddtrace/writer.rb +33 -12
  57. metadata +27 -3
@@ -98,23 +98,26 @@ module Datadog
98
98
  request_span.set_error(e) unless request_span.nil?
99
99
  raise e
100
100
  ensure
101
- # Rack is a really low level interface and it doesn't provide any
102
- # advanced functionality like routers. Because of that, we assume that
103
- # the underlying framework or application has more knowledge about
104
- # the result for this request; `resource` and `tags` are expected to
105
- # be set in another level but if they're missing, reasonable defaults
106
- # are used.
107
- set_request_tags!(request_span, env, status, headers, response, original_env)
108
-
109
- # ensure the request_span is finished and the context reset;
110
- # this assumes that the Rack middleware creates a root span
111
- request_span.finish
101
+ if request_span
102
+ # Rack is a really low level interface and it doesn't provide any
103
+ # advanced functionality like routers. Because of that, we assume that
104
+ # the underlying framework or application has more knowledge about
105
+ # the result for this request; `resource` and `tags` are expected to
106
+ # be set in another level but if they're missing, reasonable defaults
107
+ # are used.
108
+ set_request_tags!(request_span, env, status, headers, response, original_env || env)
109
+
110
+ # ensure the request_span is finished and the context reset;
111
+ # this assumes that the Rack middleware creates a root span
112
+ request_span.finish
113
+ end
114
+
112
115
  frontend_span.finish unless frontend_span.nil?
113
116
 
114
117
  # TODO: Remove this once we change how context propagation works. This
115
118
  # ensures we clean thread-local variables on each HTTP request avoiding
116
119
  # memory leaks.
117
- tracer.provider.context = Datadog::Context.new
120
+ tracer.provider.context = Datadog::Context.new if tracer
118
121
  end
119
122
 
120
123
  def resource_name_for(env, status)
@@ -62,11 +62,11 @@ module Datadog
62
62
  # rubocop:disable Lint/RescueException
63
63
  rescue Exception => e
64
64
  # rubocop:enable Lint/RescueException
65
- span.set_error(e)
65
+ span.set_error(e) if span
66
66
 
67
67
  raise e
68
68
  ensure
69
- span.finish
69
+ span.finish if span
70
70
  end
71
71
 
72
72
  private
@@ -15,6 +15,7 @@ module Datadog
15
15
  TAG_JOB_ID = 'sidekiq.job.id'.freeze
16
16
  TAG_JOB_QUEUE = 'sidekiq.job.queue'.freeze
17
17
  TAG_JOB_RETRY = 'sidekiq.job.retry'.freeze
18
+ TAG_JOB_RETRY_COUNT = 'sidekiq.job.retry_count'.freeze
18
19
  TAG_JOB_WRAPPER = 'sidekiq.job.wrapper'.freeze
19
20
  TAG_JOB_ARGS = 'sidekiq.job.args'.freeze
20
21
  end
@@ -15,14 +15,21 @@ module Datadog
15
15
 
16
16
  def patch
17
17
  require 'ddtrace/contrib/sidekiq/client_tracer'
18
+ require 'ddtrace/contrib/sidekiq/server_tracer'
19
+
18
20
  ::Sidekiq.configure_client do |config|
19
21
  config.client_middleware do |chain|
20
22
  chain.add(Sidekiq::ClientTracer)
21
23
  end
22
24
  end
23
25
 
24
- require 'ddtrace/contrib/sidekiq/server_tracer'
25
26
  ::Sidekiq.configure_server do |config|
27
+ # If a job enqueues another job, make sure it has the same client
28
+ # middleware.
29
+ config.client_middleware do |chain|
30
+ chain.add(Sidekiq::ClientTracer)
31
+ end
32
+
26
33
  config.server_middleware do |chain|
27
34
  chain.add(Sidekiq::ServerTracer)
28
35
  end
@@ -31,6 +31,7 @@ module Datadog
31
31
 
32
32
  span.set_tag(Ext::TAG_JOB_ID, job['jid'])
33
33
  span.set_tag(Ext::TAG_JOB_RETRY, job['retry'])
34
+ span.set_tag(Ext::TAG_JOB_RETRY_COUNT, job['retry_count'])
34
35
  span.set_tag(Ext::TAG_JOB_QUEUE, job['queue'])
35
36
  span.set_tag(Ext::TAG_JOB_WRAPPER, job['class']) if job['wrapped']
36
37
  span.set_tag(Ext::TAG_JOB_DELAY, 1000.0 * (Time.now.utc.to_f - job['enqueued_at'].to_f))
@@ -0,0 +1,278 @@
1
+ require 'date'
2
+ require 'json'
3
+ require 'rbconfig'
4
+
5
+ module Datadog
6
+ module Diagnostics
7
+ # A holistic collection of the environment in which ddtrace is running.
8
+ # This logger should allow for easy reporting by users to Datadog support.
9
+ #
10
+ # rubocop:disable Style/DoubleNegation
11
+ module EnvironmentLogger
12
+ class << self
13
+ # Outputs environment information to {Datadog.logger}.
14
+ # Executes only for the lifetime of the program.
15
+ def log!(transport_responses)
16
+ return if @executed || !log?
17
+ @executed = true
18
+
19
+ data = EnvironmentCollector.new.collect!(transport_responses)
20
+ data.reject! { |_, v| v.nil? } # Remove empty values from hash output
21
+
22
+ log_environment!(data.to_json)
23
+ log_error!('Agent Error'.freeze, data[:agent_error]) if data[:agent_error]
24
+ rescue => e
25
+ Datadog.logger.warn("Failed to collect environment information: #{e} location: #{e.backtrace.first}")
26
+ end
27
+
28
+ private
29
+
30
+ def log_environment!(line)
31
+ Datadog.logger.warn("DATADOG TRACER CONFIGURATION - #{line}")
32
+ end
33
+
34
+ def log_error!(type, error)
35
+ Datadog.logger.warn("DATADOG TRACER DIAGNOSTIC - #{type}: #{error}")
36
+ end
37
+
38
+ # Are we logging the environment data?
39
+ def log?
40
+ startup_logs_enabled = Datadog.configuration.diagnostics.startup_logs.enabled
41
+ if startup_logs_enabled.nil?
42
+ !repl? # Suppress logs if we running in a REPL
43
+ else
44
+ startup_logs_enabled
45
+ end
46
+ end
47
+
48
+ REPL_PROGRAM_NAMES = %w[irb pry].freeze
49
+
50
+ def repl?
51
+ REPL_PROGRAM_NAMES.include?($PROGRAM_NAME)
52
+ end
53
+ end
54
+ end
55
+
56
+ # Collects environment information for diagnostic logging
57
+ class EnvironmentCollector
58
+ # @return [String] current time in ISO8601 format
59
+ def date
60
+ DateTime.now.iso8601
61
+ end
62
+
63
+ # Best portable guess of OS information.
64
+ # @return [String] platform string
65
+ def os_name
66
+ RbConfig::CONFIG['host'.freeze]
67
+ end
68
+
69
+ # @return [String] ddtrace version
70
+ def version
71
+ VERSION::STRING
72
+ end
73
+
74
+ # @return [String] "ruby"
75
+ def lang
76
+ Ext::Runtime::LANG
77
+ end
78
+
79
+ # Supported Ruby language version.
80
+ # Will be distinct from VM version for non-MRI environments.
81
+ # @return [String]
82
+ def lang_version
83
+ Ext::Runtime::LANG_VERSION
84
+ end
85
+
86
+ # @return [String] configured application environment
87
+ def env
88
+ Datadog.configuration.env
89
+ end
90
+
91
+ # @return [Boolean, nil]
92
+ def enabled
93
+ Datadog.configuration.tracer.enabled
94
+ end
95
+
96
+ # @return [String] configured application service name
97
+ def service
98
+ Datadog.configuration.service
99
+ end
100
+
101
+ # @return [String] configured application version
102
+ def dd_version
103
+ Datadog.configuration.version
104
+ end
105
+
106
+ # @return [String] target agent URL for trace flushing
107
+ def agent_url
108
+ # Retrieve the effect agent URL, regardless of how it was configured
109
+ transport = Datadog.tracer.writer.transport
110
+ adapter = transport.client.api.adapter
111
+ adapter.url
112
+ end
113
+
114
+ # Error returned by Datadog agent during a tracer flush attempt
115
+ # @return [String] concatenated list of transport errors
116
+ def agent_error(transport_responses)
117
+ error_responses = transport_responses.reject(&:ok?)
118
+
119
+ return nil if error_responses.empty?
120
+
121
+ error_responses.map(&:inspect).join(','.freeze)
122
+ end
123
+
124
+ # @return [Boolean, nil] debug mode enabled in configuration
125
+ def debug
126
+ !!Datadog.configuration.diagnostics.debug
127
+ end
128
+
129
+ # @return [Boolean, nil] analytics enabled in configuration
130
+ def analytics_enabled
131
+ !!Datadog.configuration.analytics.enabled
132
+ end
133
+
134
+ # @return [Numeric, nil] tracer sample rate configured
135
+ def sample_rate
136
+ sampler = Datadog.configuration.tracer.sampler
137
+ return nil unless sampler
138
+
139
+ sampler.sample_rate(nil) rescue nil
140
+ end
141
+
142
+ # DEV: We currently only support SimpleRule instances.
143
+ # DEV: These are the most commonly used rules.
144
+ # DEV: We should expand support for other rules in the future,
145
+ # DEV: although it is tricky to serialize arbitrary rules.
146
+ #
147
+ # @return [Hash, nil] sample rules configured
148
+ def sampling_rules
149
+ sampler = Datadog.configuration.tracer.sampler
150
+ return nil unless sampler.is_a?(Datadog::PrioritySampler) &&
151
+ sampler.priority_sampler.is_a?(Datadog::Sampling::RuleSampler)
152
+
153
+ sampler.priority_sampler.rules.map do |rule|
154
+ next unless rule.is_a?(Datadog::Sampling::SimpleRule)
155
+
156
+ {
157
+ name: rule.matcher.name,
158
+ service: rule.matcher.service,
159
+ sample_rate: rule.sampler.sample_rate(nil)
160
+ }
161
+ end.compact
162
+ end
163
+
164
+ # @return [Hash, nil] concatenated list of global tracer tags configured
165
+ def tags
166
+ tags = Datadog.configuration.tags
167
+ return nil if tags.empty?
168
+ hash_serializer(tags)
169
+ end
170
+
171
+ # @return [Boolean, nil] runtime metrics enabled in configuration
172
+ def runtime_metrics_enabled
173
+ Datadog.configuration.runtime_metrics.enabled
174
+ end
175
+
176
+ # Concatenated list of integrations activated, with their gem version.
177
+ # Example: "rails@6.0.3,rack@2.2.3"
178
+ #
179
+ # @return [String, nil]
180
+ def integrations_loaded
181
+ integrations = instrumented_integrations
182
+ return if integrations.empty?
183
+
184
+ integrations.map { |name, integration| "#{name}@#{integration.class.version}" }.join(','.freeze)
185
+ end
186
+
187
+ # Ruby VM name and version.
188
+ # Examples: "ruby-2.7.1", "jruby-9.2.11.1", "truffleruby-20.1.0"
189
+ # @return [String, nil]
190
+ def vm
191
+ # RUBY_ENGINE_VERSION returns the VM version, which
192
+ # will differ from RUBY_VERSION for non-mri VMs.
193
+ if defined?(RUBY_ENGINE_VERSION)
194
+ "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}"
195
+ else
196
+ # Ruby < 2.3 doesn't support RUBY_ENGINE_VERSION
197
+ "#{RUBY_ENGINE}-#{RUBY_VERSION}"
198
+ end
199
+ end
200
+
201
+ # @return [Boolean, nil] partial flushing enabled in configuration
202
+ def partial_flushing_enabled
203
+ !!Datadog.configuration.tracer.partial_flush.enabled
204
+ end
205
+
206
+ # @return [Boolean, nil] priority sampling enabled in configuration
207
+ def priority_sampling_enabled
208
+ !!Datadog.configuration.tracer.priority_sampling
209
+ end
210
+
211
+ # @return [Boolean, nil] health metrics enabled in configuration
212
+ def health_metrics_enabled
213
+ !!Datadog.configuration.diagnostics.health_metrics.enabled
214
+ end
215
+
216
+ # TODO: Populate when profiling is implemented
217
+ # def profiling_enabled
218
+ # end
219
+
220
+ # TODO: Populate when automatic log correlation is implemented
221
+ # def logs_correlation_enabled
222
+ # end
223
+
224
+ # @return [Hash] environment information available at call time
225
+ def collect!(transport_responses)
226
+ {
227
+ date: date,
228
+ os_name: os_name,
229
+ version: version,
230
+ lang: lang,
231
+ lang_version: lang_version,
232
+ env: env,
233
+ enabled: enabled,
234
+ service: service,
235
+ dd_version: dd_version,
236
+ agent_url: agent_url,
237
+ agent_error: agent_error(transport_responses),
238
+ debug: debug,
239
+ analytics_enabled: analytics_enabled,
240
+ sample_rate: sample_rate,
241
+ sampling_rules: sampling_rules,
242
+ tags: tags,
243
+ runtime_metrics_enabled: runtime_metrics_enabled,
244
+ integrations_loaded: integrations_loaded,
245
+ vm: vm,
246
+ partial_flushing_enabled: partial_flushing_enabled,
247
+ priority_sampling_enabled: priority_sampling_enabled,
248
+ health_metrics_enabled: health_metrics_enabled,
249
+ **instrumented_integrations_settings
250
+ }
251
+ end
252
+
253
+ private
254
+
255
+ def instrumented_integrations
256
+ Datadog.configuration.instrumented_integrations
257
+ end
258
+
259
+ # Capture all active integration settings into "integrationName_settingName: value" entries.
260
+ def instrumented_integrations_settings
261
+ Hash[instrumented_integrations.flat_map do |name, integration|
262
+ integration.configuration.to_h.flat_map do |setting, value|
263
+ next [] if setting == :tracer # Skip internal Ruby objects
264
+
265
+ # Convert value to a string to avoid custom #to_json
266
+ # handlers possibly causing errors.
267
+ [[:"integration_#{name}_#{setting}", value.to_s]]
268
+ end
269
+ end]
270
+ end
271
+
272
+ # Outputs "k1:v1,k2:v2,..."
273
+ def hash_serializer(h)
274
+ h.map { |k, v| "#{k}:#{v}" }.join(','.freeze)
275
+ end
276
+ end
277
+ end
278
+ end
@@ -6,7 +6,11 @@ module Datadog
6
6
  # Defines helper methods for environment
7
7
  module Helpers
8
8
  def env_to_bool(var, default = nil)
9
- ENV.key?(var) ? ENV[var].to_s.downcase == 'true' : default
9
+ ENV.key?(var) ? ENV[var].to_s.strip.downcase == 'true' : default
10
+ end
11
+
12
+ def env_to_int(var, default = nil)
13
+ ENV.key?(var) ? ENV[var].to_i : default
10
14
  end
11
15
 
12
16
  def env_to_float(var, default = nil)
@@ -1,6 +1,8 @@
1
1
  module Datadog
2
2
  module Ext
3
3
  module Diagnostics
4
+ DD_TRACE_STARTUP_LOGS = 'DD_TRACE_STARTUP_LOGS'.freeze
5
+
4
6
  # Health
5
7
  module Health
6
8
  # Metrics
@@ -1,8 +1,10 @@
1
1
  module Datadog
2
2
  module Ext
3
3
  module Environment
4
+ ENV_API_KEY = 'DD_API_KEY'.freeze
4
5
  ENV_ENVIRONMENT = 'DD_ENV'.freeze
5
6
  ENV_SERVICE = 'DD_SERVICE'.freeze
7
+ ENV_SITE = 'DD_SITE'.freeze
6
8
  ENV_TAGS = 'DD_TAGS'.freeze
7
9
  ENV_VERSION = 'DD_VERSION'.freeze
8
10
 
@@ -10,12 +10,22 @@ module Datadog
10
10
  @criteria = filter || block
11
11
  end
12
12
 
13
+ # Note: this SpanFilter implementation only handles traces in which child spans appear
14
+ # after parent spans in the trace array. If in the future child spans can be before
15
+ # parent spans, then the code below will need to be updated.
13
16
  def call(trace)
14
- black_list = trace.select(&method(:drop_it?))
15
-
16
- clean_trace(black_list, trace) while black_list.any?
17
-
18
- trace
17
+ deleted = Set.new
18
+
19
+ trace.delete_if do |span|
20
+ if deleted.include?(span.parent)
21
+ deleted << span
22
+ true
23
+ else
24
+ drop = drop_it?(span)
25
+ deleted << span if drop
26
+ drop
27
+ end
28
+ end
19
29
  end
20
30
 
21
31
  private
@@ -23,16 +33,6 @@ module Datadog
23
33
  def drop_it?(span)
24
34
  @criteria.call(span) rescue false
25
35
  end
26
-
27
- def clean_trace(black_list, trace)
28
- current = black_list.shift
29
-
30
- trace.delete(current)
31
-
32
- trace.each do |span|
33
- black_list << span if span.parent == current
34
- end
35
- end
36
36
  end
37
37
  end
38
38
  end