datadog 2.5.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,13 +38,42 @@ module Datadog
38
38
  #
39
39
  # @api private
40
40
  class Serializer
41
- def initialize(settings, redactor)
41
+ # Third-party library integration / custom serializers.
42
+ #
43
+ # Dynamic instrumentation has limited payload sizes, and for efficiency
44
+ # reasons it is not desirable to transmit data to Datadog that will
45
+ # never contain useful information. Additionally, due to depth limits,
46
+ # desired data may not even be included in payloads when serialized
47
+ # with the default, naive serializer. Therefore, custom objects like
48
+ # ActiveRecord model instances may need custom serializers.
49
+ #
50
+ # CUSTOMER NOTE: The API for defining custom serializers is not yet
51
+ # finalized. Please create an issue at
52
+ # https://github.com/datadog/dd-trace-rb/issues describing the
53
+ # object(s) you wish to serialize so that we can ensure your use case
54
+ # will be supported as the library evolves.
55
+ #
56
+ # Note that the current implementation does not permit defining a
57
+ # serializer for a particular class, which is the simplest use case.
58
+ # This is because the library itself does not need this functionality
59
+ # yet, and it won't help for ActiveRecord models (that derive from
60
+ # a common base class but are all of different classes) or for Mongoid
61
+ # models (that do not have a common base class at all but include a
62
+ # standard Mongoid module).
63
+ @@flat_registry = []
64
+ def self.register(condition: nil, &block)
65
+ @@flat_registry << {condition: condition, proc: block}
66
+ end
67
+
68
+ def initialize(settings, redactor, telemetry: nil)
42
69
  @settings = settings
43
70
  @redactor = redactor
71
+ @telemetry = telemetry
44
72
  end
45
73
 
46
74
  attr_reader :settings
47
75
  attr_reader :redactor
76
+ attr_reader :telemetry
48
77
 
49
78
  # Serializes positional and keyword arguments to a method,
50
79
  # as obtained by a method probe.
@@ -86,116 +115,135 @@ module Datadog
86
115
  # (integers, strings, arrays, hashes).
87
116
  #
88
117
  # Respects string length, collection size and traversal depth limits.
89
- def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth)
90
- if redactor.redact_type?(value)
91
- return {type: class_name(value.class), notCapturedReason: "redactedType"}
92
- end
118
+ def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth, type: nil)
119
+ cls = type || value.class
120
+ begin
121
+ if redactor.redact_type?(value)
122
+ return {type: class_name(cls), notCapturedReason: "redactedType"}
123
+ end
93
124
 
94
- if name && redactor.redact_identifier?(name)
95
- return {type: class_name(value.class), notCapturedReason: "redactedIdent"}
96
- end
125
+ if name && redactor.redact_identifier?(name)
126
+ return {type: class_name(cls), notCapturedReason: "redactedIdent"}
127
+ end
97
128
 
98
- serialized = {type: class_name(value.class)}
99
- case value
100
- when NilClass
101
- serialized.update(isNull: true)
102
- when Integer, Float, TrueClass, FalseClass
103
- serialized.update(value: value.to_s)
104
- when String, Symbol
105
- need_dup = false
106
- value = if String === value
107
- # This is the only place where we duplicate the value, currently.
108
- # All other values are immutable primitives (e.g. numbers).
109
- # However, do not duplicate if the string is frozen, or if
110
- # it is later truncated.
111
- need_dup = !value.frozen?
112
- value
113
- else
114
- value.to_s
129
+ @@flat_registry.each do |entry|
130
+ if (condition = entry[:condition]) && condition.call(value)
131
+ serializer_proc = entry.fetch(:proc)
132
+ return serializer_proc.call(self, value, name: nil, depth: depth)
133
+ end
115
134
  end
116
- max = settings.dynamic_instrumentation.max_capture_string_length
117
- if value.length > max
118
- serialized.update(truncated: true, size: value.length)
119
- value = value[0...max]
135
+
136
+ serialized = {type: class_name(cls)}
137
+ case value
138
+ when NilClass
139
+ serialized.update(isNull: true)
140
+ when Integer, Float, TrueClass, FalseClass
141
+ serialized.update(value: value.to_s)
142
+ when Time
143
+ # This path also handles DateTime values although they do not need
144
+ # to be explicitly added to the case statement.
145
+ serialized.update(value: value.iso8601)
146
+ when Date
147
+ serialized.update(value: value.to_s)
148
+ when String, Symbol
120
149
  need_dup = false
121
- end
122
- value = value.dup if need_dup
123
- serialized.update(value: value)
124
- when Array
125
- if depth < 0
126
- serialized.update(notCapturedReason: "depth")
127
- else
128
- max = settings.dynamic_instrumentation.max_capture_collection_size
129
- if max != 0 && value.length > max
130
- serialized.update(notCapturedReason: "collectionSize", size: value.length)
131
- # same steep failure with array slices.
132
- # https://github.com/soutaro/steep/issues/1219
133
- value = value[0...max] || []
150
+ value = if String === value
151
+ # This is the only place where we duplicate the value, currently.
152
+ # All other values are immutable primitives (e.g. numbers).
153
+ # However, do not duplicate if the string is frozen, or if
154
+ # it is later truncated.
155
+ need_dup = !value.frozen?
156
+ value
157
+ else
158
+ value.to_s
134
159
  end
135
- entries = value.map do |elt|
136
- serialize_value(elt, depth: depth - 1)
160
+ max = settings.dynamic_instrumentation.max_capture_string_length
161
+ if value.length > max
162
+ serialized.update(truncated: true, size: value.length)
163
+ value = value[0...max]
164
+ need_dup = false
137
165
  end
138
- serialized.update(elements: entries)
139
- end
140
- when Hash
141
- if depth < 0
142
- serialized.update(notCapturedReason: "depth")
143
- else
144
- max = settings.dynamic_instrumentation.max_capture_collection_size
145
- cur = 0
146
- entries = []
147
- value.each do |k, v|
148
- if max != 0 && cur >= max
166
+ value = value.dup if need_dup
167
+ serialized.update(value: value)
168
+ when Array
169
+ if depth < 0
170
+ serialized.update(notCapturedReason: "depth")
171
+ else
172
+ max = settings.dynamic_instrumentation.max_capture_collection_size
173
+ if max != 0 && value.length > max
149
174
  serialized.update(notCapturedReason: "collectionSize", size: value.length)
150
- break
175
+ # same steep failure with array slices.
176
+ # https://github.com/soutaro/steep/issues/1219
177
+ value = value[0...max] || []
178
+ end
179
+ entries = value.map do |elt|
180
+ serialize_value(elt, depth: depth - 1)
151
181
  end
152
- cur += 1
153
- entries << [serialize_value(k, depth: depth - 1), serialize_value(v, name: k, depth: depth - 1)]
182
+ serialized.update(elements: entries)
183
+ end
184
+ when Hash
185
+ if depth < 0
186
+ serialized.update(notCapturedReason: "depth")
187
+ else
188
+ max = settings.dynamic_instrumentation.max_capture_collection_size
189
+ cur = 0
190
+ entries = []
191
+ value.each do |k, v|
192
+ if max != 0 && cur >= max
193
+ serialized.update(notCapturedReason: "collectionSize", size: value.length)
194
+ break
195
+ end
196
+ cur += 1
197
+ entries << [serialize_value(k, depth: depth - 1), serialize_value(v, name: k, depth: depth - 1)]
198
+ end
199
+ serialized.update(entries: entries)
154
200
  end
155
- serialized.update(entries: entries)
156
- end
157
- else
158
- if depth < 0
159
- serialized.update(notCapturedReason: "depth")
160
201
  else
161
- fields = {}
162
- max = settings.dynamic_instrumentation.max_capture_attribute_count
163
- cur = 0
202
+ if depth < 0
203
+ serialized.update(notCapturedReason: "depth")
204
+ else
205
+ fields = {}
206
+ max = settings.dynamic_instrumentation.max_capture_attribute_count
207
+ cur = 0
164
208
 
165
- # MRI and JRuby 9.4.5+ preserve instance variable definition
166
- # order when calling #instance_variables. Previous JRuby versions
167
- # did not preserve order and returned the variables in arbitrary
168
- # order.
169
- #
170
- # The arbitrary order is problematic because 1) when there are
171
- # fewer instance variables than capture limit, the order in which
172
- # the variables are shown in UI will change from one capture to
173
- # the next and generally will be arbitrary to the user, and
174
- # 2) when there are more instance variables than capture limit,
175
- # *which* variables are captured will also change meaning user
176
- # looking at the UI may have "new" instance variables appear and
177
- # existing ones disappear as they are looking at multiple captures.
178
- #
179
- # For consistency, we should have some kind of stable order of
180
- # instance variables on all supported Ruby runtimes, so that the UI
181
- # stays consistent. Given that initial implementation of Ruby DI
182
- # does not support JRuby, we don't handle JRuby's lack of ordering
183
- # of #instance_variables here, but if JRuby is supported in the
184
- # future this may need to be addressed.
185
- ivars = value.instance_variables
209
+ # MRI and JRuby 9.4.5+ preserve instance variable definition
210
+ # order when calling #instance_variables. Previous JRuby versions
211
+ # did not preserve order and returned the variables in arbitrary
212
+ # order.
213
+ #
214
+ # The arbitrary order is problematic because 1) when there are
215
+ # fewer instance variables than capture limit, the order in which
216
+ # the variables are shown in UI will change from one capture to
217
+ # the next and generally will be arbitrary to the user, and
218
+ # 2) when there are more instance variables than capture limit,
219
+ # *which* variables are captured will also change meaning user
220
+ # looking at the UI may have "new" instance variables appear and
221
+ # existing ones disappear as they are looking at multiple captures.
222
+ #
223
+ # For consistency, we should have some kind of stable order of
224
+ # instance variables on all supported Ruby runtimes, so that the UI
225
+ # stays consistent. Given that initial implementation of Ruby DI
226
+ # does not support JRuby, we don't handle JRuby's lack of ordering
227
+ # of #instance_variables here, but if JRuby is supported in the
228
+ # future this may need to be addressed.
229
+ ivars = value.instance_variables
186
230
 
187
- ivars.each do |ivar|
188
- if cur >= max
189
- serialized.update(notCapturedReason: "fieldCount", fields: fields)
190
- break
231
+ ivars.each do |ivar|
232
+ if cur >= max
233
+ serialized.update(notCapturedReason: "fieldCount", fields: fields)
234
+ break
235
+ end
236
+ cur += 1
237
+ fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
191
238
  end
192
- cur += 1
193
- fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
239
+ serialized.update(fields: fields)
194
240
  end
195
- serialized.update(fields: fields)
196
241
  end
242
+ serialized
243
+ rescue => exc
244
+ telemetry&.report(exc, description: "Error serializing")
245
+ {type: class_name(cls), notSerializedReason: exc.to_s}
197
246
  end
198
- serialized
199
247
  end
200
248
 
201
249
  private
@@ -35,31 +35,44 @@ module Datadog
35
35
  StringIO.new(JSON.dump(payload)), 'application/json', 'event.json'
36
36
  )
37
37
  payload = {'event' => event_payload}
38
- send_request('Probe status submission', DIAGNOSTICS_PATH, payload)
38
+ # Core transport unconditionally specifies headers to underlying
39
+ # Net::HTTP client, ends up passing 'nil' as headers if none are
40
+ # specified by us, which then causes Net::HTTP to die with an exception.
41
+ send_request('Probe status submission',
42
+ path: DIAGNOSTICS_PATH, form: payload, headers: {})
39
43
  end
40
44
 
41
45
  def send_input(payload)
42
- send_request('Probe snapshot submission', INPUT_PATH, payload,
46
+ send_request('Probe snapshot submission',
47
+ path: INPUT_PATH, body: payload.to_s,
43
48
  headers: {'content-type' => 'application/json'},)
44
49
  end
45
50
 
51
+ # TODO status should use either input or diagnostics endpoints
52
+ # depending on agent version.
53
+ alias_method :send_status, :send_diagnostics
54
+
55
+ alias_method :send_snapshot, :send_input
56
+
46
57
  private
47
58
 
48
59
  attr_reader :client
49
60
 
50
- def send_request(desc, path, payload, headers: {})
61
+ def send_request(desc, **options)
51
62
  # steep:ignore:start
52
- env = OpenStruct.new(
53
- path: path,
54
- form: payload,
55
- headers: headers,
56
- )
63
+ env = OpenStruct.new(**options)
57
64
  # steep:ignore:end
58
65
  response = client.post(env)
59
66
  unless response.ok?
60
67
  raise Error::AgentCommunicationError, "#{desc} failed: #{response.code}: #{response.payload}"
61
68
  end
62
- rescue IOError, SystemCallError => exc
69
+ # Datadog::Core::Transport does not perform any exception mapping,
70
+ # therefore we could have any exception here from failure to parse
71
+ # agent URI for example.
72
+ # If we ever implement retries for network errors, we should distinguish
73
+ # actual network errors from non-network errors that are raised by
74
+ # transport code.
75
+ rescue => exc
63
76
  raise Error::AgentCommunicationError, "#{desc} failed: #{exc.class}: #{exc}"
64
77
  end
65
78
  end
data/lib/datadog/di.rb CHANGED
@@ -1,21 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'di/error'
4
- require_relative 'di/configuration'
5
4
  require_relative 'di/code_tracker'
5
+ require_relative 'di/component'
6
+ require_relative 'di/configuration'
6
7
  require_relative 'di/extensions'
7
8
  require_relative 'di/instrumenter'
8
9
  require_relative 'di/probe'
10
+ require_relative 'di/probe_builder'
11
+ require_relative 'di/probe_manager'
12
+ require_relative 'di/probe_notification_builder'
13
+ require_relative 'di/probe_notifier_worker'
9
14
  require_relative 'di/redactor'
10
15
  require_relative 'di/serializer'
11
16
  require_relative 'di/transport'
12
17
  require_relative 'di/utils'
13
18
 
19
+ if defined?(ActiveRecord::Base)
20
+ # The third-party library integrations need to be loaded after the
21
+ # third-party libraries are loaded. Tracing and appsec use Railtie
22
+ # to delay integrations until all of the application's dependencies
23
+ # are loaded, when running under Rails. We should do the same here in
24
+ # principle, however DI currently only has an ActiveRecord integration
25
+ # and AR should be loaded before any application code is loaded, being
26
+ # part of Rails, therefore for now we should be OK to just require the
27
+ # AR integration from here.
28
+ require_relative 'di/contrib/active_record'
29
+ end
30
+
14
31
  module Datadog
15
32
  # Namespace for Datadog dynamic instrumentation.
16
33
  #
17
34
  # @api private
18
35
  module DI
36
+ class << self
37
+ def enabled?
38
+ Datadog.configuration.dynamic_instrumentation.enabled
39
+ end
40
+ end
41
+
19
42
  # Expose DI to global shared objects
20
43
  Extensions.activate!
21
44
 
@@ -52,6 +75,31 @@ module Datadog
52
75
  def code_tracking_active?
53
76
  code_tracker&.active? || false
54
77
  end
78
+
79
+ def component
80
+ # TODO uncomment when remote is merged
81
+ #Datadog.send(:components).dynamic_instrumentation
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ =begin not yet enabled
88
+ # :script_compiled trace point was added in Ruby 2.6.
89
+ if RUBY_VERSION >= '2.6'
90
+ begin
91
+ # Activate code tracking by default because line trace points will not work
92
+ # without it.
93
+ Datadog::DI.activate_tracking!
94
+ rescue => exc
95
+ if defined?(Datadog.logger)
96
+ Datadog.logger.warn("Failed to activate code tracking for DI: #{exc.class}: #{exc}")
97
+ else
98
+ # We do not have Datadog logger potentially because DI code tracker is
99
+ # being loaded early in application boot process and the rest of datadog
100
+ # wasn't loaded yet. Output to standard error.
101
+ warn("Failed to activate code tracking for DI: #{exc.class}: #{exc}")
55
102
  end
56
103
  end
57
104
  end
105
+ =end
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 5
6
+ MINOR = 7
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-05 00:00:00.000000000 Z
11
+ date: 2024-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 13.1.0.1.0
61
+ version: 14.1.0.1.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 13.1.0.1.0
68
+ version: 14.1.0.1.0
69
69
  description: |
70
70
  datadog is Datadog's client library for Ruby. It includes a suite of tools
71
71
  which provide visibility into the performance and security of Ruby applications,
@@ -364,13 +364,16 @@ files:
364
364
  - lib/datadog/core/workers/runtime_metrics.rb
365
365
  - lib/datadog/di.rb
366
366
  - lib/datadog/di/code_tracker.rb
367
+ - lib/datadog/di/component.rb
367
368
  - lib/datadog/di/configuration.rb
368
369
  - lib/datadog/di/configuration/settings.rb
370
+ - lib/datadog/di/contrib/active_record.rb
369
371
  - lib/datadog/di/error.rb
370
372
  - lib/datadog/di/extensions.rb
371
373
  - lib/datadog/di/instrumenter.rb
372
374
  - lib/datadog/di/probe.rb
373
375
  - lib/datadog/di/probe_builder.rb
376
+ - lib/datadog/di/probe_manager.rb
374
377
  - lib/datadog/di/probe_notification_builder.rb
375
378
  - lib/datadog/di/probe_notifier_worker.rb
376
379
  - lib/datadog/di/redactor.rb
@@ -895,8 +898,8 @@ licenses:
895
898
  - Apache-2.0
896
899
  metadata:
897
900
  allowed_push_host: https://rubygems.org
898
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.5.0/CHANGELOG.md
899
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.5.0
901
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.7.0/CHANGELOG.md
902
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.7.0
900
903
  post_install_message:
901
904
  rdoc_options: []
902
905
  require_paths:
@@ -915,7 +918,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
915
918
  - !ruby/object:Gem::Version
916
919
  version: 2.0.0
917
920
  requirements: []
918
- rubygems_version: 3.4.10
921
+ rubygems_version: 3.5.16
919
922
  signing_key:
920
923
  specification_version: 4
921
924
  summary: Datadog tracing code for your Ruby applications