datadog 2.8.0 → 2.10.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +66 -56
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +221 -127
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +2 -2
  13. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  16. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -76
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -2
  20. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  22. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  23. data/ext/libdatadog_api/crashtracker.c +3 -0
  24. data/lib/datadog/appsec/actions_handler.rb +27 -0
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  26. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  27. data/lib/datadog/appsec/component.rb +14 -8
  28. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  29. data/lib/datadog/appsec/context.rb +74 -0
  30. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +12 -8
  31. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  33. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +20 -30
  35. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +6 -6
  36. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +67 -96
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
  39. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +6 -6
  40. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +7 -7
  41. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -60
  43. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -33
  44. data/lib/datadog/appsec/contrib/rails/patcher.rb +4 -14
  45. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +7 -7
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +45 -65
  47. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +5 -28
  48. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +6 -6
  49. data/lib/datadog/appsec/event.rb +6 -6
  50. data/lib/datadog/appsec/ext.rb +8 -1
  51. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  52. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  53. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  54. data/lib/datadog/appsec/metrics.rb +13 -0
  55. data/lib/datadog/appsec/monitor/gateway/watcher.rb +23 -32
  56. data/lib/datadog/appsec/monitor/reactive/set_user.rb +6 -6
  57. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  58. data/lib/datadog/appsec/processor.rb +4 -3
  59. data/lib/datadog/appsec/response.rb +18 -80
  60. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  61. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  62. data/lib/datadog/appsec/security_engine.rb +9 -0
  63. data/lib/datadog/appsec.rb +17 -8
  64. data/lib/datadog/auto_instrument.rb +3 -0
  65. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  66. data/lib/datadog/core/configuration/components.rb +4 -2
  67. data/lib/datadog/core/configuration.rb +1 -1
  68. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  69. data/lib/datadog/core/crashtracking/component.rb +1 -3
  70. data/lib/datadog/core/telemetry/event.rb +87 -3
  71. data/lib/datadog/core/telemetry/logging.rb +2 -2
  72. data/lib/datadog/core/telemetry/metric.rb +22 -0
  73. data/lib/datadog/core/telemetry/worker.rb +33 -0
  74. data/lib/datadog/di/base.rb +115 -0
  75. data/lib/datadog/di/code_tracker.rb +7 -4
  76. data/lib/datadog/di/component.rb +19 -11
  77. data/lib/datadog/di/configuration/settings.rb +11 -1
  78. data/lib/datadog/di/contrib/railtie.rb +15 -0
  79. data/lib/datadog/di/contrib.rb +26 -0
  80. data/lib/datadog/di/error.rb +5 -0
  81. data/lib/datadog/di/instrumenter.rb +39 -18
  82. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  83. data/lib/datadog/di/probe_manager.rb +4 -4
  84. data/lib/datadog/di/probe_notification_builder.rb +22 -2
  85. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  86. data/lib/datadog/di/redactor.rb +0 -1
  87. data/lib/datadog/di/remote.rb +30 -9
  88. data/lib/datadog/di/transport.rb +2 -4
  89. data/lib/datadog/di.rb +5 -108
  90. data/lib/datadog/kit/appsec/events.rb +3 -3
  91. data/lib/datadog/kit/identity.rb +4 -4
  92. data/lib/datadog/profiling/component.rb +55 -53
  93. data/lib/datadog/profiling/http_transport.rb +1 -26
  94. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  95. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  96. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  97. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  98. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  99. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  100. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  101. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  102. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  103. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  104. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  105. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  106. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  107. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  108. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  109. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  110. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  111. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  112. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  113. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  114. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  115. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  116. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  118. data/lib/datadog/tracing/span.rb +12 -4
  119. data/lib/datadog/tracing/span_event.rb +123 -3
  120. data/lib/datadog/tracing/span_operation.rb +6 -0
  121. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  122. data/lib/datadog/version.rb +1 -1
  123. metadata +40 -17
  124. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  127. data/lib/datadog/appsec/scope.rb +0 -58
  128. data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
@@ -19,100 +19,38 @@ module Datadog
19
19
  [status, headers, body]
20
20
  end
21
21
 
22
- def to_sinatra_response
23
- ::Sinatra::Response.new(body, status, headers)
24
- end
25
-
26
- def to_action_dispatch_response
27
- ::ActionDispatch::Response.new(status, headers, body)
28
- end
29
-
30
22
  class << self
31
- def negotiate(env, actions)
32
- # @type var configured_response: Response?
33
- configured_response = nil
34
- actions.each do |type, parameters|
35
- # Need to use next to make steep happy :(
36
- # I rather use break to stop the execution
37
- next if configured_response
38
-
39
- configured_response = case type
40
- when 'block_request'
41
- block_response(env, parameters)
42
- when 'redirect_request'
43
- redirect_response(env, parameters)
44
- end
45
- end
46
-
47
- configured_response || default_response(env)
48
- end
49
-
50
- def graphql_response(gateway_multiplex)
51
- multiplex_return = []
52
- gateway_multiplex.queries.each do |query|
53
- # This method is only called in places where GraphQL-Ruby is already required
54
- query_result = ::GraphQL::Query::Result.new(
55
- query: query,
56
- values: JSON.parse(content('application/json'))
57
- )
58
- multiplex_return << query_result
59
- end
23
+ def from_interrupt_params(interrupt_params, http_accept_header)
24
+ return redirect_response(interrupt_params) if interrupt_params['location']
60
25
 
61
- multiplex_return
26
+ block_response(interrupt_params, http_accept_header)
62
27
  end
63
28
 
64
29
  private
65
30
 
66
- def default_response(env)
67
- content_type = content_type(env)
68
-
69
- body = []
70
- body << content(content_type)
31
+ def block_response(interrupt_params, http_accept_header)
32
+ content_type = case interrupt_params['type']
33
+ when nil, 'auto' then content_type(http_accept_header)
34
+ else FORMAT_TO_CONTENT_TYPE.fetch(interrupt_params['type'], DEFAULT_CONTENT_TYPE)
35
+ end
71
36
 
72
37
  Response.new(
73
- status: 403,
38
+ status: interrupt_params['status_code']&.to_i || 403,
74
39
  headers: { 'Content-Type' => content_type },
75
- body: body,
40
+ body: [content(content_type)],
76
41
  )
77
42
  end
78
43
 
79
- def block_response(env, options)
80
- content_type = if options['type'] == 'auto'
81
- content_type(env)
82
- else
83
- FORMAT_TO_CONTENT_TYPE[options['type']]
84
- end
85
-
86
- body = []
87
- body << content(content_type)
44
+ def redirect_response(interrupt_params)
45
+ status_code = interrupt_params['status_code'].to_i
88
46
 
89
47
  Response.new(
90
- status: options['status_code']&.to_i || 403,
91
- headers: { 'Content-Type' => content_type },
92
- body: body,
48
+ status: (status_code >= 300 && status_code < 400 ? status_code : 303),
49
+ headers: { 'Location' => interrupt_params.fetch('location') },
50
+ body: [],
93
51
  )
94
52
  end
95
53
 
96
- def redirect_response(env, options)
97
- if options['location'] && !options['location'].empty?
98
- content_type = content_type(env)
99
-
100
- headers = {
101
- 'Content-Type' => content_type,
102
- 'Location' => options['location']
103
- }
104
-
105
- status_code = options['status_code'].to_i
106
- Response.new(
107
- status: (status_code >= 300 && status_code < 400 ? status_code : 303),
108
- headers: headers,
109
- body: [],
110
- )
111
- else
112
- default_response(env)
113
- end
114
- end
115
-
116
54
  CONTENT_TYPE_TO_FORMAT = {
117
55
  'application/json' => :json,
118
56
  'text/html' => :html,
@@ -126,10 +64,10 @@ module Datadog
126
64
 
127
65
  DEFAULT_CONTENT_TYPE = 'application/json'
128
66
 
129
- def content_type(env)
130
- return DEFAULT_CONTENT_TYPE unless env.key?('HTTP_ACCEPT')
67
+ def content_type(http_accept_header)
68
+ return DEFAULT_CONTENT_TYPE if http_accept_header.nil?
131
69
 
132
- accept_types = env['HTTP_ACCEPT'].split(',').map(&:strip)
70
+ accept_types = http_accept_header.split(',').map(&:strip)
133
71
 
134
72
  accepted = accept_types.map { |m| Utils::HTTP::MediaRange.new(m) }.sort!.reverse!
135
73
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module SecurityEngine
6
+ # A namespace for value-objects representing the result of WAF check.
7
+ module Result
8
+ # A generic result without indication of its type.
9
+ class Base
10
+ attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
11
+
12
+ def initialize(events:, actions:, derivatives:, timeout:, duration_ns:, duration_ext_ns:)
13
+ @events = events
14
+ @actions = actions
15
+ @derivatives = derivatives
16
+
17
+ @timeout = timeout
18
+ @duration_ns = duration_ns
19
+ @duration_ext_ns = duration_ext_ns
20
+ end
21
+
22
+ def timeout?
23
+ !!@timeout
24
+ end
25
+
26
+ def match?
27
+ raise NotImplementedError
28
+ end
29
+ end
30
+
31
+ # A result that indicates a security rule match
32
+ class Match < Base
33
+ def match?
34
+ true
35
+ end
36
+ end
37
+
38
+ # A result that indicates a successful security rules check without a match
39
+ class Ok < Base
40
+ def match?
41
+ false
42
+ end
43
+ end
44
+
45
+ # A result that indicates an internal security library error
46
+ class Error
47
+ attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
48
+
49
+ def initialize(duration_ext_ns:)
50
+ @events = []
51
+ @actions = @derivatives = {}
52
+ @duration_ns = 0
53
+ @duration_ext_ns = duration_ext_ns
54
+ end
55
+
56
+ def timeout?
57
+ false
58
+ end
59
+
60
+ def match?
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'result'
4
+
5
+ module Datadog
6
+ module AppSec
7
+ module SecurityEngine
8
+ # A class that check input via security engine (WAF) and respond with result.
9
+ class Runner
10
+ SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze
11
+
12
+ def initialize(handle, telemetry:)
13
+ @mutex = Mutex.new
14
+ @context = WAF::Context.new(handle)
15
+ @telemetry = telemetry
16
+
17
+ @debug_tag = "libddwaf:#{WAF::VERSION::STRING} method:ddwaf_run"
18
+ end
19
+
20
+ def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
21
+ @mutex.lock
22
+
23
+ start_ns = Core::Utils::Time.get_time(:nanosecond)
24
+ persistent_data.reject! do |_, v|
25
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
26
+
27
+ v.nil? ? true : v.empty?
28
+ end
29
+
30
+ ephemeral_data.reject! do |_, v|
31
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
32
+
33
+ v.nil? ? true : v.empty?
34
+ end
35
+
36
+ _code, result = try_run(persistent_data, ephemeral_data, timeout)
37
+ stop_ns = Core::Utils::Time.get_time(:nanosecond)
38
+
39
+ report_execution(result)
40
+
41
+ unless SUCCESSFUL_EXECUTION_CODES.include?(result.status)
42
+ return Result::Error.new(duration_ext_ns: stop_ns - start_ns)
43
+ end
44
+
45
+ klass = result.status == :match ? Result::Match : Result::Ok
46
+ klass.new(
47
+ events: result.events,
48
+ actions: result.actions,
49
+ derivatives: result.derivatives,
50
+ timeout: result.timeout,
51
+ duration_ns: result.total_runtime,
52
+ duration_ext_ns: (stop_ns - start_ns)
53
+ )
54
+ ensure
55
+ @mutex.unlock
56
+ end
57
+
58
+ def finalize
59
+ @context.finalize
60
+ end
61
+
62
+ private
63
+
64
+ def try_run(persistent_data, ephemeral_data, timeout)
65
+ @context.run(persistent_data, ephemeral_data, timeout)
66
+ rescue WAF::LibDDWAF::Error => e
67
+ Datadog.logger.debug { "#{@debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
68
+ @telemetry.report(e, description: 'libddwaf-rb internal low-level error')
69
+
70
+ [:err_internal, WAF::Result.new(:err_internal, [], 0, false, [], [])]
71
+ end
72
+
73
+ def report_execution(result)
74
+ Datadog.logger.debug { "#{@debug_tag} execution timed out: #{result.inspect}" } if result.timeout
75
+
76
+ if SUCCESSFUL_EXECUTION_CODES.include?(result.status)
77
+ Datadog.logger.debug { "#{@debug_tag} execution result: #{result.inspect}" }
78
+ else
79
+ message = "#{@debug_tag} execution error: #{result.status.inspect}"
80
+
81
+ Datadog.logger.debug { message }
82
+ @telemetry.error(message)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ # A namespace for secutiry library we use to detect and prevent threats.
6
+ module SecurityEngine
7
+ end
8
+ end
9
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'appsec/configuration'
4
4
  require_relative 'appsec/extensions'
5
- require_relative 'appsec/scope'
5
+ require_relative 'appsec/context'
6
6
  require_relative 'appsec/ext'
7
7
  require_relative 'appsec/utils'
8
8
 
@@ -14,19 +14,24 @@ module Datadog
14
14
  Datadog.configuration.appsec.enabled
15
15
  end
16
16
 
17
- def active_scope
18
- Datadog::AppSec::Scope.active_scope
17
+ def rasp_enabled?
18
+ Datadog.configuration.appsec.rasp_enabled
19
19
  end
20
20
 
21
- def processor
22
- appsec_component = components.appsec
21
+ def active_context
22
+ Datadog::AppSec::Context.active
23
+ end
24
+
25
+ def telemetry
26
+ components.appsec&.telemetry
27
+ end
23
28
 
24
- appsec_component.processor if appsec_component
29
+ def processor
30
+ components.appsec&.processor
25
31
  end
26
32
 
27
33
  def reconfigure(ruleset:, telemetry:)
28
34
  appsec_component = components.appsec
29
-
30
35
  return unless appsec_component
31
36
 
32
37
  appsec_component.reconfigure(ruleset: ruleset, telemetry: telemetry)
@@ -34,12 +39,16 @@ module Datadog
34
39
 
35
40
  def reconfigure_lock(&block)
36
41
  appsec_component = components.appsec
37
-
38
42
  return unless appsec_component
39
43
 
40
44
  appsec_component.reconfigure_lock(&block)
41
45
  end
42
46
 
47
+ def api_security_enabled?
48
+ Datadog.configuration.appsec.api_security.enabled &&
49
+ Datadog.configuration.appsec.api_security.sample_rate.sample?
50
+ end
51
+
43
52
  private
44
53
 
45
54
  def components
@@ -6,6 +6,9 @@
6
6
  require_relative '../datadog'
7
7
  require_relative 'tracing/contrib/auto_instrument'
8
8
 
9
+ # DI is not loaded on Ruby 2.5 and JRuby
10
+ Datadog::DI::Contrib.load_now_or_later if defined?(Datadog::DI::Contrib)
11
+
9
12
  Datadog::Profiling.start_if_enabled
10
13
 
11
14
  module Datadog
@@ -19,21 +19,49 @@ module Datadog
19
19
  # Whenever there is a conflict (different configurations are provided in different orders), it MUST warn the users
20
20
  # about it and pick a value based on the following priority: code > environment variable > defaults.
21
21
  class AgentSettingsResolver
22
- AgentSettings = Struct.new(
23
- :adapter,
24
- :ssl,
25
- :hostname,
26
- :port,
27
- :uds_path,
28
- :timeout_seconds,
29
- keyword_init: true
30
- ) do
31
- def initialize(*)
32
- super
22
+ # Immutable container for the resulting settings
23
+ class AgentSettings
24
+ attr_reader :adapter, :ssl, :hostname, :port, :uds_path, :timeout_seconds
25
+
26
+ def initialize(adapter: nil, ssl: nil, hostname: nil, port: nil, uds_path: nil, timeout_seconds: nil)
27
+ @adapter = adapter
28
+ @ssl = ssl
29
+ @hostname = hostname
30
+ @port = port
31
+ @uds_path = uds_path
32
+ @timeout_seconds = timeout_seconds
33
33
  freeze
34
34
  end
35
+
36
+ def url
37
+ case adapter
38
+ when Datadog::Core::Configuration::Ext::Agent::HTTP::ADAPTER
39
+ hostname = self.hostname
40
+ hostname = "[#{hostname}]" if hostname =~ IPV6_REGEXP
41
+ "#{ssl ? 'https' : 'http'}://#{hostname}:#{port}/"
42
+ when Datadog::Core::Configuration::Ext::Agent::UnixSocket::ADAPTER
43
+ "unix://#{uds_path}"
44
+ else
45
+ raise ArgumentError, "Unexpected adapter: #{adapter}"
46
+ end
47
+ end
48
+
49
+ def ==(other)
50
+ self.class == other.class &&
51
+ adapter == other.adapter &&
52
+ ssl == other.ssl &&
53
+ hostname == other.hostname &&
54
+ port == other.port &&
55
+ uds_path == other.uds_path &&
56
+ timeout_seconds == other.timeout_seconds
57
+ end
35
58
  end
36
59
 
60
+ # IPv6 regular expression from
61
+ # https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
62
+ # Does not match IPv4 addresses.
63
+ IPV6_REGEXP = /\A(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\z)/.freeze # rubocop:disable Layout/LineLength
64
+
37
65
  def self.call(settings, logger: Datadog.logger)
38
66
  new(settings, logger: logger).send(:call)
39
67
  end
@@ -105,14 +105,16 @@ module Datadog
105
105
  @profiler, profiler_logger_extra = Datadog::Profiling::Component.build_profiler_component(
106
106
  settings: settings,
107
107
  agent_settings: agent_settings,
108
- optional_tracer: @tracer
108
+ optional_tracer: @tracer,
109
+ logger: @logger,
109
110
  )
110
111
  @environment_logger_extra.merge!(profiler_logger_extra) if profiler_logger_extra
111
112
 
112
113
  @runtime_metrics = self.class.build_runtime_metrics_worker(settings)
113
114
  @health_metrics = self.class.build_health_metrics(settings)
114
115
  @appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
115
- @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, telemetry: telemetry)
116
+ @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
117
+ @environment_logger_extra[:dynamic_instrumentation_enabled] = !!@dynamic_instrumentation
116
118
 
117
119
  self.class.configure_tracing(settings)
118
120
  end
@@ -236,7 +236,7 @@ module Datadog
236
236
  rescue ThreadError => e
237
237
  logger_without_components.error(
238
238
  'Detected deadlock during datadog initialization. ' \
239
- 'Please report this at https://github.com/DataDog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug' \
239
+ 'Please report this at https://github.com/datadog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug' \
240
240
  "\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
241
241
  )
242
242
  nil
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../analytics'
4
-
5
3
  module Datadog
6
- module Tracing
4
+ module Core
7
5
  module Contrib
8
6
  module Rails
9
7
  # common utilities for Rails
@@ -3,7 +3,6 @@
3
3
  require 'libdatadog'
4
4
 
5
5
  require_relative 'tag_builder'
6
- require_relative 'agent_base_url'
7
6
  require_relative '../utils/only_once'
8
7
  require_relative '../utils/at_fork_monkey_patch'
9
8
 
@@ -31,8 +30,7 @@ module Datadog
31
30
 
32
31
  def self.build(settings, agent_settings, logger:)
33
32
  tags = TagBuilder.call(settings)
34
- agent_base_url = AgentBaseUrl.resolve(agent_settings)
35
- logger.warn('Missing agent base URL; cannot enable crash tracking') unless agent_base_url
33
+ agent_base_url = agent_settings.url
36
34
 
37
35
  ld_library_path = ::Libdatadog.ld_library_path
38
36
  logger.warn('Missing ld_library_path; cannot enable crash tracking') unless ld_library_path
@@ -29,6 +29,22 @@ module Datadog
29
29
  def payload
30
30
  {}
31
31
  end
32
+
33
+ # Override equality to allow for deduplication
34
+ # The basic implementation is to check if the other object is an instance of the same class.
35
+ # This works for events that have no attributes.
36
+ # For events with attributes, you should override this method to compare the attributes.
37
+ def ==(other)
38
+ other.is_a?(self.class)
39
+ end
40
+
41
+ # @see #==
42
+ alias eql? ==
43
+
44
+ # @see #==
45
+ def hash
46
+ self.class.hash
47
+ end
32
48
  end
33
49
 
34
50
  # Telemetry class for the 'app-started' event
@@ -263,6 +279,8 @@ module Datadog
263
279
 
264
280
  # Telemetry class for the 'app-client-configuration-change' event
265
281
  class AppClientConfigurationChange < Base
282
+ attr_reader :changes, :origin
283
+
266
284
  def type
267
285
  'app-client-configuration-change'
268
286
  end
@@ -301,6 +319,16 @@ module Datadog
301
319
 
302
320
  res
303
321
  end
322
+
323
+ def ==(other)
324
+ other.is_a?(AppClientConfigurationChange) && other.changes == @changes && other.origin == @origin
325
+ end
326
+
327
+ alias eql? ==
328
+
329
+ def hash
330
+ [self.class, @changes, @origin].hash
331
+ end
304
332
  end
305
333
 
306
334
  # Telemetry class for the 'app-heartbeat' event
@@ -319,6 +347,8 @@ module Datadog
319
347
 
320
348
  # Telemetry class for the 'generate-metrics' event
321
349
  class GenerateMetrics < Base
350
+ attr_reader :namespace, :metric_series
351
+
322
352
  def type
323
353
  'generate-metrics'
324
354
  end
@@ -335,24 +365,54 @@ module Datadog
335
365
  series: @metric_series.map(&:to_h)
336
366
  }
337
367
  end
368
+
369
+ def ==(other)
370
+ other.is_a?(GenerateMetrics) && other.namespace == @namespace && other.metric_series == @metric_series
371
+ end
372
+
373
+ alias eql? ==
374
+
375
+ def hash
376
+ [self.class, @namespace, @metric_series].hash
377
+ end
338
378
  end
339
379
 
340
- # Telemetry class for the 'logs' event
380
+ # Telemetry class for the 'logs' event.
381
+ # Logs with the same content are deduplicated at flush time.
341
382
  class Log < Base
342
383
  LEVELS = {
343
384
  error: 'ERROR',
344
385
  warn: 'WARN',
345
386
  }.freeze
346
387
 
388
+ LEVELS_STRING = LEVELS.values.freeze
389
+
390
+ attr_reader :message, :level, :stack_trace, :count
391
+
347
392
  def type
348
393
  'logs'
349
394
  end
350
395
 
351
- def initialize(message:, level:, stack_trace: nil)
396
+ # @param message [String] the log message
397
+ # @param level [Symbol, String] the log level. Either :error, :warn, 'ERROR', or 'WARN'.
398
+ # @param stack_trace [String, nil] the stack trace
399
+ # @param count [Integer] the number of times the log was emitted. Used for deduplication.
400
+ def initialize(message:, level:, stack_trace: nil, count: 1)
352
401
  super()
353
402
  @message = message
354
403
  @stack_trace = stack_trace
355
- @level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
404
+
405
+ if level.is_a?(String) && LEVELS_STRING.include?(level)
406
+ # String level is used during object copy for deduplication
407
+ @level = level
408
+ elsif level.is_a?(Symbol)
409
+ # Symbol level is used by the regular log emitter user
410
+ @level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
411
+ else
412
+ raise ArgumentError, "Invalid log level #{level}"
413
+ end
414
+
415
+ @count = count
356
416
  end
357
417
 
358
418
  def payload
@@ -362,10 +422,24 @@ module Datadog
362
422
  message: @message,
363
423
  level: @level,
364
424
  stack_trace: @stack_trace,
425
+ count: @count,
365
426
  }.compact
366
427
  ]
367
428
  }
368
429
  end
430
+
431
+ # override equality to allow for deduplication
432
+ def ==(other)
433
+ other.is_a?(Log) &&
434
+ other.message == @message &&
435
+ other.level == @level && other.stack_trace == @stack_trace && other.count == @count
436
+ end
437
+
438
+ alias eql? ==
439
+
440
+ def hash
441
+ [self.class, @message, @level, @stack_trace, @count].hash
442
+ end
369
443
  end
370
444
 
371
445
  # Telemetry class for the 'distributions' event
@@ -395,6 +469,16 @@ module Datadog
395
469
  }
396
470
  end
397
471
  end
472
+
473
+ def ==(other)
474
+ other.is_a?(MessageBatch) && other.events == @events
475
+ end
476
+
477
+ alias eql? ==
478
+
479
+ def hash
480
+ [self.class, @events].hash
481
+ end
398
482
  end
399
483
  end
400
484
  end
@@ -41,7 +41,7 @@ module Datadog
41
41
  else
42
42
  'REDACTED'
43
43
  end
44
- end.join(',')
44
+ end.join("\n")
45
45
  end
46
46
  end
47
47
 
@@ -49,7 +49,7 @@ module Datadog
49
49
  # Annoymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
50
50
  message = +''
51
51
  message << (exception.class.name || exception.class.inspect)
52
- message << ':' << description if description
52
+ message << ': ' << description if description
53
53
 
54
54
  event = Event::Log.new(
55
55
  message: message,