datadog 2.27.0 → 2.29.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 +64 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -4
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +5 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +183 -51
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +34 -5
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
- data/ext/libdatadog_api/crashtracker.c +5 -0
- data/ext/libdatadog_api/crashtracker_report_exception.c +236 -0
- 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/assets/blocked.html +2 -1
- data/lib/datadog/appsec/configuration/settings.rb +14 -0
- data/lib/datadog/appsec/context.rb +44 -9
- 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/excon/ssrf_detection_middleware.rb +54 -5
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +1 -3
- data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +7 -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/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
- data/lib/datadog/appsec/security_engine/engine.rb +23 -2
- data/lib/datadog/appsec/utils/http/body.rb +38 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +33 -26
- data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
- data/lib/datadog/core/configuration/components.rb +29 -4
- data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
- data/lib/datadog/core/configuration.rb +2 -2
- data/lib/datadog/core/crashtracking/component.rb +79 -19
- data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
- data/lib/datadog/core/environment/agent_info.rb +65 -1
- data/lib/datadog/core/knuth_sampler.rb +57 -0
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/process_discovery.rb +15 -19
- data/lib/datadog/core/rate_limiter.rb +2 -0
- data/lib/datadog/core/remote/component.rb +16 -5
- data/lib/datadog/core/remote/transport/config.rb +5 -11
- data/lib/datadog/core/telemetry/component.rb +0 -13
- data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/transport/http/response.rb +4 -0
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core.rb +6 -1
- data/lib/datadog/data_streams/processor.rb +34 -33
- data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
- data/lib/datadog/data_streams/transport/http.rb +0 -4
- data/lib/datadog/data_streams/transport/stats.rb +5 -12
- data/lib/datadog/di/component.rb +1 -1
- data/lib/datadog/di/configuration/settings.rb +31 -0
- data/lib/datadog/di/context.rb +6 -0
- data/lib/datadog/di/instrumenter.rb +178 -133
- data/lib/datadog/di/probe.rb +10 -1
- data/lib/datadog/di/probe_file_loader.rb +2 -2
- data/lib/datadog/di/probe_manager.rb +7 -2
- data/lib/datadog/di/probe_notification_builder.rb +29 -8
- data/lib/datadog/di/probe_notifier_worker.rb +13 -3
- data/lib/datadog/di/proc_responder.rb +4 -0
- data/lib/datadog/di/redactor.rb +8 -1
- data/lib/datadog/di/remote.rb +2 -2
- data/lib/datadog/di/transport/diagnostics.rb +5 -7
- data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
- data/lib/datadog/di/transport/http/input.rb +1 -1
- data/lib/datadog/di/transport/input.rb +5 -6
- data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
- data/lib/datadog/open_feature/transport.rb +8 -11
- data/lib/datadog/profiling/component.rb +0 -6
- data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
- data/lib/datadog/tracing/transport/io/client.rb +5 -8
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/traces.rb +4 -10
- data/lib/datadog/version.rb +1 -1
- metadata +17 -7
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
|
@@ -4,22 +4,19 @@ 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
|
-
class ParseError < ::StandardError
|
|
14
|
-
end
|
|
15
|
-
|
|
16
13
|
WILDCARD = '*'
|
|
17
14
|
|
|
18
15
|
# See: https://www.rfc-editor.org/rfc/rfc7230#section-3.2.6
|
|
19
16
|
TOKEN_RE = /[-#$%&'*+.^_`|~A-Za-z0-9]+/.freeze
|
|
20
17
|
|
|
21
18
|
# See: https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1
|
|
22
|
-
PARAMETER_RE = %r{
|
|
19
|
+
PARAMETER_RE = %r{
|
|
23
20
|
(?:
|
|
24
21
|
(?<parameter_name>#{TOKEN_RE})
|
|
25
22
|
=
|
|
@@ -46,39 +43,49 @@ module Datadog
|
|
|
46
43
|
|
|
47
44
|
attr_reader :type, :subtype, :parameters
|
|
48
45
|
|
|
49
|
-
def
|
|
50
|
-
|
|
46
|
+
def self.parse(media)
|
|
47
|
+
match = MEDIA_TYPE_RE.match(media)
|
|
48
|
+
return if match.nil?
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
type = match['type'] || WILDCARD
|
|
51
|
+
type.downcase!
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@parameters = {}
|
|
53
|
+
subtype = match['subtype'] || WILDCARD
|
|
54
|
+
subtype.downcase!
|
|
57
55
|
|
|
58
|
-
parameters =
|
|
56
|
+
parameters = {}
|
|
57
|
+
params = match['parameters']
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
unless params.nil? || params.empty?
|
|
60
|
+
params.scan(PARAMETER_RE) do |name, unquoted_value, quoted_value|
|
|
61
|
+
# NOTE: Order of unquoted_value and quoted_value does not matter,
|
|
62
|
+
# as they are mutually exclusive by the regex.
|
|
63
|
+
# @type var value: ::String?
|
|
64
|
+
value = unquoted_value || quoted_value
|
|
65
|
+
next if name.nil? || value.nil?
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
# See https://github.com/soutaro/steep/issues/2051
|
|
68
|
+
name.downcase! # steep:ignore NoMethod
|
|
69
|
+
value.downcase!
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
# See https://github.com/soutaro/steep/issues/2051
|
|
72
|
+
parameters[name] = value # steep:ignore ArgumentTypeMismatch
|
|
73
|
+
end
|
|
74
|
+
end
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
new(type: type, subtype: subtype, parameters: parameters)
|
|
77
|
+
end
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
def initialize(type:, subtype:, parameters: {})
|
|
80
|
+
@type = type
|
|
81
|
+
@subtype = subtype
|
|
82
|
+
@parameters = parameters
|
|
74
83
|
end
|
|
75
84
|
|
|
76
85
|
def to_s
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
s << ';' << @parameters.map { |k, v| "#{k}=#{v}" }.join(';') if @parameters.count > 0
|
|
86
|
+
return "#{@type}/#{@subtype}" if @parameters.empty?
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
"#{@type}/#{@subtype};#{@parameters.map { |k, v| "#{k}=#{v}" }.join(";")}"
|
|
82
89
|
end
|
|
83
90
|
end
|
|
84
91
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AppSec
|
|
7
|
+
module Utils
|
|
8
|
+
module HTTP
|
|
9
|
+
# Module for parsing URL encoded payloads
|
|
10
|
+
module URLEncoded
|
|
11
|
+
# Parses a URL encoded payload (query string or form data) into a hash
|
|
12
|
+
# of keys and values, merging duplicate keys.
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
#
|
|
16
|
+
# URLEncoded.parse("foo=bar&foo=baz&qux=quux") # => {"foo" => ["bar", "baz"], "qux" => "quux"}
|
|
17
|
+
#
|
|
18
|
+
# NOTE: Use it in the absence of `Rack::Utils.parse_query`
|
|
19
|
+
#
|
|
20
|
+
# WARNING: This method doesn't limit params byte size.
|
|
21
|
+
# See: https://github.com/rack/rack/blob/603b799de38b5eb9b2ff1657c8036a20f4c4db7b/lib/rack/query_parser.rb#L231-L233
|
|
22
|
+
def self.parse(payload)
|
|
23
|
+
return {} if payload.nil? || payload.empty?
|
|
24
|
+
|
|
25
|
+
payload.split('&').each_with_object({}) do |pair, memo|
|
|
26
|
+
next if pair.empty?
|
|
27
|
+
|
|
28
|
+
# NOTE: Steep has issues with mutation methods
|
|
29
|
+
# See https://github.com/ruby/rbs/issues/2819
|
|
30
|
+
#
|
|
31
|
+
# @type var key: ::String
|
|
32
|
+
# @type var value: ::String
|
|
33
|
+
key, value = pair.split('=', 2).map! do |value| #: ::String
|
|
34
|
+
CGI.unescape(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if (stored = memo[key])
|
|
38
|
+
if stored.is_a?(Array)
|
|
39
|
+
stored.push(value)
|
|
40
|
+
else
|
|
41
|
+
memo[key] = [stored, value]
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
memo[key] = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -11,6 +11,8 @@ require_relative '../runtime/metrics'
|
|
|
11
11
|
require_relative '../telemetry/component'
|
|
12
12
|
require_relative '../workers/runtime_metrics'
|
|
13
13
|
require_relative '../remote/component'
|
|
14
|
+
require_relative '../utils/at_fork_monkey_patch'
|
|
15
|
+
require_relative '../utils/only_once'
|
|
14
16
|
require_relative '../../tracing/component'
|
|
15
17
|
require_relative '../../profiling/component'
|
|
16
18
|
require_relative '../../appsec/component'
|
|
@@ -28,6 +30,9 @@ module Datadog
|
|
|
28
30
|
module Configuration
|
|
29
31
|
# Global components for the trace library.
|
|
30
32
|
class Components
|
|
33
|
+
# Class-level constant to ensure fork patch is applied only once
|
|
34
|
+
AT_FORK_ONLY_ONCE = Utils::OnlyOnce.new
|
|
35
|
+
|
|
31
36
|
class << self
|
|
32
37
|
def build_health_metrics(settings, logger, telemetry)
|
|
33
38
|
settings = settings.health_metrics
|
|
@@ -38,7 +43,7 @@ module Datadog
|
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def build_logger(settings)
|
|
41
|
-
logger = settings.logger.instance || Core::Logger.new($
|
|
46
|
+
logger = settings.logger.instance || Core::Logger.new($stderr)
|
|
42
47
|
logger.level = settings.diagnostics.debug ? ::Logger::DEBUG : settings.logger.level
|
|
43
48
|
|
|
44
49
|
logger
|
|
@@ -80,14 +85,15 @@ module Datadog
|
|
|
80
85
|
Datadog::Core::Crashtracking::Component.build(settings, agent_settings, logger: logger)
|
|
81
86
|
end
|
|
82
87
|
|
|
83
|
-
def build_data_streams(settings, agent_settings, logger)
|
|
88
|
+
def build_data_streams(settings, agent_settings, logger, agent_info)
|
|
84
89
|
return unless settings.data_streams.enabled
|
|
85
90
|
|
|
86
91
|
Datadog::DataStreams::Processor.new(
|
|
87
92
|
interval: settings.data_streams.interval,
|
|
88
93
|
logger: logger,
|
|
89
94
|
settings: settings,
|
|
90
|
-
agent_settings: agent_settings
|
|
95
|
+
agent_settings: agent_settings,
|
|
96
|
+
agent_info: agent_info
|
|
91
97
|
)
|
|
92
98
|
rescue => e
|
|
93
99
|
logger.warn("Failed to initialize Data Streams Monitoring: #{e.class}: #{e}")
|
|
@@ -121,6 +127,17 @@ module Datadog
|
|
|
121
127
|
StableConfig.log_result(@logger)
|
|
122
128
|
Deprecations.log_deprecations_from_all_sources(@logger)
|
|
123
129
|
|
|
130
|
+
# Register fork handling once globally
|
|
131
|
+
self.class::AT_FORK_ONLY_ONCE.run do
|
|
132
|
+
Utils::AtForkMonkeyPatch.apply!
|
|
133
|
+
|
|
134
|
+
# Register callback that calls Components.after_fork
|
|
135
|
+
Utils::AtForkMonkeyPatch.at_fork(:child) do
|
|
136
|
+
# Access via global to avoid capturing 'self'
|
|
137
|
+
Datadog.send(:components, allow_initialization: false)&.after_fork
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
124
141
|
# This agent_settings is intended for use within Core. If you require
|
|
125
142
|
# agent_settings within a product outside of core you should extend
|
|
126
143
|
# the Core resolver from within your product/component's namespace.
|
|
@@ -150,13 +167,21 @@ module Datadog
|
|
|
150
167
|
@open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
|
|
151
168
|
@dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
|
|
152
169
|
@error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
|
|
153
|
-
@data_streams = self.class.build_data_streams(settings, agent_settings, @logger)
|
|
170
|
+
@data_streams = self.class.build_data_streams(settings, agent_settings, @logger, @agent_info)
|
|
154
171
|
@environment_logger_extra[:dynamic_instrumentation_enabled] = !!@dynamic_instrumentation
|
|
155
172
|
|
|
156
173
|
# Configure non-privileged components.
|
|
157
174
|
Datadog::Tracing::Contrib::Component.configure(settings)
|
|
158
175
|
end
|
|
159
176
|
|
|
177
|
+
# Called when a fork is detected
|
|
178
|
+
def after_fork
|
|
179
|
+
telemetry.after_fork
|
|
180
|
+
remote&.after_fork
|
|
181
|
+
crashtracker&.update_on_fork
|
|
182
|
+
ProcessDiscovery.after_fork
|
|
183
|
+
end
|
|
184
|
+
|
|
160
185
|
# Hot-swaps with a new sampler.
|
|
161
186
|
# This operation acquires the Components lock to ensure
|
|
162
187
|
# there is no concurrent modification of the sampler.
|
|
@@ -16,8 +16,10 @@ module Datadog
|
|
|
16
16
|
"DD_AI_GUARD_MAX_MESSAGES_LENGTH",
|
|
17
17
|
"DD_AI_GUARD_TIMEOUT",
|
|
18
18
|
"DD_API_KEY",
|
|
19
|
+
"DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE",
|
|
19
20
|
"DD_API_SECURITY_ENABLED",
|
|
20
21
|
"DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED",
|
|
22
|
+
"DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS",
|
|
21
23
|
"DD_API_SECURITY_REQUEST_SAMPLE_RATE",
|
|
22
24
|
"DD_API_SECURITY_SAMPLE_DELAY",
|
|
23
25
|
"DD_APM_TRACING_ENABLED",
|
|
@@ -42,12 +44,14 @@ module Datadog
|
|
|
42
44
|
"DD_APP_KEY",
|
|
43
45
|
"DD_CRASHTRACKING_ENABLED",
|
|
44
46
|
"DD_DATA_STREAMS_ENABLED",
|
|
47
|
+
"DD_DBM_INJECT_SQL_BASEHASH",
|
|
45
48
|
"DD_DBM_PROPAGATION_MODE",
|
|
46
49
|
"DD_DISABLE_DATADOG_RAILS",
|
|
47
50
|
"DD_DYNAMIC_INSTRUMENTATION_ENABLED",
|
|
48
51
|
"DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
|
|
49
52
|
"DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS",
|
|
50
53
|
"DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES",
|
|
54
|
+
"DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS",
|
|
51
55
|
"DD_ENV",
|
|
52
56
|
"DD_ERROR_TRACKING_HANDLED_ERRORS",
|
|
53
57
|
"DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE",
|
|
@@ -284,7 +284,7 @@ module Datadog
|
|
|
284
284
|
# This enables logging during initialization, otherwise we'd run into deadlocks.
|
|
285
285
|
|
|
286
286
|
@temp_logger ||= begin
|
|
287
|
-
logger = configuration.logger.instance || Core::Logger.new($
|
|
287
|
+
logger = configuration.logger.instance || Core::Logger.new($stderr)
|
|
288
288
|
logger.level = configuration.diagnostics.debug ? ::Logger::DEBUG : configuration.logger.level
|
|
289
289
|
logger
|
|
290
290
|
end
|
|
@@ -298,7 +298,7 @@ module Datadog
|
|
|
298
298
|
debug_env_value = DATADOG_ENV[Ext::Diagnostics::ENV_DEBUG_ENABLED]&.strip&.downcase
|
|
299
299
|
debug_value = debug_env_value == 'true' || debug_env_value == '1'
|
|
300
300
|
|
|
301
|
-
logger = Core::Logger.new($
|
|
301
|
+
logger = Core::Logger.new($stderr)
|
|
302
302
|
# We cannot access config and the default level is INFO, so we need to set the level manually
|
|
303
303
|
logger.level = debug_value ? ::Logger::DEBUG : ::Logger::INFO
|
|
304
304
|
logger
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require 'libdatadog'
|
|
4
4
|
|
|
5
5
|
require_relative 'tag_builder'
|
|
6
|
-
require_relative '../utils/only_once'
|
|
7
|
-
require_relative '../utils/at_fork_monkey_patch'
|
|
8
6
|
|
|
9
7
|
module Datadog
|
|
10
8
|
module Core
|
|
@@ -18,10 +16,8 @@ module Datadog
|
|
|
18
16
|
#
|
|
19
17
|
# Methods prefixed with _native_ are implemented in `crashtracker.c`
|
|
20
18
|
class Component
|
|
21
|
-
ONLY_ONCE = Core::Utils::OnlyOnce.new
|
|
22
|
-
|
|
23
19
|
def self.build(settings, agent_settings, logger:)
|
|
24
|
-
tags =
|
|
20
|
+
tags = latest_tags(settings)
|
|
25
21
|
agent_base_url = agent_settings.url
|
|
26
22
|
|
|
27
23
|
ld_library_path = ::Libdatadog.ld_library_path
|
|
@@ -45,6 +41,32 @@ module Datadog
|
|
|
45
41
|
).tap(&:start)
|
|
46
42
|
end
|
|
47
43
|
|
|
44
|
+
# Reports unhandled exceptions to the crash tracker if available and appropriate.
|
|
45
|
+
# This is called from the at_exit hook to report unhandled exceptions.
|
|
46
|
+
def self.report_unhandled_exception(exception)
|
|
47
|
+
return unless exception && !exception.is_a?(SystemExit) && !exception.is_a?(NoMemoryError)
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
crashtracker = Datadog.send(:components, allow_initialization: false)&.crashtracker
|
|
51
|
+
return unless crashtracker
|
|
52
|
+
|
|
53
|
+
crashtracker.report_unhandled_exception(exception)
|
|
54
|
+
rescue => e
|
|
55
|
+
# Unhandled exception report triggering means that the application is already in a bad state
|
|
56
|
+
# We don't want to swallow non-StandardError exceptions here; we would rather just let the
|
|
57
|
+
# application crash
|
|
58
|
+
Datadog.logger.debug("Crashtracker failed to report unhandled exception: #{e.message}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Gets the latest tags from the current configuration.
|
|
63
|
+
#
|
|
64
|
+
# We always fetch fresh tags because:
|
|
65
|
+
# After forking, we need the latest tags, not the parent's tags, such as the pid or runtime-id
|
|
66
|
+
def self.latest_tags(settings)
|
|
67
|
+
TagBuilder.call(settings)
|
|
68
|
+
end
|
|
69
|
+
|
|
48
70
|
def initialize(tags:, agent_base_url:, ld_library_path:, path_to_crashtracking_receiver_binary:, logger:)
|
|
49
71
|
@tags = tags
|
|
50
72
|
@agent_base_url = agent_base_url
|
|
@@ -54,24 +76,62 @@ module Datadog
|
|
|
54
76
|
end
|
|
55
77
|
|
|
56
78
|
def start
|
|
57
|
-
Utils::AtForkMonkeyPatch.apply!
|
|
58
|
-
|
|
59
79
|
start_or_update_on_fork(action: :start, tags: tags)
|
|
60
|
-
|
|
61
|
-
ONLY_ONCE.run do
|
|
62
|
-
Utils::AtForkMonkeyPatch.at_fork(:child) do
|
|
63
|
-
# Must NOT reference `self` here, as only the first instance will
|
|
64
|
-
# be captured by the ONLY_ONCE and we want to pick the latest active one
|
|
65
|
-
# (which may have different tags or agent config)
|
|
66
|
-
Datadog.send(:components, allow_initialization: false)&.crashtracker&.update_on_fork
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
80
|
end
|
|
70
81
|
|
|
71
82
|
def update_on_fork(settings: Datadog.configuration)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
start_or_update_on_fork(action: :update_on_fork, tags: self.class.latest_tags(settings))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def report_unhandled_exception(exception, settings: Datadog.configuration)
|
|
87
|
+
# Maximum number of stack frames to include in exception crash reports
|
|
88
|
+
# This is the same number used for signal-based crashtracking's runtime stack
|
|
89
|
+
max_exception_stack_frames = 512
|
|
90
|
+
|
|
91
|
+
current_tags = self.class.latest_tags(settings)
|
|
92
|
+
# extract all frame data upfront; c expects exactly 3 elements, proper types, no nils
|
|
93
|
+
# limit to max_exception_stack_frames frames
|
|
94
|
+
all_backtrace_locations = exception.backtrace_locations || []
|
|
95
|
+
was_truncated = all_backtrace_locations.length > max_exception_stack_frames
|
|
96
|
+
|
|
97
|
+
backtrace_slice = all_backtrace_locations[0...max_exception_stack_frames] || []
|
|
98
|
+
# @type var frames_data: Array[[String, String, Integer]]
|
|
99
|
+
frames_data = backtrace_slice.map do |loc|
|
|
100
|
+
file = loc.path
|
|
101
|
+
file = '<unknown>' if file.nil? || file.empty? || !file.is_a?(String)
|
|
102
|
+
|
|
103
|
+
function = loc.label
|
|
104
|
+
function = '<unknown>' if function.nil? || function.empty? || !function.is_a?(String)
|
|
105
|
+
|
|
106
|
+
line = loc.lineno
|
|
107
|
+
line = 0 if line.nil? || line < 0 || !line.is_a?(Integer)
|
|
108
|
+
|
|
109
|
+
[file, function, line]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Add truncation indicator frame if we had to cut off frames
|
|
113
|
+
if was_truncated
|
|
114
|
+
truncated_count = all_backtrace_locations.length - max_exception_stack_frames
|
|
115
|
+
frames_data << ['<truncated>', "<truncated #{truncated_count} more frames>", 0]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
exception_message = exception.message
|
|
119
|
+
message =
|
|
120
|
+
if exception_message && !exception_message.empty?
|
|
121
|
+
"Process was terminated due to an unhandled exception of type '#{exception.class}'. Message: \"#{exception_message}\""
|
|
122
|
+
else
|
|
123
|
+
"Process was terminated due to an unhandled exception of type '#{exception.class}'."
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
success = self.class._native_report_ruby_exception(
|
|
127
|
+
agent_base_url,
|
|
128
|
+
message,
|
|
129
|
+
frames_data,
|
|
130
|
+
current_tags.to_a,
|
|
131
|
+
Datadog::VERSION::STRING
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
logger.debug('Crashtracker failed to report unhandled exception to crash tracker') unless success
|
|
75
135
|
end
|
|
76
136
|
|
|
77
137
|
def stop
|
|
@@ -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
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../utils/fnv'
|
|
4
|
+
require_relative 'process'
|
|
5
|
+
|
|
3
6
|
module Datadog
|
|
4
7
|
module Core
|
|
5
8
|
module Environment
|
|
@@ -59,19 +62,80 @@ module Datadog
|
|
|
59
62
|
@client = Remote::Transport::HTTP.root(agent_settings: agent_settings, logger: logger)
|
|
60
63
|
end
|
|
61
64
|
|
|
62
|
-
# Fetches the information from the
|
|
65
|
+
# Fetches the information from the Trace Agent.
|
|
63
66
|
# @return [Datadog::Core::Remote::Transport::HTTP::Negotiation::Response] the response from the agent
|
|
64
67
|
# @return [nil] if an error occurred while fetching the information
|
|
65
68
|
def fetch
|
|
66
69
|
res = @client.send_info
|
|
67
70
|
return unless res.ok?
|
|
68
71
|
|
|
72
|
+
update_propagation_checksum(res)
|
|
73
|
+
|
|
69
74
|
res
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
def ==(other)
|
|
73
78
|
other.is_a?(self.class) && other.agent_settings == agent_settings
|
|
74
79
|
end
|
|
80
|
+
|
|
81
|
+
# Returns the propagation checksum, comprising of process tags and optionally container tags (from the Trace Agent)
|
|
82
|
+
# Currently called/used by the DBM code to inject the propagation checksum into the SQL comment.
|
|
83
|
+
#
|
|
84
|
+
# This checksum is used for correlation across signals (traces, DBM, data streams, etc.) in environments
|
|
85
|
+
#
|
|
86
|
+
# The checksum is populated by the trace transport's periodic fetch calls.
|
|
87
|
+
# @return [Integer, nil] the FNV hash based on the container and process tags or nil
|
|
88
|
+
attr_reader :propagation_checksum
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# Trace Agent 7.69.0+ provides a SHA256 checksum in the response header DATADOG-CONTAINER-TAGS-HASH based on the container id computed in Datadog::Core::Environment::Container
|
|
93
|
+
# This is a short checksum that uniquely identifies this process, its container environment, and
|
|
94
|
+
# the Datadog agent it connects to.
|
|
95
|
+
#
|
|
96
|
+
# This checksum only has to be internally consistent: the same value must be used by every signal
|
|
97
|
+
# emitted by this process+container+agent combinations). It is not required that this checksum is
|
|
98
|
+
# consistent with other SDKs.
|
|
99
|
+
# During calls to the Trace Agent, this checksum is cached but invalidated if a new value is returned
|
|
100
|
+
# The resulting propagation_checksum uses the container_tags_checksum
|
|
101
|
+
# https://github.com/DataDog/datadog-agent/pull/38515
|
|
102
|
+
attr_reader :container_tags_checksum
|
|
103
|
+
|
|
104
|
+
# Setting DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED to true enables the following behavior:
|
|
105
|
+
# - Computes a propagation checksum from process tags and optionally container tags when tags change
|
|
106
|
+
# - This is needed in traces (dsm and dbm related spans), DBM, and DSM.
|
|
107
|
+
#
|
|
108
|
+
# Container tags extraction:
|
|
109
|
+
# Datadog::Core::Environment::Container extracts the container id from the cgroup folder if possible
|
|
110
|
+
# (note: not currently available in cgroupv2) and sends it to the Trace Agent via the header Datadog-Container-ID.
|
|
111
|
+
# The Trace Agent takes the container id and looks for matching container tags to compute a SHA256 checksum via the response header DATADOG-CONTAINER-TAGS-HASH
|
|
112
|
+
# https://github.com/DataDog/datadog-agent/blob/c923da011c8e51c35c0d05b6b10d016521915e7d/pkg/trace/api/info.go#L203-L227
|
|
113
|
+
#
|
|
114
|
+
# When deciding whether the propagation checksum should be updated, we need to be aware of some concerns:
|
|
115
|
+
# - The tracer fails to send the container id in the first place
|
|
116
|
+
# - It is possible that older Trace Agents may not have this specific header
|
|
117
|
+
# - It is possible that we don't have access to the value if the Trace Agent is temporarily down. In these cases, we need to check for the value again on the next call to the info endpoint
|
|
118
|
+
# - If we have access to the value, we need to check if it changed from the previous value.
|
|
119
|
+
# - The Trace Agent runs into a permissions/setup issue.
|
|
120
|
+
def update_propagation_checksum(res)
|
|
121
|
+
return unless Datadog.configuration.experimental_propagate_process_tags_enabled
|
|
122
|
+
|
|
123
|
+
header_value = res.headers[Core::Transport::Ext::HTTP::HEADER_CONTAINER_TAGS_HASH]
|
|
124
|
+
new_container_tags_checksum = header_value if header_value && !header_value.empty?
|
|
125
|
+
|
|
126
|
+
# if the Trace Agent returns a new value for the checksum, calculate and cache the propagation checksum
|
|
127
|
+
# If there was no previous propagation_checksum, then we should calculate the checksum by checking the agent and getting process info
|
|
128
|
+
if @propagation_checksum.nil? || (new_container_tags_checksum && new_container_tags_checksum != @container_tags_checksum)
|
|
129
|
+
@container_tags_checksum = new_container_tags_checksum
|
|
130
|
+
|
|
131
|
+
checksum_input = Process.serialized
|
|
132
|
+
# Use local variable for Steep type narrowing (helps Steep with type narrowing)
|
|
133
|
+
container_checksum = @container_tags_checksum
|
|
134
|
+
checksum_input += container_checksum if container_checksum
|
|
135
|
+
|
|
136
|
+
@propagation_checksum = Core::Utils::FNV.fnv1_64(checksum_input)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
75
139
|
end
|
|
76
140
|
end
|
|
77
141
|
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
|
data/lib/datadog/core/logger.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Datadog
|
|
|
12
12
|
attr_accessor :logger
|
|
13
13
|
|
|
14
14
|
def initialize(logger = nil)
|
|
15
|
-
@logger = logger || Logger.new($
|
|
15
|
+
@logger = logger || Logger.new($stderr).tap do |l|
|
|
16
16
|
l.level = ::Logger::INFO
|
|
17
17
|
l.progname = nil
|
|
18
18
|
l.formatter = proc do |_severity, datetime, _progname, msg|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative '
|
|
6
|
-
require_relative 'utils/only_once'
|
|
3
|
+
require_relative 'process_discovery/tracer_memfd'
|
|
4
|
+
require_relative 'environment/process'
|
|
5
|
+
require_relative 'environment/container'
|
|
7
6
|
|
|
8
7
|
module Datadog
|
|
9
8
|
module Core
|
|
10
9
|
# Class used to store tracer metadata in a native file descriptor.
|
|
11
10
|
module ProcessDiscovery
|
|
12
|
-
ONLY_ONCE = Core::Utils::OnlyOnce.new
|
|
13
|
-
|
|
14
11
|
class << self
|
|
15
12
|
def publish(settings)
|
|
16
13
|
if (libdatadog_api_failure = Datadog::Core::LIBDATADOG_API_FAILURE)
|
|
@@ -18,8 +15,6 @@ module Datadog
|
|
|
18
15
|
return
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
ONLY_ONCE.run { apply_at_fork_patch }
|
|
22
|
-
|
|
23
18
|
metadata = get_metadata(settings)
|
|
24
19
|
|
|
25
20
|
shutdown!
|
|
@@ -27,8 +22,15 @@ module Datadog
|
|
|
27
22
|
end
|
|
28
23
|
|
|
29
24
|
def shutdown!
|
|
30
|
-
@file_descriptor
|
|
31
|
-
|
|
25
|
+
if defined?(@file_descriptor)
|
|
26
|
+
@file_descriptor&.shutdown!(Datadog.logger)
|
|
27
|
+
@file_descriptor = nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def after_fork
|
|
32
|
+
# The runtime-id changes after a fork. We call publish to ensure that the runtime-id is updated.
|
|
33
|
+
publish(Datadog.configuration)
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
private
|
|
@@ -44,17 +46,11 @@ module Datadog
|
|
|
44
46
|
service_name: settings.service || '',
|
|
45
47
|
service_env: settings.env || '',
|
|
46
48
|
service_version: settings.version || '',
|
|
47
|
-
#
|
|
48
|
-
process_tags: '',
|
|
49
|
-
container_id: ''
|
|
49
|
+
# Follows Java: https://github.com/DataDog/dd-trace-java/blob/2ebc964340ac530342cc389ba68ff0f5070d5f9f/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java#L37-L38
|
|
50
|
+
process_tags: settings.experimental_propagate_process_tags_enabled ? Core::Environment::Process.serialized : '',
|
|
51
|
+
container_id: Core::Environment::Container.container_id || ''
|
|
50
52
|
}
|
|
51
53
|
end
|
|
52
|
-
|
|
53
|
-
def apply_at_fork_patch
|
|
54
|
-
# The runtime-id changes after a fork. We apply this patch to at_fork to ensure that the runtime-id is updated.
|
|
55
|
-
Utils::AtForkMonkeyPatch.apply!
|
|
56
|
-
Utils::AtForkMonkeyPatch.at_fork(:child) { publish(Datadog.configuration) }
|
|
57
|
-
end
|
|
58
54
|
end
|
|
59
55
|
end
|
|
60
56
|
end
|