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 +4 -4
- data/CHANGELOG.md +19 -1
- data/lib/datadog/ai_guard/configuration/settings.rb +13 -1
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +42 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
- data/lib/datadog/ai_guard.rb +2 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +37 -23
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
- data/lib/datadog/core/knuth_sampler.rb +57 -0
- data/lib/datadog/di/configuration/settings.rb +22 -0
- data/lib/datadog/di/redactor.rb +8 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
- data/lib/datadog/version.rb +1 -1
- metadata +11 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91ec2ddb3720f61843ee7526cd4e3493a646ab7afaf3d6e4cce71019d1dfd582
|
|
4
|
+
data.tar.gz: d0996f8b1eb568aea751a1e781fa4d9dcc7e1d436911fd579c7aa43947340f32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
|
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
|
data/lib/datadog/ai_guard.rb
CHANGED
|
@@ -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
|
|
144
|
-
Datadog::AppSec::Contrib::Rails::Patcher.report_routes_via_telemetry(
|
|
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,
|
|
53
|
-
|
|
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
|
|
@@ -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 |*
|
|
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(*
|
|
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
|
|
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
|
-
|
|
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{
|
|
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
|
|
50
|
-
|
|
48
|
+
def self.json?(media_type)
|
|
49
|
+
return false if media_type.nil? || media_type.empty?
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
match = MEDIA_TYPE_RE.match(media_type)
|
|
52
|
+
return false if match.nil?
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@parameters = {}
|
|
54
|
+
subtype = match['subtype']
|
|
55
|
+
return false if subtype.nil? || subtype.empty?
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
subtype.downcase!
|
|
58
|
+
subtype == 'json' || subtype.end_with?('+json')
|
|
59
|
+
end
|
|
59
60
|
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
65
|
+
@type = match['type'] || WILDCARD
|
|
66
|
+
@type.downcase!
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
@subtype = match['subtype'] || WILDCARD
|
|
69
|
+
@subtype.downcase!
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
@parameters = {}
|
|
72
|
+
|
|
73
|
+
parameters = match['parameters']
|
|
74
|
+
return if parameters.nil? || parameters.empty?
|
|
69
75
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
s << ';' << @parameters.map { |k, v| "#{k}=#{v}" }.join(';') if @parameters.count > 0
|
|
93
|
+
return "#{@type}/#{@subtype}" if @parameters.empty?
|
|
80
94
|
|
|
81
|
-
|
|
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
|
data/lib/datadog/di/redactor.rb
CHANGED
|
@@ -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
|
-
|
|
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 '
|
|
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
|
|
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
|
-
@
|
|
26
|
+
@sampler.rate
|
|
35
27
|
end
|
|
36
28
|
|
|
37
29
|
def sample_rate=(sample_rate)
|
|
38
|
-
@
|
|
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
|
-
(
|
|
34
|
+
@sampler.sample?(trace.id)
|
|
44
35
|
end
|
|
45
36
|
|
|
46
37
|
def sample!(trace)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return false unless sampled
|
|
38
|
+
return false unless sample?(trace)
|
|
50
39
|
|
|
51
|
-
trace.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
|
data/lib/datadog/version.rb
CHANGED
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
1096
|
-
source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.
|
|
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:
|