datadog 2.6.0 → 2.7.1

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.
@@ -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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ostruct'
3
4
  require_relative 'error'
4
5
 
5
6
  module Datadog
@@ -35,31 +36,44 @@ module Datadog
35
36
  StringIO.new(JSON.dump(payload)), 'application/json', 'event.json'
36
37
  )
37
38
  payload = {'event' => event_payload}
38
- send_request('Probe status submission', DIAGNOSTICS_PATH, payload)
39
+ # Core transport unconditionally specifies headers to underlying
40
+ # Net::HTTP client, ends up passing 'nil' as headers if none are
41
+ # specified by us, which then causes Net::HTTP to die with an exception.
42
+ send_request('Probe status submission',
43
+ path: DIAGNOSTICS_PATH, form: payload, headers: {})
39
44
  end
40
45
 
41
46
  def send_input(payload)
42
- send_request('Probe snapshot submission', INPUT_PATH, payload,
47
+ send_request('Probe snapshot submission',
48
+ path: INPUT_PATH, body: payload.to_s,
43
49
  headers: {'content-type' => 'application/json'},)
44
50
  end
45
51
 
52
+ # TODO status should use either input or diagnostics endpoints
53
+ # depending on agent version.
54
+ alias_method :send_status, :send_diagnostics
55
+
56
+ alias_method :send_snapshot, :send_input
57
+
46
58
  private
47
59
 
48
60
  attr_reader :client
49
61
 
50
- def send_request(desc, path, payload, headers: {})
62
+ def send_request(desc, **options)
51
63
  # steep:ignore:start
52
- env = OpenStruct.new(
53
- path: path,
54
- form: payload,
55
- headers: headers,
56
- )
64
+ env = OpenStruct.new(**options)
57
65
  # steep:ignore:end
58
66
  response = client.post(env)
59
67
  unless response.ok?
60
68
  raise Error::AgentCommunicationError, "#{desc} failed: #{response.code}: #{response.payload}"
61
69
  end
62
- rescue IOError, SystemCallError => exc
70
+ # Datadog::Core::Transport does not perform any exception mapping,
71
+ # therefore we could have any exception here from failure to parse
72
+ # agent URI for example.
73
+ # If we ever implement retries for network errors, we should distinguish
74
+ # actual network errors from non-network errors that are raised by
75
+ # transport code.
76
+ rescue => exc
63
77
  raise Error::AgentCommunicationError, "#{desc} failed: #{exc.class}: #{exc}"
64
78
  end
65
79
  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
@@ -442,7 +442,7 @@ module Datadog
442
442
  tags || @tags.dup
443
443
  end
444
444
  # Remove version tag if service is not the default service
445
- if merged_tags.key?(Core::Environment::Ext::TAG_VERSION) && service != @default_service
445
+ if merged_tags.key?(Core::Environment::Ext::TAG_VERSION) && service && service != @default_service
446
446
  merged_tags.delete(Core::Environment::Ext::TAG_VERSION)
447
447
  end
448
448
  merged_tags
@@ -3,8 +3,8 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 6
7
- PATCH = 0
6
+ MINOR = 7
7
+ PATCH = 1
8
8
  PRE = nil
9
9
  BUILD = nil
10
10
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
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.6.0
4
+ version: 2.7.1
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-06 00:00:00.000000000 Z
11
+ date: 2024-11-28 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: 14.0.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: 14.0.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.6.0/CHANGELOG.md
899
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.6.0
901
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.7.1/CHANGELOG.md
902
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.7.1
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