ddtrace 0.35.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -1
  3. data/.gitignore +2 -0
  4. data/.gitlab-ci.yml +26 -0
  5. data/.rubocop.yml +4 -0
  6. data/Appraisals +23 -6
  7. data/CHANGELOG.md +116 -1
  8. data/Rakefile +43 -12
  9. data/ddtrace.gemspec +5 -0
  10. data/docker-compose.yml +37 -2
  11. data/docs/GettingStarted.md +63 -34
  12. data/lib/ddtrace.rb +2 -0
  13. data/lib/ddtrace/configuration/base.rb +1 -1
  14. data/lib/ddtrace/configuration/components.rb +2 -2
  15. data/lib/ddtrace/configuration/options.rb +1 -1
  16. data/lib/ddtrace/configuration/pin_setup.rb +3 -2
  17. data/lib/ddtrace/configuration/settings.rb +18 -0
  18. data/lib/ddtrace/contrib/active_support/cache/redis.rb +1 -1
  19. data/lib/ddtrace/contrib/active_support/notifications/event.rb +3 -1
  20. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +1 -1
  21. data/lib/ddtrace/contrib/concurrent_ruby/context_composite_executor_service.rb +9 -3
  22. data/lib/ddtrace/contrib/configuration/settings.rb +19 -1
  23. data/lib/ddtrace/contrib/dalli/patcher.rb +1 -5
  24. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -2
  25. data/lib/ddtrace/contrib/extensions.rb +38 -4
  26. data/lib/ddtrace/contrib/faraday/middleware.rb +5 -3
  27. data/lib/ddtrace/contrib/faraday/patcher.rb +4 -5
  28. data/lib/ddtrace/contrib/grape/patcher.rb +1 -1
  29. data/lib/ddtrace/contrib/graphql/patcher.rb +6 -3
  30. data/lib/ddtrace/contrib/grpc/datadog_interceptor.rb +1 -1
  31. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +1 -3
  32. data/lib/ddtrace/contrib/grpc/patcher.rb +1 -5
  33. data/lib/ddtrace/contrib/http/circuit_breaker.rb +8 -32
  34. data/lib/ddtrace/contrib/http/instrumentation.rb +13 -8
  35. data/lib/ddtrace/contrib/httprb/configuration/settings.rb +27 -0
  36. data/lib/ddtrace/contrib/httprb/ext.rb +14 -0
  37. data/lib/ddtrace/contrib/httprb/instrumentation.rb +163 -0
  38. data/lib/ddtrace/contrib/httprb/integration.rb +43 -0
  39. data/lib/ddtrace/contrib/httprb/patcher.rb +35 -0
  40. data/lib/ddtrace/contrib/kafka/configuration/settings.rb +25 -0
  41. data/lib/ddtrace/contrib/kafka/consumer_event.rb +14 -0
  42. data/lib/ddtrace/contrib/kafka/consumer_group_event.rb +14 -0
  43. data/lib/ddtrace/contrib/kafka/event.rb +51 -0
  44. data/lib/ddtrace/contrib/kafka/events.rb +44 -0
  45. data/lib/ddtrace/contrib/kafka/events/connection/request.rb +34 -0
  46. data/lib/ddtrace/contrib/kafka/events/consumer/process_batch.rb +41 -0
  47. data/lib/ddtrace/contrib/kafka/events/consumer/process_message.rb +39 -0
  48. data/lib/ddtrace/contrib/kafka/events/consumer_group/heartbeat.rb +39 -0
  49. data/lib/ddtrace/contrib/kafka/events/consumer_group/join_group.rb +29 -0
  50. data/lib/ddtrace/contrib/kafka/events/consumer_group/leave_group.rb +29 -0
  51. data/lib/ddtrace/contrib/kafka/events/consumer_group/sync_group.rb +29 -0
  52. data/lib/ddtrace/contrib/kafka/events/produce_operation/send_messages.rb +32 -0
  53. data/lib/ddtrace/contrib/kafka/events/producer/deliver_messages.rb +35 -0
  54. data/lib/ddtrace/contrib/kafka/ext.rb +38 -0
  55. data/lib/ddtrace/contrib/kafka/integration.rb +39 -0
  56. data/lib/ddtrace/contrib/kafka/patcher.rb +26 -0
  57. data/lib/ddtrace/contrib/mongodb/instrumentation.rb +1 -2
  58. data/lib/ddtrace/contrib/mysql2/instrumentation.rb +1 -1
  59. data/lib/ddtrace/contrib/patcher.rb +14 -8
  60. data/lib/ddtrace/contrib/rack/middlewares.rb +15 -12
  61. data/lib/ddtrace/contrib/rails/configuration/settings.rb +13 -11
  62. data/lib/ddtrace/contrib/rails/framework.rb +52 -46
  63. data/lib/ddtrace/contrib/rails/integration.rb +1 -1
  64. data/lib/ddtrace/contrib/redis/patcher.rb +1 -1
  65. data/lib/ddtrace/contrib/rest_client/request_patch.rb +2 -2
  66. data/lib/ddtrace/contrib/sequel/database.rb +1 -1
  67. data/lib/ddtrace/contrib/sidekiq/ext.rb +1 -0
  68. data/lib/ddtrace/contrib/sidekiq/patcher.rb +8 -1
  69. data/lib/ddtrace/contrib/sidekiq/server_tracer.rb +1 -0
  70. data/lib/ddtrace/contrib/sucker_punch/patcher.rb +1 -1
  71. data/lib/ddtrace/diagnostics/environment_logger.rb +278 -0
  72. data/lib/ddtrace/environment.rb +5 -1
  73. data/lib/ddtrace/ext/diagnostics.rb +2 -0
  74. data/lib/ddtrace/ext/environment.rb +2 -0
  75. data/lib/ddtrace/pin.rb +39 -15
  76. data/lib/ddtrace/pipeline/span_filter.rb +15 -15
  77. data/lib/ddtrace/sampler.rb +2 -0
  78. data/lib/ddtrace/span.rb +10 -0
  79. data/lib/ddtrace/tracer.rb +15 -8
  80. data/lib/ddtrace/transport/http/adapters/net.rb +8 -0
  81. data/lib/ddtrace/transport/http/adapters/test.rb +4 -0
  82. data/lib/ddtrace/transport/http/adapters/unix_socket.rb +4 -0
  83. data/lib/ddtrace/transport/response.rb +11 -0
  84. data/lib/ddtrace/version.rb +1 -1
  85. data/lib/ddtrace/workers/trace_writer.rb +3 -0
  86. data/lib/ddtrace/writer.rb +33 -12
  87. metadata +98 -3
@@ -49,14 +49,13 @@ module Datadog
49
49
  module InstanceMethods
50
50
  def datadog_pin
51
51
  @datadog_pin ||= begin
52
- tracer = Datadog.configuration[:mongo][:tracer]
53
52
  service = Datadog.configuration[:mongo][:service_name]
54
53
 
55
54
  Datadog::Pin.new(
56
55
  service,
57
56
  app: Datadog::Contrib::MongoDB::Ext::APP,
58
57
  app_type: Datadog::Ext::AppTypes::DB,
59
- tracer: tracer
58
+ tracer: -> { Datadog.configuration[:mongo][:tracer] }
60
59
  )
61
60
  end
62
61
  end
@@ -36,7 +36,7 @@ module Datadog
36
36
  Datadog.configuration[:mysql2][:service_name],
37
37
  app: Ext::APP,
38
38
  app_type: Datadog::Ext::AppTypes::DB,
39
- tracer: Datadog.configuration[:mysql2][:tracer]
39
+ tracer: -> { Datadog.configuration[:mysql2][:tracer] }
40
40
  )
41
41
  end
42
42
 
@@ -31,18 +31,24 @@ module Datadog
31
31
  Datadog.health_metrics.instrumentation_patched(1, tags: default_tags)
32
32
  end
33
33
  rescue StandardError => e
34
- # Log the error
35
- Datadog.logger.error("Failed to apply #{patch_name} patch. Cause: #{e} Location: #{e.backtrace.first}")
36
-
37
- # Emit a metric
38
- tags = default_tags
39
- tags << "error:#{e.class.name}"
40
-
41
- Datadog.health_metrics.error_instrumentation_patch(1, tags: tags)
34
+ on_patch_error(e)
42
35
  end
43
36
  end
44
37
  end
45
38
 
39
+ # Processes patching errors. This default implementation logs the error and reports relevant metrics.
40
+ # @param e [Exception]
41
+ def on_patch_error(e)
42
+ # Log the error
43
+ Datadog.logger.error("Failed to apply #{patch_name} patch. Cause: #{e} Location: #{e.backtrace.first}")
44
+
45
+ # Emit a metric
46
+ tags = default_tags
47
+ tags << "error:#{e.class.name}"
48
+
49
+ Datadog.health_metrics.error_instrumentation_patch(1, tags: tags)
50
+ end
51
+
46
52
  private
47
53
 
48
54
  def default_tags
@@ -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)
@@ -6,6 +6,19 @@ module Datadog
6
6
  module Configuration
7
7
  # Custom settings for the Rails integration
8
8
  class Settings < Contrib::Configuration::Settings
9
+ def initialize(options = {})
10
+ super(options)
11
+
12
+ # NOTE: Eager load these
13
+ # Rails integration is responsible for orchestrating other integrations.
14
+ # When using environment variables, settings will not be automatically
15
+ # filled because nothing explicitly calls them. They must though, so
16
+ # integrations like ActionPack can receive the value as it should.
17
+ # Trigger these manually to force an eager load and propagate them.
18
+ analytics_enabled
19
+ analytics_sample_rate
20
+ end
21
+
9
22
  option :analytics_enabled do |o|
10
23
  o.default { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, nil) }
11
24
  o.lazy
@@ -63,17 +76,6 @@ module Datadog
63
76
  Datadog.configuration[:action_view][:template_base_path] = value
64
77
  end
65
78
  end
66
-
67
- option :tracer do |o|
68
- o.delegate_to { Datadog.tracer }
69
- o.on_set do |value|
70
- Datadog.configuration[:action_cable][:tracer] = value
71
- Datadog.configuration[:active_record][:tracer] = value
72
- Datadog.configuration[:active_support][:tracer] = value
73
- Datadog.configuration[:action_pack][:tracer] = value
74
- Datadog.configuration[:action_view][:tracer] = value
75
- end
76
- end
77
79
  end
78
80
  end
79
81
  end
@@ -19,32 +19,40 @@ module Datadog
19
19
  # - handle configuration entries which are specific to Datadog tracing
20
20
  # - instrument parts of the framework when needed
21
21
  module Framework
22
- # configure Datadog settings
22
+ # After the Rails application finishes initializing, we configure the Rails
23
+ # integration and all its sub-components with the application information
24
+ # available.
25
+ # We do this after the initialization because not all the information we
26
+ # require is available before then.
23
27
  def self.setup
24
- config = config_with_defaults
25
-
26
- activate_rack!(config)
27
- activate_action_cable!(config)
28
- activate_active_support!(config)
29
- activate_action_pack!(config)
30
- activate_action_view!(config)
31
- activate_active_record!(config)
32
-
33
- # By default, default service would be guessed from the script
34
- # being executed, but here we know better, get it from Rails config.
35
- # Don't set this if service has been explicitly provided by the user.
36
- Datadog.configuration.service ||= config[:service_name]
37
-
38
- # Update the tracer if its not the default tracer.
39
- if config[:tracer] != Datadog.configuration.tracer
40
- config[:tracer].default_service = config[:service_name]
28
+ # NOTE: #configure has the side effect of rebuilding trace components.
29
+ # During a typical Rails application lifecycle, we will see trace
30
+ # components initialized twice because of this. This is necessary
31
+ # because key configuration is not available until after the Rails
32
+ # application has fully loaded, and some of this configuration is
33
+ # used to reconfigure tracer components with Rails-sourced defaults.
34
+ # This is a trade-off we take to get nice defaults.
35
+ Datadog.configure do |datadog_config|
36
+ rails_config = config_with_defaults(datadog_config)
37
+
38
+ # By default, default service would be guessed from the script
39
+ # being executed, but here we know better, get it from Rails config.
40
+ # Don't set this if service has been explicitly provided by the user.
41
+ datadog_config.service ||= rails_config[:service_name]
42
+
43
+ activate_rack!(datadog_config, rails_config)
44
+ activate_action_cable!(datadog_config, rails_config)
45
+ activate_active_support!(datadog_config, rails_config)
46
+ activate_action_pack!(datadog_config, rails_config)
47
+ activate_action_view!(datadog_config, rails_config)
48
+ activate_active_record!(datadog_config, rails_config)
41
49
  end
42
50
  end
43
51
 
44
- def self.config_with_defaults
52
+ def self.config_with_defaults(datadog_config)
45
53
  # We set defaults here instead of in the patcher because we need to wait
46
54
  # for the Rails application to be fully initialized.
47
- Datadog.configuration[:rails].tap do |config|
55
+ datadog_config[:rails].tap do |config|
48
56
  config[:service_name] ||= (Datadog.configuration.service || Utils.app_name)
49
57
  config[:database_service] ||= "#{config[:service_name]}-#{Contrib::ActiveRecord::Utils.adapter_name}"
50
58
  config[:controller_service] ||= config[:service_name]
@@ -52,64 +60,62 @@ module Datadog
52
60
  end
53
61
  end
54
62
 
55
- def self.activate_rack!(config)
56
- Datadog.configuration.use(
63
+ def self.activate_rack!(datadog_config, rails_config)
64
+ datadog_config.use(
57
65
  :rack,
58
- tracer: config[:tracer],
59
66
  application: ::Rails.application,
60
- service_name: config[:service_name],
61
- middleware_names: config[:middleware_names],
62
- distributed_tracing: config[:distributed_tracing]
67
+ service_name: rails_config[:service_name],
68
+ middleware_names: rails_config[:middleware_names],
69
+ distributed_tracing: rails_config[:distributed_tracing]
63
70
  )
64
71
  end
65
72
 
66
- def self.activate_active_support!(config)
73
+ def self.activate_active_support!(datadog_config, rails_config)
67
74
  return unless defined?(::ActiveSupport)
68
75
 
69
- Datadog.configuration.use(
76
+ datadog_config.use(
70
77
  :active_support,
71
- cache_service: config[:cache_service],
72
- tracer: config[:tracer]
78
+ cache_service: rails_config[:cache_service]
73
79
  )
74
80
  end
75
81
 
76
- def self.activate_action_cable!(config)
82
+ def self.activate_action_cable!(datadog_config, rails_config)
77
83
  return unless defined?(::ActionCable)
78
84
 
79
- Datadog.configuration.use(
85
+ datadog_config.use(
80
86
  :action_cable,
81
- service_name: "#{config[:service_name]}-#{Contrib::ActionCable::Ext::SERVICE_NAME}",
82
- tracer: config[:tracer]
87
+ service_name: "#{rails_config[:service_name]}-#{Contrib::ActionCable::Ext::SERVICE_NAME}"
83
88
  )
84
89
  end
85
90
 
86
- def self.activate_action_pack!(config)
91
+ def self.activate_action_pack!(datadog_config, rails_config)
87
92
  return unless defined?(::ActionPack)
88
93
 
89
- Datadog.configuration.use(
94
+ # TODO: This is configuring ActionPack but not patching. It will queue ActionPack
95
+ # for patching, but patching won't take place until Datadog.configure completes.
96
+ # Should we manually patch here?
97
+
98
+ datadog_config.use(
90
99
  :action_pack,
91
- service_name: config[:service_name],
92
- tracer: config[:tracer]
100
+ service_name: rails_config[:service_name]
93
101
  )
94
102
  end
95
103
 
96
- def self.activate_action_view!(config)
104
+ def self.activate_action_view!(datadog_config, rails_config)
97
105
  return unless defined?(::ActionView)
98
106
 
99
- Datadog.configuration.use(
107
+ datadog_config.use(
100
108
  :action_view,
101
- service_name: config[:service_name],
102
- tracer: config[:tracer]
109
+ service_name: rails_config[:service_name]
103
110
  )
104
111
  end
105
112
 
106
- def self.activate_active_record!(config)
113
+ def self.activate_active_record!(datadog_config, rails_config)
107
114
  return unless defined?(::ActiveRecord)
108
115
 
109
- Datadog.configuration.use(
116
+ datadog_config.use(
110
117
  :active_record,
111
- service_name: config[:database_service],
112
- tracer: config[:tracer]
118
+ service_name: rails_config[:database_service]
113
119
  )
114
120
  end
115
121
  end
@@ -16,7 +16,7 @@ module Datadog
16
16
  register_as :rails, auto_patch: false
17
17
 
18
18
  def self.version
19
- Gem.loaded_specs['rails'] && Gem.loaded_specs['rails'].version
19
+ Gem.loaded_specs['railties'] && Gem.loaded_specs['railties'].version
20
20
  end
21
21
 
22
22
  def self.loaded?
@@ -76,7 +76,7 @@ module Datadog
76
76
  datadog_configuration[:service_name],
77
77
  app: Ext::APP,
78
78
  app_type: Datadog::Ext::AppTypes::DB,
79
- tracer: datadog_configuration[:tracer]
79
+ tracer: -> { datadog_configuration[:tracer] }
80
80
  )
81
81
  pin.onto(self)
82
82
  end
@@ -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
@@ -36,7 +36,7 @@ module Datadog
36
36
  Datadog.configuration[:sequel][:service_name] || adapter_name,
37
37
  app: Ext::APP,
38
38
  app_type: Datadog::Ext::AppTypes::DB,
39
- tracer: Datadog.configuration[:sequel][:tracer] || Datadog.tracer
39
+ tracer: -> { Datadog.configuration[:sequel][:tracer] }
40
40
  )
41
41
  end
42
42
 
@@ -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))
@@ -29,7 +29,7 @@ module Datadog
29
29
  get_option(:service_name),
30
30
  app: Ext::APP,
31
31
  app_type: Datadog::Ext::AppTypes::WORKER,
32
- tracer: get_option(:tracer)
32
+ tracer: -> { get_option(:tracer) }
33
33
  ).onto(::SuckerPunch)
34
34
  end
35
35
 
@@ -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