datadog 2.3.0 → 2.5.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +10 -22
  5. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
  6. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
  7. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
  12. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
  13. data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  15. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  16. data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
  17. data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
  18. data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
  19. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
  20. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
  22. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  23. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
  24. data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
  25. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  26. data/ext/libdatadog_api/crashtracker.c +20 -18
  27. data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
  28. data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  31. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  32. data/lib/datadog/appsec/component.rb +29 -8
  33. data/lib/datadog/appsec/configuration/settings.rb +10 -2
  34. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  35. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  36. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  37. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
  38. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
  40. data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
  41. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
  42. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
  44. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
  45. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
  46. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
  47. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
  48. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
  49. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
  51. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
  52. data/lib/datadog/appsec/event.rb +25 -1
  53. data/lib/datadog/appsec/ext.rb +4 -0
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
  55. data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
  56. data/lib/datadog/appsec/processor/context.rb +109 -0
  57. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  58. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  59. data/lib/datadog/appsec/processor.rb +42 -107
  60. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  61. data/lib/datadog/appsec/remote.rb +7 -3
  62. data/lib/datadog/appsec/scope.rb +1 -4
  63. data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
  64. data/lib/datadog/appsec/utils.rb +2 -0
  65. data/lib/datadog/appsec.rb +3 -2
  66. data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
  67. data/lib/datadog/core/configuration/components.rb +4 -3
  68. data/lib/datadog/core/configuration/settings.rb +96 -5
  69. data/lib/datadog/core/configuration.rb +1 -3
  70. data/lib/datadog/core/crashtracking/component.rb +9 -6
  71. data/lib/datadog/core/environment/execution.rb +5 -5
  72. data/lib/datadog/core/environment/yjit.rb +5 -0
  73. data/lib/datadog/core/metrics/client.rb +7 -0
  74. data/lib/datadog/core/rate_limiter.rb +183 -0
  75. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  76. data/lib/datadog/core/remote/component.rb +4 -2
  77. data/lib/datadog/core/remote/negotiation.rb +4 -4
  78. data/lib/datadog/core/remote/tie.rb +2 -0
  79. data/lib/datadog/core/remote/transport/http.rb +5 -0
  80. data/lib/datadog/core/remote/worker.rb +1 -1
  81. data/lib/datadog/core/runtime/ext.rb +1 -0
  82. data/lib/datadog/core/runtime/metrics.rb +5 -1
  83. data/lib/datadog/core/semaphore.rb +35 -0
  84. data/lib/datadog/core/telemetry/component.rb +2 -0
  85. data/lib/datadog/core/telemetry/event.rb +12 -7
  86. data/lib/datadog/core/telemetry/logger.rb +51 -0
  87. data/lib/datadog/core/telemetry/logging.rb +50 -14
  88. data/lib/datadog/core/telemetry/request.rb +13 -1
  89. data/lib/datadog/core/transport/ext.rb +1 -0
  90. data/lib/datadog/core/utils/time.rb +12 -0
  91. data/lib/datadog/core/workers/async.rb +1 -1
  92. data/lib/datadog/di/code_tracker.rb +166 -0
  93. data/lib/datadog/di/configuration/settings.rb +163 -0
  94. data/lib/datadog/di/configuration.rb +11 -0
  95. data/lib/datadog/di/error.rb +31 -0
  96. data/lib/datadog/di/extensions.rb +16 -0
  97. data/lib/datadog/di/instrumenter.rb +301 -0
  98. data/lib/datadog/di/probe.rb +162 -0
  99. data/lib/datadog/di/probe_builder.rb +47 -0
  100. data/lib/datadog/di/probe_notification_builder.rb +207 -0
  101. data/lib/datadog/di/probe_notifier_worker.rb +244 -0
  102. data/lib/datadog/di/redactor.rb +188 -0
  103. data/lib/datadog/di/serializer.rb +215 -0
  104. data/lib/datadog/di/transport.rb +67 -0
  105. data/lib/datadog/di/utils.rb +39 -0
  106. data/lib/datadog/di.rb +57 -0
  107. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
  109. data/lib/datadog/profiling/collectors/info.rb +12 -3
  110. data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
  111. data/lib/datadog/profiling/component.rb +21 -4
  112. data/lib/datadog/profiling/http_transport.rb +6 -1
  113. data/lib/datadog/profiling/scheduler.rb +2 -0
  114. data/lib/datadog/profiling/stack_recorder.rb +40 -9
  115. data/lib/datadog/single_step_instrument.rb +12 -0
  116. data/lib/datadog/tracing/component.rb +13 -0
  117. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  118. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  119. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  120. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  121. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  122. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  123. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  124. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
  125. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  126. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  127. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  128. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
  129. data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
  130. data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
  131. data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
  132. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  133. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  134. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  135. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
  136. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
  137. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
  138. data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
  139. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
  140. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  141. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
  142. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  143. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
  144. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  145. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  146. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  147. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  148. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  149. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  150. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  151. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
  152. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  153. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  154. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  155. data/lib/datadog/tracing/metadata/ext.rb +2 -0
  156. data/lib/datadog/tracing/remote.rb +5 -2
  157. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  158. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
  161. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  162. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  163. data/lib/datadog/tracing/trace_operation.rb +26 -2
  164. data/lib/datadog/tracing/tracer.rb +29 -22
  165. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  166. data/lib/datadog/tracing/transport/http.rb +4 -0
  167. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  168. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  169. data/lib/datadog/tracing/workers.rb +2 -2
  170. data/lib/datadog/tracing/writer.rb +26 -28
  171. data/lib/datadog/version.rb +1 -1
  172. metadata +40 -15
  173. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utils/time'
4
+
5
+ module Datadog
6
+ module Core
7
+ # Checks for rate limiting on a resource.
8
+ class RateLimiter
9
+ # Checks if resource of specified size can be
10
+ # conforms with the current limit.
11
+ #
12
+ # Implementations of this method are not guaranteed
13
+ # to be side-effect free.
14
+ #
15
+ # @return [Boolean] whether a resource conforms with the current limit
16
+ def allow?(size = 1); end
17
+
18
+ # The effective rate limiting ratio based on
19
+ # recent calls to `allow?`.
20
+ #
21
+ # @return [Float] recent allowance ratio
22
+ def effective_rate; end
23
+ end
24
+
25
+ # Implementation of the Token Bucket metering algorithm
26
+ # for rate limiting.
27
+ #
28
+ # @see https://en.wikipedia.org/wiki/Token_bucket Token bucket
29
+ class TokenBucket < RateLimiter
30
+ attr_reader :rate, :max_tokens
31
+
32
+ # @param rate [Numeric] Allowance rate, in units per second
33
+ # if rate is negative, always allow
34
+ # if rate is zero, never allow
35
+ # @param max_tokens [Numeric] Limit of available tokens
36
+ def initialize(rate, max_tokens = rate)
37
+ super()
38
+
39
+ raise ArgumentError, "rate must be a number: #{rate}" unless rate.is_a?(Numeric)
40
+ raise ArgumentError, "max_tokens must be a number: #{max_tokens}" unless max_tokens.is_a?(Numeric)
41
+
42
+ @rate = rate
43
+ @max_tokens = max_tokens
44
+
45
+ @tokens = max_tokens
46
+ @total_messages = 0
47
+ @conforming_messages = 0
48
+ @prev_conforming_messages = nil
49
+ @prev_total_messages = nil
50
+ @current_window = nil
51
+
52
+ @last_refill = Core::Utils::Time.get_time
53
+ end
54
+
55
+ # Checks if a message of provided +size+
56
+ # conforms with the current bucket limit.
57
+ #
58
+ # If it does, return +true+ and remove +size+
59
+ # tokens from the bucket.
60
+ # If it does not, return +false+ without affecting
61
+ # the tokens from the bucket.
62
+ #
63
+ # @return [Boolean] +true+ if message conforms with current bucket limit
64
+ def allow?(size = 1)
65
+ allowed = should_allow?(size)
66
+ update_rate_counts(allowed)
67
+ allowed
68
+ end
69
+
70
+ # Ratio of 'conformance' per 'total messages' checked
71
+ # averaged for the past 2 buckets
72
+ #
73
+ # Returns +1.0+ when no messages have been checked yet.
74
+ #
75
+ # @return [Float] Conformance ratio, between +[0,1]+
76
+ def effective_rate
77
+ return 0.0 if @rate.zero?
78
+ return 1.0 if @rate < 0 || @total_messages.zero?
79
+
80
+ return current_window_rate if @prev_conforming_messages.nil? || @prev_total_messages.nil?
81
+
82
+ (@conforming_messages.to_f + @prev_conforming_messages.to_f) / (@total_messages + @prev_total_messages)
83
+ end
84
+
85
+ # Ratio of 'conformance' per 'total messages' checked
86
+ # on this bucket
87
+ #
88
+ # Returns +1.0+ when no messages have been checked yet.
89
+ #
90
+ # @return [Float] Conformance ratio, between +[0,1]+
91
+ def current_window_rate
92
+ return 1.0 if @total_messages.zero?
93
+
94
+ @conforming_messages.to_f / @total_messages
95
+ end
96
+
97
+ # @return [Numeric] number of tokens currently available
98
+ def available_tokens
99
+ @tokens
100
+ end
101
+
102
+ private
103
+
104
+ def refill_since_last_message
105
+ now = Core::Utils::Time.get_time
106
+ elapsed = now - @last_refill
107
+
108
+ # Update the number of available tokens, but ensure we do not exceed the max
109
+ # we return the min of tokens + rate*elapsed, or max tokens
110
+ refill_tokens(@rate * elapsed)
111
+
112
+ @last_refill = now
113
+ end
114
+
115
+ def refill_tokens(size)
116
+ @tokens += size
117
+ @tokens = @max_tokens if @tokens > @max_tokens
118
+ end
119
+
120
+ def increment_total_count
121
+ @total_messages += 1
122
+ end
123
+
124
+ def increment_conforming_count
125
+ @conforming_messages += 1
126
+ end
127
+
128
+ def should_allow?(size = 1)
129
+ # rate limit of 0 blocks everything
130
+ return false if @rate.zero?
131
+
132
+ # negative rate limit disables rate limiting
133
+ return true if @rate < 0
134
+
135
+ refill_since_last_message
136
+
137
+ # if tokens < 1 we don't allow?
138
+ return false if @tokens < size
139
+
140
+ @tokens -= size
141
+
142
+ true
143
+ end
144
+
145
+ # Sets and Updates the past two 1 second windows for which
146
+ # the rate limiter must compute it's rate over and updates
147
+ # the total count, and conforming message count if +allowed+
148
+ def update_rate_counts(allowed)
149
+ now = Core::Utils::Time.get_time
150
+
151
+ # No tokens have been seen yet, start a new window
152
+ if @current_window.nil?
153
+ @current_window = now
154
+ # If more than 1 second has past since last window, reset
155
+ elsif now - @current_window >= 1
156
+ @prev_conforming_messages = @conforming_messages
157
+ @prev_total_messages = @total_messages
158
+ @conforming_messages = 0
159
+ @total_messages = 0
160
+ @current_window = now
161
+ end
162
+
163
+ increment_conforming_count if allowed
164
+
165
+ increment_total_count
166
+ end
167
+ end
168
+
169
+ # {Datadog::Core::RateLimiter} that accepts all resources,
170
+ # with no limits.
171
+ class UnlimitedLimiter < RateLimiter
172
+ # @return [Boolean] always +true+
173
+ def allow?(_ = 1)
174
+ true
175
+ end
176
+
177
+ # @return [Float] always 100%
178
+ def effective_rate
179
+ 1.0
180
+ end
181
+ end
182
+ end
183
+ end
@@ -12,10 +12,11 @@ module Datadog
12
12
  class Capabilities
13
13
  attr_reader :products, :capabilities, :receivers, :base64_capabilities
14
14
 
15
- def initialize(settings)
15
+ def initialize(settings, telemetry)
16
16
  @capabilities = []
17
17
  @products = []
18
18
  @receivers = []
19
+ @telemetry = telemetry
19
20
 
20
21
  register(settings)
21
22
 
@@ -28,12 +29,12 @@ module Datadog
28
29
  if settings.respond_to?(:appsec) && settings.appsec.enabled
29
30
  register_capabilities(Datadog::AppSec::Remote.capabilities)
30
31
  register_products(Datadog::AppSec::Remote.products)
31
- register_receivers(Datadog::AppSec::Remote.receivers)
32
+ register_receivers(Datadog::AppSec::Remote.receivers(@telemetry))
32
33
  end
33
34
 
34
35
  register_capabilities(Datadog::Tracing::Remote.capabilities)
35
36
  register_products(Datadog::Tracing::Remote.products)
36
- register_receivers(Datadog::Tracing::Remote.receivers)
37
+ register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
37
38
  end
38
39
 
39
40
  def register_capabilities(capabilities)
@@ -39,6 +39,7 @@ module Datadog
39
39
  @client.sync
40
40
  @healthy ||= true
41
41
  rescue Client::SyncError => e
42
+ # Transient errors due to network or agent. Logged the error but not via telemetry
42
43
  Datadog.logger.error do
43
44
  "remote worker client sync error: #{e.message} location: #{Array(e.backtrace).first}. skipping sync"
44
45
  end
@@ -48,6 +49,7 @@ module Datadog
48
49
  # negotiation object stores error logging state that should be reset.
49
50
  negotiation = Negotiation.new(settings, agent_settings)
50
51
 
52
+ # Transient errors due to network or agent. Logged the error but not via telemetry
51
53
  Datadog.logger.error do
52
54
  "remote worker error: #{e.class.name} #{e.message} location: #{Array(e.backtrace).first}. "\
53
55
  'reseting client state'
@@ -150,10 +152,10 @@ module Datadog
150
152
  #
151
153
  # Those checks are instead performed inside the worker loop.
152
154
  # This allows users to upgrade their agent while keeping their application running.
153
- def build(settings, agent_settings)
155
+ def build(settings, agent_settings, telemetry:)
154
156
  return unless settings.remote.enabled
155
157
 
156
- new(settings, Client::Capabilities.new(settings), agent_settings)
158
+ new(settings, Client::Capabilities.new(settings, telemetry), agent_settings)
157
159
  end
158
160
  end
159
161
  end
@@ -20,7 +20,7 @@ module Datadog
20
20
 
21
21
  if res.internal_error? && network_error?(res.error)
22
22
  unless @logged[:agent_unreachable]
23
- Datadog.logger.error { "agent unreachable: cannot negotiate #{path}" }
23
+ Datadog.logger.warn { "agent unreachable: cannot negotiate #{path}" }
24
24
  @logged[:agent_unreachable] = true
25
25
  end
26
26
 
@@ -29,7 +29,7 @@ module Datadog
29
29
 
30
30
  if res.not_found?
31
31
  unless @logged[:no_info_endpoint]
32
- Datadog.logger.error { "agent reachable but has no /info endpoint: cannot negotiate #{path}" }
32
+ Datadog.logger.warn { "agent reachable but has no /info endpoint: cannot negotiate #{path}" }
33
33
  @logged[:no_info_endpoint] = true
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ module Datadog
38
38
 
39
39
  unless res.ok?
40
40
  unless @logged[:unexpected_response]
41
- Datadog.logger.error { "agent reachable but unexpected response: cannot negotiate #{path}" }
41
+ Datadog.logger.warn { "agent reachable but unexpected response: cannot negotiate #{path}" }
42
42
  @logged[:unexpected_response] = true
43
43
  end
44
44
 
@@ -47,7 +47,7 @@ module Datadog
47
47
 
48
48
  unless res.endpoints.include?(path)
49
49
  unless @logged[:no_config_endpoint]
50
- Datadog.logger.error { "agent reachable but does not report #{path}" }
50
+ Datadog.logger.warn { "agent reachable but does not report #{path}" }
51
51
  @logged[:no_config_endpoint] = true
52
52
  end
53
53
 
@@ -19,6 +19,8 @@ module Datadog
19
19
  barrier = Datadog::Core::Remote.active_remote.barrier(:once)
20
20
  end
21
21
 
22
+ # steep does not permit the next line due to
23
+ # https://github.com/soutaro/steep/issues/1231
22
24
  Boot.new(barrier, t)
23
25
  end
24
26
  end
@@ -120,6 +120,11 @@ module Datadog
120
120
  # Add container ID, if present.
121
121
  container_id = Datadog::Core::Environment::Container.container_id
122
122
  headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CONTAINER_ID] = container_id unless container_id.nil?
123
+ # Sending this header to the agent will disable metrics computation (and billing) on the agent side
124
+ # by pretending it has already been done on the library side.
125
+ if Datadog.configuration.appsec.standalone.enabled
126
+ headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_STATS] = 'yes'
127
+ end
123
128
  end
124
129
  end
125
130
 
@@ -34,7 +34,7 @@ module Datadog
34
34
  @starting = true
35
35
 
36
36
  thread = Thread.new { poll(@interval) }
37
- thread.name = self.class.name unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
37
+ thread.name = self.class.name
38
38
  thread.thread_variable_set(:fork_safe, true)
39
39
  @thr = thread
40
40
 
@@ -31,6 +31,7 @@ module Datadog
31
31
  METRIC_YJIT_OBJECT_SHAPE_COUNT = 'runtime.ruby.yjit.object_shape_count'
32
32
  METRIC_YJIT_OUTLINED_CODE_SIZE = 'runtime.ruby.yjit.outlined_code_size'
33
33
  METRIC_YJIT_YJIT_ALLOC_SIZE = 'runtime.ruby.yjit.yjit_alloc_size'
34
+ METRIC_YJIT_RATIO_IN_YJIT = 'runtime.ruby.yjit.ratio_in_yjit'
34
35
 
35
36
  TAG_SERVICE = 'service'
36
37
  end
@@ -94,7 +94,7 @@ module Datadog
94
94
  def try_flush
95
95
  yield
96
96
  rescue StandardError => e
97
- Datadog.logger.error("Error while sending runtime metric. Cause: #{e.class.name} #{e.message}")
97
+ Datadog.logger.warn("Error while sending runtime metric. Cause: #{e.class.name} #{e.message}")
98
98
  end
99
99
 
100
100
  def default_metric_options
@@ -181,6 +181,10 @@ module Datadog
181
181
  Core::Runtime::Ext::Metrics::METRIC_YJIT_YJIT_ALLOC_SIZE,
182
182
  Core::Environment::YJIT.yjit_alloc_size
183
183
  )
184
+ gauge_if_not_nil(
185
+ Core::Runtime::Ext::Metrics::METRIC_YJIT_RATIO_IN_YJIT,
186
+ Core::Environment::YJIT.ratio_in_yjit
187
+ )
184
188
  end
185
189
  end
186
190
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ # Semaphore pattern implementation, as described in documentation for
6
+ # ConditionVariable.
7
+ #
8
+ # @api private
9
+ class Semaphore
10
+ def initialize
11
+ @wake_lock = Mutex.new
12
+ @wake = ConditionVariable.new
13
+ end
14
+
15
+ def signal
16
+ wake_lock.synchronize do
17
+ wake.signal
18
+ end
19
+ end
20
+
21
+ def wait(timeout = nil)
22
+ wake_lock.synchronize do
23
+ # steep specifies that the second argument to wait is of type
24
+ # ::Time::_Timeout which for some reason is not Numeric and is not
25
+ # castable from Numeric.
26
+ wake.wait(wake_lock, timeout) # steep:ignore
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :wake_lock, :wake
33
+ end
34
+ end
35
+ end
@@ -5,6 +5,7 @@ require_relative 'event'
5
5
  require_relative 'http/transport'
6
6
  require_relative 'metrics_manager'
7
7
  require_relative 'worker'
8
+ require_relative 'logging'
8
9
 
9
10
  require_relative '../configuration/ext'
10
11
  require_relative '../utils/forking'
@@ -17,6 +18,7 @@ module Datadog
17
18
  attr_reader :enabled
18
19
 
19
20
  include Core::Utils::Forking
21
+ include Telemetry::Logging
20
22
 
21
23
  def self.build(settings, agent_settings, logger)
22
24
  enabled = settings.telemetry.enabled
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/forking'
4
+ require_relative '../utils/sequence'
5
+
3
6
  module Datadog
4
7
  module Core
5
8
  module Telemetry
@@ -338,7 +341,6 @@ module Datadog
338
341
  class Log < Base
339
342
  LEVELS = {
340
343
  error: 'ERROR',
341
- debug: 'DEBUG',
342
344
  warn: 'WARN',
343
345
  }.freeze
344
346
 
@@ -346,19 +348,22 @@ module Datadog
346
348
  'logs'
347
349
  end
348
350
 
349
- def initialize(message:, level:)
351
+ def initialize(message:, level:, stack_trace: nil)
350
352
  super()
351
353
  @message = message
354
+ @stack_trace = stack_trace
352
355
  @level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
353
356
  end
354
357
 
355
358
  def payload
356
359
  {
357
- logs: [{
358
- message: @message,
359
- level: @level,
360
- # More optional fields to be added here...
361
- }]
360
+ logs: [
361
+ {
362
+ message: @message,
363
+ level: @level,
364
+ stack_trace: @stack_trace,
365
+ }.compact
366
+ ]
362
367
  }
363
368
  end
364
369
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Telemetry
6
+ # === INTERNAL USAGE ONLY ===
7
+ #
8
+ # Report telemetry logs via delegating to the telemetry component instance via mutex.
9
+ #
10
+ # IMPORTANT: Invoking this method during the lifecycle of component initialization will
11
+ # be no-op instead.
12
+ #
13
+ # For developer using this module:
14
+ # read: lib/datadog/core/telemetry/logging.rb
15
+ module Logger
16
+ class << self
17
+ def report(exception, level: :error, description: nil)
18
+ instance&.report(exception, level: level, description: description)
19
+ end
20
+
21
+ def error(description)
22
+ instance&.error(description)
23
+ end
24
+
25
+ private
26
+
27
+ def instance
28
+ # Component initialization uses a mutex to avoid having concurrent initialization.
29
+ # Trying to access the telemetry component during initialization (specifically:
30
+ # from the thread that's actually doing the initialization) would cause a deadlock,
31
+ # since accessing the components would try to recursively lock the mutex.
32
+ #
33
+ # To work around this, we use allow_initialization: false to avoid triggering this issue.
34
+ #
35
+ # The downside is: this leaves us unable to report telemetry during component initialization.
36
+ components = Datadog.send(:components, allow_initialization: false)
37
+
38
+ if components && components.telemetry
39
+ components.telemetry
40
+ else
41
+ Datadog.logger.warn(
42
+ 'Failed to send telemetry before components initialization or within components lifecycle'
43
+ )
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,32 +2,68 @@
2
2
 
3
3
  require_relative 'event'
4
4
 
5
+ require 'pathname'
6
+
5
7
  module Datadog
6
8
  module Core
7
9
  module Telemetry
8
- # Logging interface for sending telemetry logs.
10
+ # === INTERNAL USAGE ONLY ===
11
+ #
12
+ # Logging interface for sending telemetry logs... so we can fix them.
13
+ #
14
+ # For developer using this module:
15
+ # - MUST NOT provide any sensitive information (PII)
16
+ # - SHOULD reduce the data cardinality for batching/aggregation
17
+ #
18
+ # Before using it, ask yourself:
19
+ # - Do we need to know about this (ie. internal error or client error)?
20
+ # - How severe/critical is this error? (ie. error, warning, fatal)
21
+ # - What information needed to make it actionable?
9
22
  #
10
- # Reporting internal error so that we can fix them.
11
- # IMPORTANT: Make sure to not log any sensitive information.
12
23
  module Logging
13
- module_function
24
+ # Extract datadog stack trace from the exception
25
+ module DatadogStackTrace
26
+ GEM_ROOT = Pathname.new("#{__dir__}/../../../..").cleanpath.to_s
27
+
28
+ def self.from(exception)
29
+ backtrace = exception.backtrace
30
+
31
+ return unless backtrace
32
+ return if backtrace.empty?
33
+
34
+ # vendored deps
35
+ vendored_deps = Gem.path.any? { |p| p.start_with?(GEM_ROOT) }
14
36
 
15
- def report(exception, level:)
37
+ backtrace.map do |line|
38
+ if !vendored_deps && line.start_with?(GEM_ROOT) ||
39
+ vendored_deps && line.start_with?(GEM_ROOT) && Gem.path.none? { |p| line.start_with?(p) }
40
+ line[GEM_ROOT.length..-1] || ''
41
+ else
42
+ 'REDACTED'
43
+ end
44
+ end.join(',')
45
+ end
46
+ end
47
+
48
+ def report(exception, level: :error, description: nil)
16
49
  # Annoymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
17
- message = exception.class.name || exception.class.inspect
50
+ message = +''
51
+ message << (exception.class.name || exception.class.inspect)
52
+ message << ':' << description if description
18
53
 
19
54
  event = Event::Log.new(
20
55
  message: message,
21
- level: level
56
+ level: level,
57
+ stack_trace: DatadogStackTrace.from(exception)
22
58
  )
23
59
 
24
- if (telemetry = Datadog.send(:components).telemetry)
25
- telemetry.log!(event)
26
- else
27
- Datadog.logger.debug do
28
- "Attempting to send telemetry log when telemetry component is not ready: #{message}"
29
- end
30
- end
60
+ log!(event)
61
+ end
62
+
63
+ def error(description)
64
+ event = Event::Log.new(message: description, level: :error)
65
+
66
+ log!(event)
31
67
  end
32
68
  end
33
69
  end
@@ -31,6 +31,18 @@ module Datadog
31
31
 
32
32
  def application
33
33
  config = Datadog.configuration
34
+
35
+ tracer_version = Core::Environment::Identity.gem_datadog_version_semver2
36
+
37
+ # We need some to distinguish datadog-ci gem versions
38
+ # when examining telemetry metrics emitted from the datadog-ci gem.
39
+ #
40
+ # This code checks that Datadog::CI is loaded and ci mode is enabled and adds
41
+ # "-ci-X.Y.Z" suffix to the tracer version.
42
+ if defined?(::Datadog::CI::VERSION) && config.respond_to?(:ci) && config.ci.enabled
43
+ tracer_version = "#{tracer_version}-ci-#{::Datadog::CI::VERSION::STRING}"
44
+ end
45
+
34
46
  {
35
47
  env: config.env,
36
48
  language_name: Core::Environment::Ext::LANG,
@@ -39,7 +51,7 @@ module Datadog
39
51
  runtime_version: Core::Environment::Ext::ENGINE_VERSION,
40
52
  service_name: config.service,
41
53
  service_version: config.version,
42
- tracer_version: Core::Environment::Identity.gem_datadog_version_semver2
54
+ tracer_version: tracer_version
43
55
  }
44
56
  end
45
57
 
@@ -16,6 +16,7 @@ module Datadog
16
16
  #
17
17
  # Setting this header to any non-empty value enables this feature.
18
18
  HEADER_CLIENT_COMPUTED_TOP_LEVEL = 'Datadog-Client-Computed-Top-Level'
19
+ HEADER_CLIENT_COMPUTED_STATS = 'Datadog-Client-Computed-Stats'
19
20
  HEADER_META_LANG = 'Datadog-Meta-Lang'
20
21
  HEADER_META_LANG_VERSION = 'Datadog-Meta-Lang-Version'
21
22
  HEADER_META_LANG_INTERPRETER = 'Datadog-Meta-Lang-Interpreter'
@@ -34,6 +34,18 @@ module Datadog
34
34
  define_singleton_method(:now, &block)
35
35
  end
36
36
 
37
+ # Overrides the implementation of `#get_time
38
+ # with the provided callable.
39
+ #
40
+ # Overriding the method `#get_time` instead of
41
+ # indirectly calling `block` removes
42
+ # one level of method call overhead.
43
+ #
44
+ # @param block [Proc] block that accepts unit and returns timestamp in the requested unit
45
+ def get_time_provider=(block)
46
+ define_singleton_method(:get_time, &block)
47
+ end
48
+
37
49
  def measure(unit = :float_second)
38
50
  before = get_time(unit)
39
51
  yield
@@ -148,7 +148,7 @@ module Datadog
148
148
  end
149
149
  # rubocop:enable Lint/RescueException
150
150
  end
151
- @worker.name = self.class.name unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
151
+ @worker.name = self.class.name
152
152
  @worker.thread_variable_set(:fork_safe, true)
153
153
 
154
154
  nil