datadog 2.27.0 → 2.28.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fec5b056193bb744192c4df3a9843e630d0675a53d88162ba51d5f250ca7a31d
4
- data.tar.gz: bb542f2f2c8164afc1426c103bd49113b10a5282be3ee13872fc13d138fa6183
3
+ metadata.gz: 91ec2ddb3720f61843ee7526cd4e3493a646ab7afaf3d6e4cce71019d1dfd582
4
+ data.tar.gz: d0996f8b1eb568aea751a1e781fa4d9dcc7e1d436911fd579c7aa43947340f32
5
5
  SHA512:
6
- metadata.gz: cd063e5e9e617d771603c6eae2305b1c5677587b3ae75690e1e96d273957fc20dc911bdd03264982ded4353c263fd06423f990a00135be3ed310cfd9a1a9c259
7
- data.tar.gz: dbeea8e08bdd6e1f2fb592532410008980a44a10d349437da297fae0f49f21aa93ec72f0af909e5844d5bf5ddf3fa7be36762627505508bf509d78f57c81220e
6
+ metadata.gz: fb8da926cd9a09a500c781f60ad5fb35c72c9b3162acc067229f88d01281c63c6753c4230a2cf56f803fb0174994707b159e31f5d584e6682313a13e2187b61e
7
+ data.tar.gz: 8c93304f737b5798af5d6ed520f7006d7aa2e7644243fbfcd38994d66dde0aefb53084bca96024e61a7cbebcb72e60f958715fe4864c1e7fe90733103f9cb527
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.28.0] - 2026-02-04
6
+
7
+ ### Added
8
+
9
+ * AI Guard: Add instrumentation for `ruby_llm` gem. ([#5273][])
10
+
11
+ ### Changed
12
+
13
+ * Core: Bump minimum version of datadog-ruby_core_source to 3.5.2 ([#5278][])
14
+
15
+ ### Fixed
16
+
17
+ * AppSec: Fix exception in Rails contrib `:after_routes_loaded` hook when routes are reloaded. ([#5283][])
18
+
5
19
  ## [2.27.0] - 2026-01-21
6
20
 
7
21
  ### Added
@@ -3472,7 +3486,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3472
3486
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3473
3487
 
3474
3488
 
3475
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.27.0...master
3489
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.28.0...master
3490
+ [2.28.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.27.0...v2.28.0
3476
3491
  [2.27.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.26.0...v2.27.0
3477
3492
  [2.26.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.25.0...v2.26.0
3478
3493
  [2.25.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.24.0...v2.25.0
@@ -5148,6 +5163,9 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5148
5163
  [#5246]: https://github.com/DataDog/dd-trace-rb/issues/5246
5149
5164
  [#5247]: https://github.com/DataDog/dd-trace-rb/issues/5247
5150
5165
  [#5254]: https://github.com/DataDog/dd-trace-rb/issues/5254
5166
+ [#5273]: https://github.com/DataDog/dd-trace-rb/issues/5273
5167
+ [#5278]: https://github.com/DataDog/dd-trace-rb/issues/5278
5168
+ [#5283]: https://github.com/DataDog/dd-trace-rb/issues/5283
5151
5169
  [@AdrianLC]: https://github.com/AdrianLC
5152
5170
  [@Azure7111]: https://github.com/Azure7111
5153
5171
  [@BabyGroot]: https://github.com/BabyGroot
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'uri'
3
+ require "uri"
4
4
  require_relative "ext"
5
5
 
6
6
  module Datadog
@@ -30,6 +30,18 @@ module Datadog
30
30
  o.default false
31
31
  end
32
32
 
33
+ define_method(:instrument) do |integration_name|
34
+ return unless enabled # steep:ignore
35
+
36
+ if (registered_integration = Datadog::AIGuard::Contrib::Integration.registry[integration_name])
37
+ klass = registered_integration.klass
38
+ if klass.loaded? && klass.compatible?
39
+ instance = klass.new
40
+ instance.patcher.patch unless instance.patcher.patched?
41
+ end
42
+ end
43
+ end
44
+
33
45
  # AI Guard API endpoint path.
34
46
  #
35
47
  # @default `DD_AI_GUARD_ENDPOINT`, otherwise `nil`
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Contrib
6
+ # Base provides features that are shared across all integrations
7
+ module Integration
8
+ RegisteredIntegration = Struct.new(:name, :klass, :options)
9
+
10
+ @registry = {}
11
+
12
+ # Class-level methods for Integration
13
+ module ClassMethods
14
+ def register_as(name, options = {})
15
+ Integration.register(self, name, options)
16
+ end
17
+
18
+ def compatible?
19
+ true
20
+ end
21
+ end
22
+
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ def self.register(integration, name, options)
28
+ @registry[name] = RegisteredIntegration.new(name, integration, options)
29
+ end
30
+
31
+ def self.registry
32
+ @registry
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Contrib
6
+ module RubyLLM
7
+ # module that gets prepended to RubyLLM::Chat
8
+ module ChatInstrumentation
9
+ class << self
10
+ def evaluate!(messages)
11
+ ai_guard_messages = messages.flat_map do |message|
12
+ if message.tool_call?
13
+ message.tool_calls.map do |tool_call_id, tool_call|
14
+ AIGuard.assistant(id: tool_call_id, tool_name: tool_call.name, arguments: tool_call.arguments.to_s)
15
+ end
16
+ elsif message.tool_result?
17
+ AIGuard.tool(tool_call_id: message.tool_call_id, content: message.content)
18
+ else
19
+ AIGuard.message(role: message.role, content: message.content)
20
+ end
21
+ end
22
+
23
+ AIGuard.evaluate(*ai_guard_messages, allow_raise: true)
24
+ end
25
+ end
26
+
27
+ def complete(&block)
28
+ Datadog::AIGuard::Contrib::RubyLLM::ChatInstrumentation.evaluate!(messages)
29
+
30
+ super
31
+ end
32
+
33
+ def handle_tool_calls(response, &block)
34
+ Datadog::AIGuard::Contrib::RubyLLM::ChatInstrumentation.evaluate!(messages)
35
+
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration"
4
+ require_relative "patcher"
5
+
6
+ module Datadog
7
+ module AIGuard
8
+ module Contrib
9
+ module RubyLLM
10
+ # This class provides helper methods that are used when patching RubyLLM
11
+ class Integration
12
+ include Datadog::AIGuard::Contrib::Integration
13
+
14
+ MINIMUM_VERSION = Gem::Version.new("1.0.0")
15
+
16
+ register_as :ruby_llm, auto_patch: false
17
+
18
+ def self.version
19
+ Gem.loaded_specs["ruby_llm"]&.version
20
+ end
21
+
22
+ def self.loaded?
23
+ !defined?(::RubyLLM).nil?
24
+ end
25
+
26
+ def self.compatible?
27
+ super && !!(version&.>= MINIMUM_VERSION)
28
+ end
29
+
30
+ def self.auto_instrument?
31
+ false
32
+ end
33
+
34
+ def patcher
35
+ Patcher
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chat_instrumentation"
4
+
5
+ module Datadog
6
+ module AIGuard
7
+ module Contrib
8
+ module RubyLLM
9
+ # AIGuard patcher module for RubyLLM
10
+ module Patcher
11
+ module_function
12
+
13
+ def patched?
14
+ !!Patcher.instance_variable_get(:@patched)
15
+ end
16
+
17
+ def target_version
18
+ Integration.version
19
+ end
20
+
21
+ def patch
22
+ ::RubyLLM::Chat.prepend(ChatInstrumentation)
23
+
24
+ Patcher.instance_variable_set(:@patched, true)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,8 @@
3
3
  require_relative "core/configuration"
4
4
  require_relative "ai_guard/configuration"
5
5
 
6
+ require_relative "ai_guard/contrib/ruby_llm/integration"
7
+
6
8
  module Datadog
7
9
  # A namespace for the AI Guard component.
8
10
  module AIGuard
@@ -24,7 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  def self.compatible?
27
- super && version >= MINIMUM_VERSION
27
+ super && !!(version&.>= MINIMUM_VERSION)
28
28
  end
29
29
 
30
30
  def self.auto_instrument?
@@ -11,7 +11,7 @@ module Datadog
11
11
  module_function
12
12
 
13
13
  def patched?
14
- Patcher.instance_variable_get(:@patched)
14
+ !!Patcher.instance_variable_get(:@patched)
15
15
  end
16
16
 
17
17
  def target_version
@@ -136,12 +136,20 @@ module Datadog
136
136
  if Datadog::AppSec::Contrib::Rails::Patcher.target_version < Gem::Version.new('7.1')
137
137
  Datadog::AppSec::Contrib::Rails::Patcher.report_routes_via_telemetry(::Rails.application.routes.routes)
138
138
  end
139
+ rescue => e
140
+ error_message = 'Failed to get application routes'
141
+ Datadog.logger.error("#{error_message}, #{e.class}: #{e.message}")
142
+ AppSec.telemetry.report(e, description: error_message)
139
143
  end
140
144
  end
141
145
 
142
146
  def subscribe_to_routes_loaded
143
- ::ActiveSupport.on_load(:after_routes_loaded) do |app|
144
- Datadog::AppSec::Contrib::Rails::Patcher.report_routes_via_telemetry(app.routes.routes)
147
+ ::ActiveSupport.on_load(:after_routes_loaded) do
148
+ Datadog::AppSec::Contrib::Rails::Patcher.report_routes_via_telemetry(::Rails.application.routes.routes)
149
+ rescue => e
150
+ error_message = 'Failed to get application routes'
151
+ Datadog.logger.error("#{error_message}, #{e.class}: #{e.message}")
152
+ AppSec.telemetry.report(e, description: error_message)
145
153
  end
146
154
  end
147
155
 
@@ -23,7 +23,7 @@ module Datadog
23
23
 
24
24
  def watch_request_dispatch(gateway = Instrumentation.gateway)
25
25
  gateway.watch('sinatra.request.dispatch', :appsec) do |stack, gateway_request|
26
- context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
26
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY] # : Context
27
27
 
28
28
  persistent_data = {
29
29
  'server.request.body' => gateway_request.form_hash
@@ -49,8 +49,9 @@ module Datadog
49
49
  end
50
50
 
51
51
  def watch_request_routed(gateway = Instrumentation.gateway)
52
- gateway.watch('sinatra.request.routed', :appsec) do |stack, (gateway_request, gateway_route_params)|
53
- context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
52
+ gateway.watch('sinatra.request.routed', :appsec) do |stack, args|
53
+ gateway_request, gateway_route_params = args # : [Gateway::Request, Gateway::RouteParams]
54
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY] # : Context
54
55
 
55
56
  persistent_data = {
56
57
  'server.request.path_params' => gateway_route_params.params
@@ -75,7 +76,7 @@ module Datadog
75
76
 
76
77
  def watch_response_body_json(gateway = Instrumentation.gateway)
77
78
  gateway.watch('sinatra.response.body.json', :appsec) do |stack, container|
78
- context = container.context
79
+ context = container.context # : Context
79
80
 
80
81
  persistent_data = {
81
82
  'server.response.body' => container.data
@@ -26,7 +26,7 @@ module Datadog
26
26
  end
27
27
 
28
28
  def self.compatible?
29
- super && version >= MINIMUM_VERSION
29
+ !!(super && (version&.>= MINIMUM_VERSION))
30
30
  end
31
31
 
32
32
  def self.auto_instrument?
@@ -71,7 +71,7 @@ module Datadog
71
71
  # path params are returned by pattern.params in process_route, then
72
72
  # merged with normal params, so we get both
73
73
  module RoutePatch
74
- def process_route(*)
74
+ def process_route(*args)
75
75
  env = @request.env
76
76
 
77
77
  context = env[Datadog::AppSec::Ext::CONTEXT_KEY]
@@ -83,7 +83,7 @@ module Datadog
83
83
  # Capture normal params.
84
84
  base_params = params
85
85
 
86
- super do |*args|
86
+ super do |*super_args|
87
87
  # This block is called only once the route is found.
88
88
  # At this point params has both route params and normal params.
89
89
  route_params = params.each.with_object({}) { |(k, v), h| h[k] = v unless base_params.key?(k) }
@@ -93,7 +93,7 @@ module Datadog
93
93
 
94
94
  Instrumentation.gateway.push('sinatra.request.routed', [gateway_request, gateway_route_params])
95
95
 
96
- yield(*args)
96
+ yield(*super_args)
97
97
  end
98
98
  end
99
99
  end
@@ -123,7 +123,7 @@ module Datadog
123
123
  end
124
124
 
125
125
  def patch_json?
126
- defined?(::Sinatra::JSON) && ::Sinatra::Base < ::Sinatra::JSON
126
+ !!(defined?(::Sinatra::JSON) && ::Sinatra::Base < ::Sinatra::JSON)
127
127
  end
128
128
  end
129
129
  end
@@ -12,7 +12,7 @@ module Datadog
12
12
  # body right before it is serialized.
13
13
  module JsonPatch
14
14
  def json(object, options = {})
15
- context = @request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
15
+ context = @request.env[Datadog::AppSec::Ext::CONTEXT_KEY] # : Context?
16
16
  return super unless context
17
17
 
18
18
  data = Utils::HashCoercion.coerce(object)
@@ -4,14 +4,13 @@ module Datadog
4
4
  module AppSec
5
5
  module Utils
6
6
  module HTTP
7
- # Implementation of media type for content negotiation
7
+ # Implementation of media type for HTTP headers
8
8
  #
9
9
  # See:
10
10
  # - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.1
11
11
  # - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
12
12
  class MediaType
13
- class ParseError < ::StandardError
14
- end
13
+ ParseError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
15
14
 
16
15
  WILDCARD = '*'
17
16
 
@@ -19,7 +18,7 @@ module Datadog
19
18
  TOKEN_RE = /[-#$%&'*+.^_`|~A-Za-z0-9]+/.freeze
20
19
 
21
20
  # See: https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1
22
- PARAMETER_RE = %r{ # rubocop:disable Style/RegexpLiteral
21
+ PARAMETER_RE = %r{
23
22
  (?:
24
23
  (?<parameter_name>#{TOKEN_RE})
25
24
  =
@@ -46,39 +45,54 @@ module Datadog
46
45
 
47
46
  attr_reader :type, :subtype, :parameters
48
47
 
49
- def initialize(media_type)
50
- media_type_match = MEDIA_TYPE_RE.match(media_type)
48
+ def self.json?(media_type)
49
+ return false if media_type.nil? || media_type.empty?
51
50
 
52
- raise ParseError, media_type.inspect if media_type_match.nil?
51
+ match = MEDIA_TYPE_RE.match(media_type)
52
+ return false if match.nil?
53
53
 
54
- @type = (media_type_match['type'] || WILDCARD).downcase
55
- @subtype = (media_type_match['subtype'] || WILDCARD).downcase
56
- @parameters = {}
54
+ subtype = match['subtype']
55
+ return false if subtype.nil? || subtype.empty?
57
56
 
58
- parameters = media_type_match['parameters']
57
+ subtype.downcase!
58
+ subtype == 'json' || subtype.end_with?('+json')
59
+ end
59
60
 
60
- return if parameters.nil?
61
+ def initialize(media_type)
62
+ match = MEDIA_TYPE_RE.match(media_type)
63
+ raise ParseError, media_type.inspect if match.nil?
61
64
 
62
- parameters.split(';').map(&:strip).each do |parameter|
63
- parameter_match = PARAMETER_RE.match(parameter)
65
+ @type = match['type'] || WILDCARD
66
+ @type.downcase!
64
67
 
65
- next if parameter_match.nil?
68
+ @subtype = match['subtype'] || WILDCARD
69
+ @subtype.downcase!
66
70
 
67
- parameter_name = parameter_match['parameter_name']
68
- parameter_value = parameter_match['parameter_value']
71
+ @parameters = {}
72
+
73
+ parameters = match['parameters']
74
+ return if parameters.nil? || parameters.empty?
69
75
 
70
- next if parameter_name.nil? || parameter_value.nil?
76
+ parameters.scan(PARAMETER_RE) do |name, unquoted_value, quoted_value|
77
+ # NOTE: Order of unquoted_value and quoted_value does not matter,
78
+ # as they are mutually exclusive by the regex.
79
+ # @type var value: ::String?
80
+ value = unquoted_value || quoted_value
81
+ next if name.nil? || value.nil?
71
82
 
72
- @parameters[parameter_name.downcase] = parameter_value.downcase
83
+ # See https://github.com/soutaro/steep/issues/2051
84
+ name.downcase! # steep:ignore NoMethod
85
+ value.downcase!
86
+
87
+ # See https://github.com/soutaro/steep/issues/2051
88
+ @parameters[name] = value # steep:ignore ArgumentTypeMismatch
73
89
  end
74
90
  end
75
91
 
76
92
  def to_s
77
- s = +"#{@type}/#{@subtype}"
78
-
79
- s << ';' << @parameters.map { |k, v| "#{k}=#{v}" }.join(';') if @parameters.count > 0
93
+ return "#{@type}/#{@subtype}" if @parameters.empty?
80
94
 
81
- s
95
+ "#{@type}/#{@subtype};#{@parameters.map { |k, v| "#{k}=#{v}" }.join(";")}"
82
96
  end
83
97
  end
84
98
  end
@@ -48,6 +48,7 @@ module Datadog
48
48
  "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
49
49
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS",
50
50
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES",
51
+ "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS",
51
52
  "DD_ENV",
52
53
  "DD_ERROR_TRACKING_HANDLED_ERRORS",
53
54
  "DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE",
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../tag_builder'
4
4
  require_relative '../utils'
5
+ require_relative '../environment/process'
5
6
 
6
7
  module Datadog
7
8
  module Core
@@ -13,6 +14,11 @@ module Datadog
13
14
  'is_crash' => 'true',
14
15
  )
15
16
 
17
+ if settings.experimental_propagate_process_tags_enabled
18
+ process_tags = Environment::Process.serialized
19
+ hash['process_tags'] = process_tags unless process_tags.empty?
20
+ end
21
+
16
22
  Utils.encode_tags(hash)
17
23
  end
18
24
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ # Deterministic sampler using Knuth multiplicative hash algorithm.
6
+ #
7
+ # This sampler provides consistent sampling decisions based on an input value,
8
+ # ensuring the same input always produces the same sampling decision for a given rate.
9
+ #
10
+ # The algorithm multiplies the input by a large prime (Knuth factor), takes modulo
11
+ # to constrain to a fixed range, and compares against a threshold derived from the sample rate.
12
+ #
13
+ # @api private
14
+ # @see https://en.wikipedia.org/wiki/Hash_function#Multiplicative_hashing
15
+ class KnuthSampler
16
+ # Maximum unsigned 64-bit integer for uniform distribution across 64-bit input space.
17
+ UINT64_MAX = (1 << 64) - 1
18
+ UINT64_MODULO = 1 << 64
19
+
20
+ # Golden ratio constant for optimal distribution.
21
+ # @see https://en.wikipedia.org/wiki/Hash_function#Fibonacci_hashing
22
+ DEFAULT_KNUTH_FACTOR = 11400714819323198485
23
+
24
+ attr_reader :rate
25
+
26
+ # @param rate [Float] Sampling rate between +0.0+ and +1.0+ (inclusive).
27
+ # +0.0+ means no samples are kept; +1.0+ means all samples are kept.
28
+ # Invalid values fall back to +1.0+ (sample everything).
29
+ # @param knuth_factor [Integer] Multiplicative constant for hashing.
30
+ # Different factors produce different sampling distributions.
31
+ def initialize(rate = 1.0, knuth_factor: DEFAULT_KNUTH_FACTOR)
32
+ @knuth_factor = knuth_factor
33
+
34
+ rate = rate.to_f
35
+ unless rate >= 0.0 && rate <= 1.0
36
+ Datadog.logger.warn("Sample rate #{rate} is not between 0.0 and 1.0, falling back to 1.0")
37
+ rate = 1.0
38
+ end
39
+
40
+ @rate = rate
41
+ @threshold = @rate * UINT64_MAX
42
+ end
43
+
44
+ # Determines if the given input should be sampled.
45
+ #
46
+ # This method is deterministic: the same input value always produces
47
+ # the same result for a given sample rate and configuration.
48
+ #
49
+ # @param input [Integer] Value to determine sampling decision.
50
+ # Typically a trace ID or incrementing counter.
51
+ # @return [Boolean] +true+ if input should be sampled, +false+ otherwise
52
+ def sample?(input)
53
+ ((input * @knuth_factor) % UINT64_MODULO) <= @threshold
54
+ end
55
+ end
56
+ end
57
+ end
@@ -45,6 +45,28 @@ module Datadog
45
45
  o.default []
46
46
  end
47
47
 
48
+ # An array of variable and key names to exclude from the
49
+ # built-in redaction list.
50
+ #
51
+ # This allows users to capture values of variables that would
52
+ # otherwise be redacted by the default identifier list.
53
+ # For example, if an application has a "session" variable
54
+ # that does not contain sensitive data, "session" can be added
55
+ # to this list to exclude it from redaction.
56
+ #
57
+ # The names will be normalized the same way as redacted_identifiers,
58
+ # by removing the following symbols: _, -, @, $, and then matched
59
+ # against the complete variable or key name while ignoring the case.
60
+ option :redaction_excluded_identifiers do |o|
61
+ o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS"
62
+ o.env_parser do |value|
63
+ value&.split(",")&.map(&:strip)
64
+ end
65
+
66
+ o.type :array
67
+ o.default []
68
+ end
69
+
48
70
  # An array of class names, values of which will be redacted from
49
71
  # dynamic instrumentation snapshots. Example: FooClass.
50
72
  # If a name is suffixed by '*', it becomes a wildcard and
@@ -13,6 +13,10 @@ module Datadog
13
13
  # redaction. Additional names can be provided by the user via the
14
14
  # settings.dynamic_instrumentation.redacted_identifiers setting or
15
15
  # the DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS environment
16
+ # variable. Users can also exclude specific identifiers from the default
17
+ # redaction list via the
18
+ # settings.dynamic_instrumentation.redaction_excluded_identifiers setting or
19
+ # the DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS environment
16
20
  # variable. Currently no class names are subject to redaction by default;
17
21
  # class names can be provided via the
18
22
  # settings.dynamic_instrumentation.redacted_type_names setting or
@@ -61,7 +65,10 @@ module Datadog
61
65
  names.map! do |name|
62
66
  normalize(name)
63
67
  end
64
- Set.new(names)
68
+ excluded = settings.dynamic_instrumentation.redaction_excluded_identifiers.map do |name|
69
+ normalize(name)
70
+ end
71
+ Set.new(names) - Set.new(excluded)
65
72
  end
66
73
  end
67
74
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'sampler'
4
- require_relative '../utils'
4
+ require_relative '../../core/knuth_sampler'
5
5
 
6
6
  module Datadog
7
7
  module Tracing
@@ -9,46 +9,35 @@ module Datadog
9
9
  # {Datadog::Tracing::Sampling::RateSampler} is based on a sample rate.
10
10
  class RateSampler < Sampler
11
11
  KNUTH_FACTOR = 1111111111111111111
12
- UINT64_MODULO = (1 << 64)
13
12
 
14
13
  # Initialize a {Datadog::Tracing::Sampling::RateSampler}.
15
14
  # This sampler keeps a random subset of the traces. Its main purpose is to
16
15
  # reduce the instrumentation footprint.
17
16
  #
18
17
  # @param sample_rate [Numeric] the sample rate between 0.0 and 1.0, inclusive.
19
- # 0.0 means that no trace will be sampled; 1.0 means that all traces will be sampled.
18
+ # 0.0 means that no trace will be sampled; 1.0 means that all traces will be sampled.
20
19
  def initialize(sample_rate = 1.0, decision: nil)
21
20
  super()
22
-
23
- unless sample_rate >= 0.0 && sample_rate <= 1.0
24
- Datadog.logger.warn('sample rate is not between 0 and 1, falling back to 1')
25
- sample_rate = 1.0
26
- end
27
-
28
- self.sample_rate = sample_rate
29
-
21
+ @sampler = Core::KnuthSampler.new(sample_rate, knuth_factor: KNUTH_FACTOR)
30
22
  @decision = decision
31
23
  end
32
24
 
33
25
  def sample_rate(*_)
34
- @sample_rate
26
+ @sampler.rate
35
27
  end
36
28
 
37
29
  def sample_rate=(sample_rate)
38
- @sample_rate = sample_rate
39
- @sampling_id_threshold = sample_rate * Tracing::Utils::EXTERNAL_MAX_ID
30
+ @sampler = Core::KnuthSampler.new(sample_rate, knuth_factor: KNUTH_FACTOR)
40
31
  end
41
32
 
42
33
  def sample?(trace)
43
- ((trace.id * KNUTH_FACTOR) % UINT64_MODULO) <= @sampling_id_threshold
34
+ @sampler.sample?(trace.id)
44
35
  end
45
36
 
46
37
  def sample!(trace)
47
- sampled = sample?(trace)
48
-
49
- return false unless sampled
38
+ return false unless sample?(trace)
50
39
 
51
- trace.sample_rate = @sample_rate
40
+ trace.sample_rate = sample_rate
52
41
  trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, @decision) if @decision
53
42
 
54
43
  true
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 27
6
+ MINOR = 28
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.27.0
4
+ version: 2.28.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: 2026-01-21 00:00:00.000000000 Z
11
+ date: 2026-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: '3.5'
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: 3.5.1
36
+ version: 3.5.2
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '3.5'
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 3.5.1
46
+ version: 3.5.2
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: libddwaf
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -189,6 +189,10 @@ files:
189
189
  - lib/datadog/ai_guard/configuration.rb
190
190
  - lib/datadog/ai_guard/configuration/ext.rb
191
191
  - lib/datadog/ai_guard/configuration/settings.rb
192
+ - lib/datadog/ai_guard/contrib/integration.rb
193
+ - lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb
194
+ - lib/datadog/ai_guard/contrib/ruby_llm/integration.rb
195
+ - lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb
192
196
  - lib/datadog/ai_guard/evaluation.rb
193
197
  - lib/datadog/ai_guard/evaluation/message.rb
194
198
  - lib/datadog/ai_guard/evaluation/no_op_result.rb
@@ -360,6 +364,7 @@ files:
360
364
  - lib/datadog/core/feature_flags.rb
361
365
  - lib/datadog/core/git/ext.rb
362
366
  - lib/datadog/core/header_collection.rb
367
+ - lib/datadog/core/knuth_sampler.rb
363
368
  - lib/datadog/core/logger.rb
364
369
  - lib/datadog/core/logging/ext.rb
365
370
  - lib/datadog/core/metrics/client.rb
@@ -1092,8 +1097,8 @@ licenses:
1092
1097
  - Apache-2.0
1093
1098
  metadata:
1094
1099
  allowed_push_host: https://rubygems.org
1095
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.27.0/CHANGELOG.md
1096
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.27.0
1100
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.28.0/CHANGELOG.md
1101
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.28.0
1097
1102
  post_install_message:
1098
1103
  rdoc_options: []
1099
1104
  require_paths: