datadog 2.21.0 → 2.23.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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -2
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +4 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  10. data/ext/libdatadog_api/ddsketch.c +106 -0
  11. data/ext/libdatadog_api/feature_flags.c +554 -0
  12. data/ext/libdatadog_api/feature_flags.h +5 -0
  13. data/ext/libdatadog_api/init.c +5 -0
  14. data/ext/libdatadog_api/library_config.c +34 -25
  15. data/ext/libdatadog_api/process_discovery.c +19 -13
  16. data/ext/libdatadog_extconf_helpers.rb +1 -1
  17. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  18. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  19. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  20. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  21. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  22. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  23. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  24. data/lib/datadog/appsec/assets/blocked.html +8 -0
  25. data/lib/datadog/appsec/assets/blocked.json +1 -1
  26. data/lib/datadog/appsec/assets/blocked.text +3 -1
  27. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  28. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  29. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  30. data/lib/datadog/appsec/assets.rb +1 -1
  31. data/lib/datadog/appsec/compressed_json.rb +1 -1
  32. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  33. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  35. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  36. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  38. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  39. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  40. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  41. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  42. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  43. data/lib/datadog/appsec/event.rb +12 -14
  44. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  45. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  46. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  47. data/lib/datadog/appsec/remote.rb +29 -13
  48. data/lib/datadog/appsec/response.rb +18 -4
  49. data/lib/datadog/appsec/security_engine/result.rb +28 -9
  50. data/lib/datadog/appsec/security_engine/runner.rb +17 -7
  51. data/lib/datadog/appsec/security_event.rb +5 -7
  52. data/lib/datadog/core/configuration/components.rb +44 -9
  53. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  54. data/lib/datadog/core/configuration/settings.rb +14 -0
  55. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  56. data/lib/datadog/core/configuration/supported_configurations.rb +330 -299
  57. data/lib/datadog/core/configuration.rb +1 -1
  58. data/lib/datadog/core/ddsketch.rb +19 -0
  59. data/lib/datadog/core/environment/ext.rb +6 -0
  60. data/lib/datadog/core/environment/process.rb +79 -0
  61. data/lib/datadog/core/environment/yjit.rb +2 -1
  62. data/lib/datadog/core/feature_flags.rb +61 -0
  63. data/lib/datadog/core/pin.rb +4 -8
  64. data/lib/datadog/core/process_discovery.rb +4 -2
  65. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  66. data/lib/datadog/core/remote/component.rb +4 -6
  67. data/lib/datadog/core/remote/transport/config.rb +2 -10
  68. data/lib/datadog/core/remote/transport/http/config.rb +9 -9
  69. data/lib/datadog/core/remote/transport/http/negotiation.rb +17 -8
  70. data/lib/datadog/core/remote/transport/http.rb +2 -0
  71. data/lib/datadog/core/remote/transport/negotiation.rb +2 -18
  72. data/lib/datadog/core/remote/worker.rb +25 -37
  73. data/lib/datadog/core/tag_builder.rb +0 -4
  74. data/lib/datadog/core/tag_normalizer.rb +84 -0
  75. data/lib/datadog/core/telemetry/component.rb +18 -3
  76. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  77. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  78. data/lib/datadog/core/telemetry/event/app_started.rb +52 -49
  79. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  80. data/lib/datadog/core/telemetry/event.rb +1 -0
  81. data/lib/datadog/core/telemetry/logger.rb +2 -2
  82. data/lib/datadog/core/telemetry/logging.rb +2 -8
  83. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -6
  84. data/lib/datadog/core/telemetry/transport/telemetry.rb +1 -2
  85. data/lib/datadog/core/transport/http/client.rb +69 -0
  86. data/lib/datadog/core/transport/response.rb +4 -1
  87. data/lib/datadog/core/utils/array.rb +29 -0
  88. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  89. data/lib/datadog/core/utils/network.rb +22 -1
  90. data/lib/datadog/core/utils/only_once_successful.rb +6 -2
  91. data/lib/datadog/core/utils.rb +2 -0
  92. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  93. data/lib/datadog/data_streams/configuration.rb +11 -0
  94. data/lib/datadog/data_streams/ext.rb +11 -0
  95. data/lib/datadog/data_streams/extensions.rb +16 -0
  96. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  97. data/lib/datadog/data_streams/processor.rb +509 -0
  98. data/lib/datadog/data_streams/transport/http/api.rb +33 -0
  99. data/lib/datadog/data_streams/transport/http/client.rb +21 -0
  100. data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
  101. data/lib/datadog/data_streams/transport/http.rb +41 -0
  102. data/lib/datadog/data_streams/transport/stats.rb +60 -0
  103. data/lib/datadog/data_streams.rb +100 -0
  104. data/lib/datadog/di/boot.rb +1 -0
  105. data/lib/datadog/di/component.rb +14 -16
  106. data/lib/datadog/di/context.rb +70 -0
  107. data/lib/datadog/di/el/compiler.rb +164 -0
  108. data/lib/datadog/di/el/evaluator.rb +159 -0
  109. data/lib/datadog/di/el/expression.rb +42 -0
  110. data/lib/datadog/di/el.rb +5 -0
  111. data/lib/datadog/di/error.rb +29 -0
  112. data/lib/datadog/di/instrumenter.rb +163 -48
  113. data/lib/datadog/di/probe.rb +55 -15
  114. data/lib/datadog/di/probe_builder.rb +39 -1
  115. data/lib/datadog/di/probe_manager.rb +13 -4
  116. data/lib/datadog/di/probe_notification_builder.rb +105 -67
  117. data/lib/datadog/di/proc_responder.rb +32 -0
  118. data/lib/datadog/di/serializer.rb +151 -7
  119. data/lib/datadog/di/transport/diagnostics.rb +2 -2
  120. data/lib/datadog/di/transport/http/diagnostics.rb +2 -4
  121. data/lib/datadog/di/transport/http/input.rb +2 -4
  122. data/lib/datadog/di/transport/http.rb +6 -2
  123. data/lib/datadog/di/transport/input.rb +64 -4
  124. data/lib/datadog/open_feature/component.rb +60 -0
  125. data/lib/datadog/open_feature/configuration.rb +27 -0
  126. data/lib/datadog/open_feature/evaluation_engine.rb +69 -0
  127. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  128. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  129. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  130. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  131. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  132. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  133. data/lib/datadog/open_feature/ext.rb +14 -0
  134. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  135. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  136. data/lib/datadog/open_feature/provider.rb +141 -0
  137. data/lib/datadog/open_feature/remote.rb +74 -0
  138. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  139. data/lib/datadog/open_feature/transport.rb +72 -0
  140. data/lib/datadog/open_feature.rb +19 -0
  141. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  142. data/lib/datadog/opentelemetry/metrics.rb +110 -0
  143. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  144. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +38 -0
  145. data/lib/datadog/opentelemetry.rb +3 -0
  146. data/lib/datadog/profiling/collectors/code_provenance.rb +15 -6
  147. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  148. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  149. data/lib/datadog/profiling/profiler.rb +4 -0
  150. data/lib/datadog/profiling/tag_builder.rb +36 -3
  151. data/lib/datadog/profiling.rb +1 -2
  152. data/lib/datadog/single_step_instrument.rb +1 -1
  153. data/lib/datadog/tracing/component.rb +6 -17
  154. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  155. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  156. data/lib/datadog/tracing/configuration/settings.rb +77 -3
  157. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  158. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  159. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  160. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  161. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  162. data/lib/datadog/tracing/contrib/component.rb +2 -2
  163. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  164. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  165. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  166. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  167. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  168. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  169. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +74 -44
  170. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  171. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  172. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  173. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  174. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  175. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  176. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  177. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  178. data/lib/datadog/tracing/contrib/karafka/patcher.rb +32 -0
  179. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  180. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  181. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  182. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  183. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  184. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  185. data/lib/datadog/tracing/contrib/status_range_matcher.rb +7 -0
  186. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  187. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  188. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  189. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  190. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  191. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +46 -0
  192. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  193. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  194. data/lib/datadog/tracing/contrib.rb +1 -0
  195. data/lib/datadog/tracing/metadata/ext.rb +9 -1
  196. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +3 -5
  199. data/lib/datadog/version.rb +2 -2
  200. data/lib/datadog.rb +2 -0
  201. metadata +92 -16
  202. data/ext/libdatadog_api/macos_development.md +0 -26
  203. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  204. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  205. data/lib/datadog/di/transport/http/client.rb +0 -47
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Lint/AssignmentInCondition
4
+
3
5
  module Datadog
4
6
  module DI
5
7
  # Builds probe status notification and snapshot payloads.
@@ -40,32 +42,17 @@ module Datadog
40
42
 
41
43
  # Duration is in seconds.
42
44
  # path is the actual path of the instrumented file.
43
- def build_executed(probe,
44
- path: nil, rv: nil, duration: nil, caller_locations: nil,
45
- serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
46
- serialized_entry_args: nil)
47
- build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
48
- # Actual path of the instrumented file.
49
- path: path,
50
- duration: duration,
51
- # TODO check how many stack frames we should be keeping/sending,
52
- # this should be all frames for enriched probes and no frames for
53
- # non-enriched probes?
54
- caller_locations: caller_locations,
55
- args: args, kwargs: kwargs,
56
- target_self: target_self,
57
- serialized_entry_args: serialized_entry_args)
45
+ def build_executed(context)
46
+ build_snapshot(context)
58
47
  end
59
48
 
60
- def build_snapshot(probe, rv: nil, serialized_locals: nil, path: nil,
61
- # In Ruby everything is a method, therefore we should always have
62
- # a target self. However, if we are not capturing a snapshot,
63
- # there is no need to pass in the target self.
64
- target_self: nil,
65
- duration: nil, caller_locations: nil,
66
- args: nil, kwargs: nil,
67
- serialized_entry_args: nil)
68
- if probe.capture_snapshot? && !target_self
49
+ NANOSECONDS = 10**9
50
+ MILLISECONDS = 1000
51
+
52
+ def build_snapshot(context)
53
+ probe = context.probe
54
+
55
+ if probe.capture_snapshot? && !context.target_self
69
56
  raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
70
57
  end
71
58
 
@@ -74,22 +61,14 @@ module Datadog
74
61
  captures = if probe.capture_snapshot?
75
62
  if probe.method?
76
63
  return_arguments = {
77
- "@return": serializer.serialize_value(rv,
64
+ "@return": serializer.serialize_value(context.return_value,
78
65
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
79
66
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
80
- self: serializer.serialize_value(target_self),
67
+ self: serializer.serialize_value(context.target_self),
81
68
  }
82
69
  {
83
70
  entry: {
84
- # standard:disable all
85
- arguments: if serialized_entry_args
86
- serialized_entry_args
87
- else
88
- (args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
89
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
90
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
91
- end,
92
- # standard:enable all
71
+ arguments: context.serialized_entry_args,
93
72
  },
94
73
  return: {
95
74
  arguments: return_arguments,
@@ -98,20 +77,51 @@ module Datadog
98
77
  }
99
78
  elsif probe.line?
100
79
  {
101
- lines: serialized_locals && {
80
+ lines: (locals = context.serialized_locals) && {
102
81
  probe.line_no => {
103
- locals: serialized_locals,
104
- arguments: {self: serializer.serialize_value(target_self)},
82
+ locals: locals,
83
+ arguments: {self: serializer.serialize_value(context.target_self)},
105
84
  },
106
85
  },
107
86
  }
108
87
  end
109
88
  end
110
89
 
90
+ message = nil
91
+ evaluation_errors = []
92
+ if segments = probe.template_segments
93
+ message, evaluation_errors = evaluate_template(segments, context)
94
+ end
95
+ build_snapshot_base(context,
96
+ evaluation_errors: evaluation_errors, message: message,
97
+ captures: captures)
98
+ end
99
+
100
+ def build_condition_evaluation_failed(context, expression, exception)
101
+ error = {
102
+ message: "#{exception.class}: #{exception}",
103
+ expr: expression.dsl_expr,
104
+ }
105
+ build_snapshot_base(context, evaluation_errors: [error])
106
+ end
107
+
108
+ private
109
+
110
+ def build_snapshot_base(context, evaluation_errors: [], captures: nil, message: nil)
111
+ probe = context.probe
112
+
113
+ timestamp = timestamp_now
114
+ duration = context.duration
115
+
111
116
  location = if probe.line?
112
117
  {
113
- file: path,
114
- lines: [probe.line_no],
118
+ file: context.path,
119
+ # Line numbers are required to be strings by the
120
+ # system tests schema.
121
+ # Backend I think accepts them also as integers, but some
122
+ # other languages send strings and we decided to require
123
+ # strings for everyone.
124
+ lines: [probe.line_no.to_s],
115
125
  }
116
126
  elsif probe.method?
117
127
  {
@@ -120,34 +130,50 @@ module Datadog
120
130
  }
121
131
  end
122
132
 
123
- stack = if caller_locations
133
+ stack = if caller_locations = context.caller_locations
124
134
  format_caller_locations(caller_locations)
125
135
  end
126
136
 
127
- timestamp = timestamp_now
128
137
  {
129
138
  service: settings.service,
130
- "debugger.snapshot": {
131
- id: SecureRandom.uuid,
132
- timestamp: timestamp,
133
- evaluationErrors: [],
134
- probe: {
135
- id: probe.id,
136
- version: 0,
137
- location: location,
139
+ debugger: {
140
+ type: 'snapshot',
141
+ # Product can have three values: di, ld, er.
142
+ # We do not currently implement exception replay.
143
+ # There is currently no specification, and no consensus, for
144
+ # when product should be di (dynamic instrumentation) and when
145
+ # it should be ld (live debugger). I thought the backend was
146
+ # supposed to provide this in probe specification via remote
147
+ # config, but apparently this is not the case and the expectation
148
+ # is that the library figures out the product via heuristics,
149
+ # except there is currently no consensus on said heuristics.
150
+ # .NET always sends ld, other languages send nothing at the moment.
151
+ # Don't send anything for the time being.
152
+ #product: 'di/ld',
153
+ snapshot: {
154
+ id: SecureRandom.uuid,
155
+ timestamp: timestamp,
156
+ evaluationErrors: evaluation_errors,
157
+ probe: {
158
+ id: probe.id,
159
+ version: 0,
160
+ location: location,
161
+ },
162
+ language: 'ruby',
163
+ # TODO add test coverage for callers being nil
164
+ stack: stack,
165
+ # System tests schema validation requires captures to
166
+ # always be present
167
+ captures: captures || {},
138
168
  },
139
- language: 'ruby',
140
- # TODO add test coverage for callers being nil
141
- stack: stack,
142
- captures: captures,
143
169
  },
144
170
  # In python tracer duration is under debugger.snapshot,
145
171
  # but UI appears to expect it here at top level.
146
- duration: duration ? (duration * 10**9).to_i : 0,
172
+ duration: duration ? (duration * NANOSECONDS).to_i : 0,
147
173
  host: nil,
148
174
  logger: {
149
175
  name: probe.file,
150
- method: probe.method_name || 'no_method',
176
+ method: probe.method_name,
151
177
  thread_name: Thread.current.name,
152
178
  # Dynamic instrumentation currently does not need thread_id for
153
179
  # anything. It can be sent if a customer requests it at which point
@@ -160,14 +186,11 @@ module Datadog
160
186
  "dd.trace_id": active_trace&.id&.to_s,
161
187
  "dd.span_id": active_span&.id&.to_s,
162
188
  ddsource: 'dd_debugger',
163
- message: probe.template && evaluate_template(probe.template,
164
- duration: duration ? duration * 1000 : 0),
189
+ message: message,
165
190
  timestamp: timestamp,
166
191
  }
167
192
  end
168
193
 
169
- private
170
-
171
194
  def build_status(probe, message:, status:)
172
195
  {
173
196
  service: settings.service,
@@ -192,16 +215,29 @@ module Datadog
192
215
  end
193
216
  end
194
217
 
195
- def evaluate_template(template, **vars)
196
- message = template.dup
197
- vars.each do |key, value|
198
- message.gsub!("{@#{key}}") { value.to_s }
199
- end
200
- message
218
+ def evaluate_template(template_segments, context)
219
+ evaluation_errors = []
220
+ message = template_segments.map do |segment|
221
+ case segment
222
+ when String
223
+ segment
224
+ when EL::Expression
225
+ serializer.serialize_value_for_message(segment.evaluate(context))
226
+ else
227
+ raise ArgumentError, "Invalid template segment type: #{segment}"
228
+ end
229
+ rescue => exc
230
+ evaluation_errors << {
231
+ message: "#{exc.class}: #{exc}",
232
+ expr: segment.dsl_expr,
233
+ }
234
+ '[evaluation error]'
235
+ end.join
236
+ [message, evaluation_errors]
201
237
  end
202
238
 
203
239
  def timestamp_now
204
- (Core::Utils::Time.now.to_f * 1000).to_i
240
+ (Core::Utils::Time.now.to_f * MILLISECONDS).to_i
205
241
  end
206
242
 
207
243
  def active_trace
@@ -218,3 +254,5 @@ module Datadog
218
254
  end
219
255
  end
220
256
  end
257
+
258
+ # rubocop:enable Lint/AssignmentInCondition
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ # An adapter to convert procs to responders.
6
+ #
7
+ # Used in test suite and benchmarks.
8
+ #
9
+ # @api private
10
+ class ProcResponder
11
+ def initialize(executed_proc, failed_proc = nil)
12
+ @executed_proc = executed_proc
13
+ @failed_proc = failed_proc
14
+ end
15
+
16
+ attr_reader :executed_proc
17
+ attr_reader :failed_proc
18
+
19
+ def probe_executed_callback(context)
20
+ executed_proc.call(context)
21
+ end
22
+
23
+ def probe_condition_evaluation_failed_callback(context, exc)
24
+ if failed_proc.nil?
25
+ raise NotImplementedError, "Failed proc not provided"
26
+ end
27
+
28
+ failed_proc.call(context, exc)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -64,6 +64,9 @@ module Datadog
64
64
  # a common base class but are all of different classes) or for Mongoid
65
65
  # models (that do not have a common base class at all but include a
66
66
  # standard Mongoid module).
67
+ #
68
+ # Important: these serializers are NOT used in log messages.
69
+ # They are only used for variables that are captured in the snapshots.
67
70
  @@flat_registry = []
68
71
  def self.register(condition: nil, &block)
69
72
  @@flat_registry << {condition: condition, proc: block}
@@ -79,6 +82,18 @@ module Datadog
79
82
  attr_reader :redactor
80
83
  attr_reader :telemetry
81
84
 
85
+ def combine_args(args, kwargs, target_self)
86
+ counter = 0
87
+ combined = args.each_with_object({}) do |value, c|
88
+ counter += 1
89
+ # Conversion to symbol is needed here to put args ahead of
90
+ # kwargs when they are merged below.
91
+ c[:"arg#{counter}"] = value
92
+ end.update(kwargs)
93
+ combined[:self] = target_self
94
+ combined
95
+ end
96
+
82
97
  # Serializes positional and keyword arguments to a method,
83
98
  # as obtained by a method probe.
84
99
  #
@@ -93,13 +108,7 @@ module Datadog
93
108
  def serialize_args(args, kwargs, target_self,
94
109
  depth: settings.dynamic_instrumentation.max_capture_depth,
95
110
  attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
96
- counter = 0
97
- combined = args.each_with_object({}) do |value, c|
98
- counter += 1
99
- # Conversion to symbol is needed here to put args ahead of
100
- # kwargs when they are merged below.
101
- c[:"arg#{counter}"] = value
102
- end.update(kwargs).update(self: target_self)
111
+ combined = combine_args(args, kwargs, target_self)
103
112
  serialize_vars(combined, depth: depth, attribute_count: attribute_count)
104
113
  end
105
114
 
@@ -150,6 +159,8 @@ module Datadog
150
159
  end
151
160
 
152
161
  serialized = {type: class_name(cls)}
162
+ # https://github.com/soutaro/steep/issues/1860
163
+ # @type var serialized: untyped
153
164
  case value
154
165
  when NilClass
155
166
  serialized.update(isNull: true)
@@ -261,8 +272,120 @@ module Datadog
261
272
  end
262
273
  end
263
274
 
275
+ # This method is used for serializing arbitrary values into log messages.
276
+ # Because the output is meant to be human-readable, we cannot use
277
+ # the "normal" serialization format which is meant to be machine-readable.
278
+ # Serialize objects with depth of 1 and include the class name.
279
+ #
280
+ # Note that this method does not (currently) utilize the custom
281
+ # serializers that the "normal" serialization logic uses.
282
+ #
283
+ # This serializer differs from the RFC in two ways:
284
+ # 1. We omit the middle of long strings rather than the end,
285
+ # and also the inner entries in arrays/hashes/objects.
286
+ # 2. We use Ruby-ish syntax for hashes and objects.
287
+ #
288
+ # We also use the Ruby-like syntax for symbols, which don't exist
289
+ # in other languages.
290
+ def serialize_value_for_message(value, depth = 1)
291
+ # This method is more verbose than "normal" Ruby code to avoid
292
+ # array allocations.
293
+ case value
294
+ when NilClass
295
+ 'nil'
296
+ when Integer, Float, TrueClass, FalseClass, Time, Date
297
+ value.to_s
298
+ when String
299
+ serialize_string_or_symbol_for_message(value)
300
+ when Symbol
301
+ ':' + serialize_string_or_symbol_for_message(value)
302
+ when Array
303
+ return '...' if depth <= 0
304
+
305
+ max = max_capture_collection_size_for_message
306
+ if value.length > max
307
+ value_ = value[0...max - 1] || []
308
+ value_ << '...'
309
+ value_ << value[-1]
310
+ value = value_
311
+ end
312
+ '[' + value.map do |item|
313
+ serialize_value_for_message(item, depth - 1)
314
+ end.join(', ') + ']'
315
+ when Hash
316
+ return '...' if depth <= 0
317
+
318
+ max = max_capture_collection_size_for_message
319
+ keys = value.keys
320
+ truncated = false
321
+ if value.length > max
322
+ keys_ = keys[0...max - 1] || []
323
+ keys_ << keys[-1]
324
+ keys = keys_
325
+ truncated = true
326
+ end
327
+ serialized = keys.map do |key|
328
+ "#{serialize_value_for_message(key, depth - 1)} => #{serialize_value_for_message(value[key], depth - 1)}"
329
+ end
330
+ if truncated
331
+ serialized[serialized.length] = serialized[serialized.length - 1]
332
+ serialized[serialized.length - 2] = '...'
333
+ end
334
+ "{#{serialized.join(", ")}}"
335
+ else
336
+ return '...' if depth <= 0
337
+
338
+ vars = value.instance_variables
339
+ truncated = false
340
+ max = max_capture_attribute_count_for_message
341
+ if vars.length > max
342
+ vars_ = vars[0...max - 1] || []
343
+ vars_ << vars[-1]
344
+ truncated = true
345
+ vars = vars_
346
+ end
347
+ serialized = vars.map do |var|
348
+ # +var+ here is always the instance variable name which is a
349
+ # symbol, we do not need to run it through our serializer.
350
+ "#{var}=#{serialize_value_for_message(value.send(:instance_variable_get, var), depth - 1)}"
351
+ end
352
+ if truncated
353
+ serialized << serialized.last
354
+ serialized[-2] = '...'
355
+ end
356
+ serialized = if serialized.any?
357
+ ' ' + serialized.join(' ')
358
+ end
359
+ "#<#{class_name(value.class)}#{serialized}>"
360
+ end
361
+ rescue => exc
362
+ telemetry&.report(exc, description: "Error serializing for message")
363
+ # TODO class_name(foo) can also fail, which we don't handle here.
364
+ # Telemetry reporting could potentially also fail?
365
+ "#<#{class_name(value.class)}: serialization error>"
366
+ end
367
+
264
368
  private
265
369
 
370
+ MAX_MESSAGE_COLLECTION_SIZE = 3
371
+ MAX_MESSAGE_ATTRIBUTE_COUNT = 5
372
+
373
+ def max_capture_collection_size_for_message
374
+ max = settings.dynamic_instrumentation.max_capture_collection_size
375
+ if max > MAX_MESSAGE_COLLECTION_SIZE
376
+ max = MAX_MESSAGE_COLLECTION_SIZE
377
+ end
378
+ max
379
+ end
380
+
381
+ def max_capture_attribute_count_for_message
382
+ max = settings.dynamic_instrumentation.max_capture_attribute_count
383
+ if max > MAX_MESSAGE_ATTRIBUTE_COUNT
384
+ max = MAX_MESSAGE_ATTRIBUTE_COUNT
385
+ end
386
+ max
387
+ end
388
+
266
389
  # Returns the name for the specified class object.
267
390
  #
268
391
  # Ruby can have nameless classes, e.g. Class.new is a class object
@@ -273,6 +396,27 @@ module Datadog
273
396
  # and we don't want to invoke user code.
274
397
  cls.name || "[Unnamed class]"
275
398
  end
399
+
400
+ def serialize_string_or_symbol_for_message(value)
401
+ max = settings.dynamic_instrumentation.max_capture_string_length
402
+ if max > 100
403
+ max = 100
404
+ end
405
+ value = value.to_s
406
+ if (length = value.length) > max
407
+ if max < 5
408
+ value[0...max]
409
+ else
410
+ upper = length - max / 2 + 1
411
+ if max % 2 == 0
412
+ upper += 1
413
+ end
414
+ value[0...max / 2 - 1] + '...' + value[upper...length]
415
+ end
416
+ else
417
+ value
418
+ end
419
+ end
276
420
  end
277
421
  end
278
422
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../core/transport/parcel'
4
- require_relative 'http/client'
4
+ require_relative 'http/diagnostics'
5
5
 
6
6
  module Datadog
7
7
  module DI
@@ -21,7 +21,7 @@ module Datadog
21
21
  @apis = apis
22
22
  @logger = logger
23
23
 
24
- @client = HTTP::Client.new(current_api, logger: logger)
24
+ @client = DI::Transport::HTTP::Diagnostics::Client.new(current_api, logger: logger)
25
25
  end
26
26
 
27
27
  def current_api
@@ -2,14 +2,14 @@
2
2
 
3
3
  require_relative '../../../core/transport/http/api/instance'
4
4
  require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
5
+ require_relative '../../../core/transport/http/client'
6
6
 
7
7
  module Datadog
8
8
  module DI
9
9
  module Transport
10
10
  module HTTP
11
11
  module Diagnostics
12
- module Client
12
+ class Client < Core::Transport::HTTP::Client
13
13
  def send_diagnostics_payload(request)
14
14
  send_request(request) do |api, env|
15
15
  api.send_diagnostics(env)
@@ -57,8 +57,6 @@ module Datadog
57
57
  end
58
58
  end
59
59
  end
60
-
61
- HTTP::Client.include(Diagnostics::Client)
62
60
  end
63
61
  end
64
62
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  require_relative '../../../core/transport/http/api/instance'
4
4
  require_relative '../../../core/transport/http/api/spec'
5
- require_relative 'client'
5
+ require_relative '../../../core/transport/http/client'
6
6
 
7
7
  module Datadog
8
8
  module DI
9
9
  module Transport
10
10
  module HTTP
11
11
  module Input
12
- module Client
12
+ class Client < Core::Transport::HTTP::Client
13
13
  def send_input_payload(request)
14
14
  send_request(request) do |api, env|
15
15
  api.send_input(env)
@@ -69,8 +69,6 @@ module Datadog
69
69
  end
70
70
  end
71
71
  end
72
-
73
- HTTP::Client.include(Input::Client)
74
72
  end
75
73
  end
76
74
  end
@@ -40,9 +40,13 @@ module Datadog
40
40
  api_version: nil,
41
41
  headers: nil
42
42
  )
43
- Core::Transport::HTTP.build(api_instance_class: Input::API::Instance,
43
+ Core::Transport::HTTP.build(
44
+ api_instance_class: Input::API::Instance,
44
45
  logger: logger,
45
- agent_settings: agent_settings, api_version: api_version, headers: headers) do |transport|
46
+ agent_settings: agent_settings,
47
+ api_version: api_version,
48
+ headers: headers,
49
+ ) do |transport|
46
50
  apis = API.defaults
47
51
 
48
52
  transport.api API::INPUT, apis[API::INPUT]
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../core/chunker'
4
+ require_relative '../../core/encoding'
5
+ require_relative '../../core/tag_builder'
3
6
  require_relative '../../core/transport/parcel'
4
- require_relative 'http/client'
7
+ require_relative '../../core/transport/request'
8
+ require_relative '../error'
9
+ require_relative 'http/input'
5
10
 
6
11
  module Datadog
7
12
  module DI
@@ -24,11 +29,30 @@ module Datadog
24
29
  class Transport
25
30
  attr_reader :client, :apis, :default_api, :current_api_id, :logger
26
31
 
32
+ # The limit on an individual snapshot payload, aka "log line",
33
+ # is 1 MB.
34
+ #
35
+ # TODO There is an RFC for snapshot pruning that should be
36
+ # implemented to reduce the size of snapshots to be below this
37
+ # limit, so that we can send a portion of the captured data
38
+ # rather than dropping the snapshot entirely.
39
+ MAX_SERIALIZED_SNAPSHOT_SIZE = 1024 * 1024
40
+
41
+ # The maximum chunk (batch) size that intake permits is 5 MB.
42
+ #
43
+ # Two bytes are for the [ and ] of JSON array syntax.
44
+ MAX_CHUNK_SIZE = 5 * 1024 * 1024 - 2
45
+
46
+ # Try to send smaller payloads to avoid large network requests.
47
+ # If a payload is larger than default chunk size but is under the
48
+ # max chunk size, it will still get sent out.
49
+ DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024
50
+
27
51
  def initialize(apis, default_api, logger:)
28
52
  @apis = apis
29
53
  @logger = logger
30
54
 
31
- @client = HTTP::Client.new(current_api, logger: logger)
55
+ @client = DI::Transport::HTTP::Input::Client.new(current_api, logger: logger)
32
56
  end
33
57
 
34
58
  def current_api
@@ -36,9 +60,45 @@ module Datadog
36
60
  end
37
61
 
38
62
  def send_input(payload, tags)
39
- json = JSON.dump(payload)
40
- parcel = EncodedParcel.new(json)
63
+ # Tags are the same for all chunks, serialize them one time.
41
64
  serialized_tags = Core::TagBuilder.serialize_tags(tags)
65
+
66
+ encoder = Core::Encoding::JSONEncoder
67
+ encoded_snapshots = Core::Utils::Array.filter_map(payload) do |snapshot|
68
+ encoded = encoder.encode(snapshot)
69
+ if encoded.length > MAX_SERIALIZED_SNAPSHOT_SIZE
70
+ # Drop the snapshot.
71
+ # TODO report via telemetry metric?
72
+ logger.debug { "di: dropping too big snapshot" }
73
+ nil
74
+ else
75
+ encoded
76
+ end
77
+ end
78
+
79
+ Datadog::Core::Chunker.chunk_by_size(
80
+ encoded_snapshots, DEFAULT_CHUNK_SIZE,
81
+ ).each do |chunk|
82
+ # We drop snapshots that are too big earlier.
83
+ # The limit on chunked payload length here is greater
84
+ # than the limit on snapshot size, therefore no chunks
85
+ # can exceed limits here.
86
+ chunked_payload = encoder.join(chunk)
87
+
88
+ # We need to rescue exceptions for each chunk so that
89
+ # subsequent chunks are attempted to be sent.
90
+ begin
91
+ send_input_chunk(chunked_payload, serialized_tags)
92
+ rescue => exc
93
+ logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
94
+ end
95
+ end
96
+
97
+ payload
98
+ end
99
+
100
+ def send_input_chunk(chunked_payload, serialized_tags)
101
+ parcel = EncodedParcel.new(chunked_payload)
42
102
  request = Request.new(parcel, serialized_tags)
43
103
 
44
104
  response = @client.send_input_payload(request)