datadog 2.33.0 → 2.34.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 +30 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +20 -0
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/lib/datadog/appsec/component.rb +4 -1
- data/lib/datadog/appsec/compressed_json.rb +2 -2
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
- data/lib/datadog/core/configuration/components.rb +8 -1
- data/lib/datadog/core/configuration/settings.rb +6 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +10 -0
- data/lib/datadog/core/environment/ext.rb +4 -0
- data/lib/datadog/core/environment/identity.rb +15 -1
- data/lib/datadog/core/environment/process.rb +48 -27
- data/lib/datadog/core/remote/client/capabilities.rb +11 -2
- data/lib/datadog/core/remote/transport/http/config.rb +5 -5
- data/lib/datadog/core/telemetry/request.rb +0 -2
- data/lib/datadog/core/transport/response.rb +1 -1
- data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +3 -2
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +46 -16
- data/lib/datadog/data_streams/pathway_context.rb +3 -3
- data/lib/datadog/di/code_tracker.rb +43 -22
- data/lib/datadog/di/contrib/active_record.rb +6 -2
- data/lib/datadog/di/instrumenter.rb +24 -4
- data/lib/datadog/di/probe_notification_builder.rb +1 -1
- data/lib/datadog/di/remote.rb +4 -4
- data/lib/datadog/di/serializer.rb +5 -5
- data/lib/datadog/di/utils.rb +42 -14
- data/lib/datadog/opentelemetry/configuration/settings.rb +65 -0
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +10 -46
- data/lib/datadog/opentelemetry/sdk/configurator.rb +40 -0
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +37 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
- data/lib/datadog/opentelemetry.rb +1 -0
- data/lib/datadog/symbol_database/component.rb +409 -0
- data/lib/datadog/symbol_database/configuration.rb +2 -2
- data/lib/datadog/symbol_database/extractor.rb +29 -1
- data/lib/datadog/symbol_database/remote.rb +175 -0
- data/lib/datadog/symbol_database/scope_batcher.rb +8 -0
- data/lib/datadog/symbol_database/service_version.rb +11 -2
- data/lib/datadog/symbol_database/symbol.rb +6 -3
- data/lib/datadog/symbol_database/uploader.rb +62 -8
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +8 -0
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +0 -4
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +0 -4
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +0 -4
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +0 -5
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +0 -5
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +0 -8
- data/lib/datadog/tracing/contrib/excon/middleware.rb +0 -5
- data/lib/datadog/tracing/contrib/ext.rb +2 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +0 -5
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +0 -5
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +0 -5
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +0 -5
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +0 -5
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +0 -5
- data/lib/datadog/tracing/contrib/racecar/event.rb +0 -5
- data/lib/datadog/tracing/contrib/redis/tags.rb +0 -5
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +0 -5
- data/lib/datadog/tracing/contrib/sequel/utils.rb +0 -5
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +0 -5
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -13
- data/lib/datadog/tracing/distributed/trace_context.rb +0 -28
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/span_operation.rb +13 -0
- data/lib/datadog/tracing/trace_operation.rb +22 -0
- data/lib/datadog/tracing/tracer.rb +6 -0
- data/lib/datadog/version.rb +1 -1
- metadata +12 -5
|
@@ -9,6 +9,10 @@ module Datadog
|
|
|
9
9
|
module SymbolDatabase
|
|
10
10
|
# Extracts symbol metadata from loaded Ruby modules and classes via introspection.
|
|
11
11
|
#
|
|
12
|
+
# Instance created by Component with injected dependencies (logger, settings).
|
|
13
|
+
# All methods are instance methods accessing @logger, @settings directly —
|
|
14
|
+
# no parameter threading needed.
|
|
15
|
+
#
|
|
12
16
|
# Uses Ruby's reflection APIs (Module#constants, Class#instance_methods, Method#parameters)
|
|
13
17
|
# to build hierarchical Scope structures representing code organization.
|
|
14
18
|
# Filters to user code only (excludes gems, stdlib, test files).
|
|
@@ -80,6 +84,11 @@ module Datadog
|
|
|
80
84
|
MODULE_SINGLETON_CLASS_PRED = Module.instance_method(:singleton_class?)
|
|
81
85
|
private_constant :MODULE_SINGLETON_CLASS_PRED
|
|
82
86
|
|
|
87
|
+
# Cached UnboundMethod for Module#name — avoids resolving it on every
|
|
88
|
+
# safe_mod_name call. Some classes override .name (e.g. Faker::Travel::Airport),
|
|
89
|
+
# so we bind the original Module#name to get the real module name safely.
|
|
90
|
+
MODULE_NAME = Module.instance_method(:name)
|
|
91
|
+
|
|
83
92
|
# @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
|
|
84
93
|
# @param settings [Configuration::Settings] Tracer settings
|
|
85
94
|
def initialize(logger:, settings:)
|
|
@@ -150,7 +159,7 @@ module Datadog
|
|
|
150
159
|
# @param mod [Module] The module
|
|
151
160
|
# @return [String, nil] Module name or nil
|
|
152
161
|
def safe_mod_name(mod)
|
|
153
|
-
|
|
162
|
+
MODULE_NAME.bind(mod).call
|
|
154
163
|
rescue => e
|
|
155
164
|
@logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
|
|
156
165
|
nil
|
|
@@ -605,10 +614,26 @@ module Datadog
|
|
|
605
614
|
|
|
606
615
|
# ── extract_all helpers ──────────────────────────────────────────────
|
|
607
616
|
|
|
617
|
+
# Sleep between chunks of modules processed in collect_extractable_modules so
|
|
618
|
+
# request-handling threads have guaranteed CPU time while extraction is in
|
|
619
|
+
# flight. Unlike Thread.pass (which only offers the GVL among runnable
|
|
620
|
+
# threads and leaves the extractor immediately re-runnable), sleep removes
|
|
621
|
+
# the extractor thread from the runnable set for a fixed duration, capping
|
|
622
|
+
# its CPU share at sleep_work_ratio regardless of GVL scheduling.
|
|
623
|
+
#
|
|
624
|
+
# The cadence is measured in modules that pass the singleton-class fast-path
|
|
625
|
+
# skip — singleton classes are discarded in microseconds and counting them
|
|
626
|
+
# would add wall-clock delay disproportionate to the work being done (e.g.
|
|
627
|
+
# on heavily monkey-patched processes that retain large singleton chains).
|
|
628
|
+
SLEEP_EVERY_N_MODULES = 100
|
|
629
|
+
SLEEP_SECONDS = 0.001
|
|
630
|
+
private_constant :SLEEP_EVERY_N_MODULES, :SLEEP_SECONDS
|
|
631
|
+
|
|
608
632
|
# Pass 1: Collect all extractable modules with methods grouped by source file.
|
|
609
633
|
# @return [Hash] { mod_name => { mod:, methods_by_file: { path => [{name:, method:, type:}] } } }
|
|
610
634
|
def collect_extractable_modules
|
|
611
635
|
entries = {}
|
|
636
|
+
seen = 0
|
|
612
637
|
|
|
613
638
|
ObjectSpace.each_object(Module) do |mod|
|
|
614
639
|
# Singleton classes (per-object metaclasses) are never user-code classes.
|
|
@@ -620,6 +645,9 @@ module Datadog
|
|
|
620
645
|
# processes. Ruby 2.7+ optimized this path; the skip is a no-op there.
|
|
621
646
|
next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
|
|
622
647
|
|
|
648
|
+
seen += 1
|
|
649
|
+
sleep SLEEP_SECONDS if (seen % SLEEP_EVERY_N_MODULES).zero?
|
|
650
|
+
|
|
623
651
|
mod_name = safe_mod_name(mod)
|
|
624
652
|
next unless mod_name
|
|
625
653
|
next unless user_code_module?(mod)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module SymbolDatabase
|
|
5
|
+
# Provides remote configuration integration for symbol database.
|
|
6
|
+
#
|
|
7
|
+
# Responsibilities:
|
|
8
|
+
# - Registers with Core::Remote as a receiver for LIVE_DEBUGGING_SYMBOL_DB product
|
|
9
|
+
# - Processes remote config changes (insert/update/delete)
|
|
10
|
+
# - Calls Component.start_upload when upload_symbols: true
|
|
11
|
+
# - Calls Component.stop_upload when config deleted or upload_symbols: false
|
|
12
|
+
#
|
|
13
|
+
# Flow:
|
|
14
|
+
# 1. Remote config system calls receiver with repository and changes
|
|
15
|
+
# 2. For each change, process_change called
|
|
16
|
+
# 3. parse_config extracts upload_symbols flag
|
|
17
|
+
# 4. enable_upload or disable_upload called on component
|
|
18
|
+
#
|
|
19
|
+
# Created by: Symbol database initialization
|
|
20
|
+
# Accessed by: Core::Remote system when configurations change
|
|
21
|
+
# Requires: Component must exist (accessed via Datadog.send(:components).symbol_database)
|
|
22
|
+
#
|
|
23
|
+
# @api private
|
|
24
|
+
module Remote
|
|
25
|
+
PRODUCT = 'LIVE_DEBUGGING_SYMBOL_DB'
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# Declare products this receiver handles.
|
|
29
|
+
# @return [Array<String>] Product names
|
|
30
|
+
def products
|
|
31
|
+
[PRODUCT]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Declare capabilities for this receiver.
|
|
35
|
+
# @return [Array] Capabilities (none for symbol database)
|
|
36
|
+
def capabilities
|
|
37
|
+
[]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Create receivers for remote configuration.
|
|
41
|
+
# @return [Array<Receiver>] Array of receivers
|
|
42
|
+
def receivers(_telemetry)
|
|
43
|
+
receiver do |repository, changes|
|
|
44
|
+
telemetry = lookup_telemetry
|
|
45
|
+
component = begin
|
|
46
|
+
Datadog.send(:components, allow_initialization: false)&.symbol_database
|
|
47
|
+
rescue => e
|
|
48
|
+
Datadog.logger.debug { "symdb: failed to look up component in RC receiver: #{e.class}: #{e.message}" }
|
|
49
|
+
telemetry&.report(e, description: 'symdb: failed to look up component in RC receiver')
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if component
|
|
54
|
+
changes.each do |change|
|
|
55
|
+
process_change(component, change, telemetry)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a single receiver for the product.
|
|
62
|
+
# @param products [Array<String>] Product names to match
|
|
63
|
+
# @return [Array<Receiver>] Receiver array
|
|
64
|
+
def receiver(products = [PRODUCT], &block)
|
|
65
|
+
matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
|
|
66
|
+
[Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Look up the telemetry component for error reporting. Returns nil if the
|
|
72
|
+
# component tree isn't built yet (very early boot) or the lookup raises.
|
|
73
|
+
# `allow_initialization: false` avoids triggering component-tree construction
|
|
74
|
+
# from inside an RC receiver callback.
|
|
75
|
+
# @return [Core::Telemetry::Component, nil]
|
|
76
|
+
# @api private
|
|
77
|
+
def lookup_telemetry
|
|
78
|
+
Datadog.send(:components, allow_initialization: false)&.telemetry
|
|
79
|
+
rescue
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Process a single configuration change.
|
|
84
|
+
# @param component [Component] Symbol database component
|
|
85
|
+
# @param change [Change] Configuration change (:insert, :update, :delete)
|
|
86
|
+
# @param telemetry [Core::Telemetry::Component, nil] Telemetry for error reporting
|
|
87
|
+
# @return [void]
|
|
88
|
+
# @api private
|
|
89
|
+
def process_change(component, change, telemetry)
|
|
90
|
+
case change.type
|
|
91
|
+
when :insert
|
|
92
|
+
# @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Inserted
|
|
93
|
+
enable_upload(component, change.content)
|
|
94
|
+
change.content.applied
|
|
95
|
+
when :update
|
|
96
|
+
# @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Updated
|
|
97
|
+
disable_upload(component)
|
|
98
|
+
enable_upload(component, change.content)
|
|
99
|
+
change.content.applied
|
|
100
|
+
when :delete
|
|
101
|
+
# @type var change: ::Datadog::Core::Remote::Configuration::Repository::Change::Deleted
|
|
102
|
+
disable_upload(component)
|
|
103
|
+
change.previous&.applied
|
|
104
|
+
else
|
|
105
|
+
component.logger.debug { "symdb: unrecognized change type: #{change.type}" }
|
|
106
|
+
# Steep cannot narrow `change.content` from a respond_to? check — it sees
|
|
107
|
+
# the Repository::Change union type where `Deleted` lacks `content`.
|
|
108
|
+
change.content.errored("Unrecognized change type: #{change.type}") if change.respond_to?(:content) # steep:ignore NoMethod
|
|
109
|
+
end
|
|
110
|
+
rescue => e
|
|
111
|
+
component.logger.debug { "symdb: error processing remote config change: #{e.class}: #{e.message}" }
|
|
112
|
+
telemetry&.report(e, description: 'symdb: error processing remote config change')
|
|
113
|
+
# Rescue runs regardless of which branch raised — Steep cannot narrow the
|
|
114
|
+
# union type from a respond_to? check.
|
|
115
|
+
content_obj = change.respond_to?(:content) ? change.content : change.previous # steep:ignore NoMethod
|
|
116
|
+
content_obj&.errored(e.to_s)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Enable upload if config has upload_symbols: true.
|
|
120
|
+
# @param component [Component] Symbol database component
|
|
121
|
+
# @param content [Content] Remote config content
|
|
122
|
+
# @return [void]
|
|
123
|
+
# @api private
|
|
124
|
+
def enable_upload(component, content)
|
|
125
|
+
config = parse_config(content, component.logger)
|
|
126
|
+
|
|
127
|
+
unless config
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if config['upload_symbols']
|
|
132
|
+
component.logger.debug { "symdb: upload enabled via remote config" }
|
|
133
|
+
component.start_upload
|
|
134
|
+
else
|
|
135
|
+
component.logger.debug { "symdb: upload disabled in config" }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Disable upload.
|
|
140
|
+
# @param component [Component] Symbol database component
|
|
141
|
+
# @return [void]
|
|
142
|
+
# @api private
|
|
143
|
+
def disable_upload(component)
|
|
144
|
+
component.logger.debug { "symdb: upload disabled via remote config" }
|
|
145
|
+
component.stop_upload
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Parse and validate remote config content.
|
|
149
|
+
# @param content [Content] Remote config content
|
|
150
|
+
# @param logger [SymbolDatabase::Logger] Logger for invalid-config diagnostics
|
|
151
|
+
# @return [Hash, nil] Parsed config or nil if invalid
|
|
152
|
+
# @api private
|
|
153
|
+
#
|
|
154
|
+
# JSON::ParserError is intentionally NOT rescued here — it propagates to
|
|
155
|
+
# process_change's rescue, which logs and reports to telemetry. Catching
|
|
156
|
+
# it locally would swallow the error from telemetry observability.
|
|
157
|
+
def parse_config(content, logger)
|
|
158
|
+
config = JSON.parse(content.data)
|
|
159
|
+
|
|
160
|
+
unless config.is_a?(Hash)
|
|
161
|
+
logger.debug { "symdb: invalid config format: expected Hash, got #{config.class}" }
|
|
162
|
+
return nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
unless config.key?('upload_symbols')
|
|
166
|
+
logger.debug { "symdb: missing 'upload_symbols' key in config" }
|
|
167
|
+
return nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
config
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -91,6 +91,14 @@ module Datadog
|
|
|
91
91
|
return
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
# Marked uploaded before perform_upload runs. This is intentional:
|
|
95
|
+
# symdb extraction is a one-shot operation per process — extract_all
|
|
96
|
+
# walks ObjectSpace once and never revisits a module within the same
|
|
97
|
+
# process. The Set deduplicates within a single extraction run, not
|
|
98
|
+
# across upload attempts. Failed uploads are not retried; symbols
|
|
99
|
+
# from a failed batch are lost until the next process restart, by
|
|
100
|
+
# design (matches Python and Go; Java retries via OkHttp, .NET via
|
|
101
|
+
# exponential backoff — Ruby does neither).
|
|
94
102
|
@uploaded_modules.add(scope.name)
|
|
95
103
|
@file_count += 1
|
|
96
104
|
|
|
@@ -16,15 +16,18 @@ module Datadog
|
|
|
16
16
|
#
|
|
17
17
|
# @api private
|
|
18
18
|
class ServiceVersion
|
|
19
|
-
attr_reader :service, :env, :version, :language, :scopes
|
|
19
|
+
attr_reader :service, :env, :version, :language, :scopes, :upload_id, :batch_num, :final
|
|
20
20
|
|
|
21
21
|
# Initialize a new ServiceVersion
|
|
22
22
|
# @param service [String] Service name (required, from DD_SERVICE)
|
|
23
23
|
# @param env [String, nil] Environment (from DD_ENV, passed through unchanged)
|
|
24
24
|
# @param version [String, nil] Version (from DD_VERSION, passed through unchanged)
|
|
25
25
|
# @param scopes [Array<Scope>] Top-level scopes (required)
|
|
26
|
+
# @param upload_id [String, nil] UUID identifying the logical upload (shared by all batches)
|
|
27
|
+
# @param batch_num [Integer, nil] 1-indexed batch number within the upload
|
|
28
|
+
# @param final [Boolean, nil] true if this is the last batch of the upload
|
|
26
29
|
# @raise [ArgumentError] if service empty or scopes not an array
|
|
27
|
-
def initialize(service:, env:, version:, scopes:)
|
|
30
|
+
def initialize(service:, env:, version:, scopes:, upload_id: nil, batch_num: nil, final: nil)
|
|
28
31
|
raise ArgumentError, 'service is required' if service.nil? || service.empty?
|
|
29
32
|
raise ArgumentError, 'scopes must be an array' unless scopes.is_a?(Array)
|
|
30
33
|
|
|
@@ -33,6 +36,9 @@ module Datadog
|
|
|
33
36
|
@version = version
|
|
34
37
|
@language = 'ruby'
|
|
35
38
|
@scopes = scopes
|
|
39
|
+
@upload_id = upload_id
|
|
40
|
+
@batch_num = batch_num
|
|
41
|
+
@final = final
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
# Convert service version to Hash for JSON serialization.
|
|
@@ -44,6 +50,9 @@ module Datadog
|
|
|
44
50
|
version: version,
|
|
45
51
|
language: language,
|
|
46
52
|
scopes: scopes.map(&:to_h),
|
|
53
|
+
upload_id: upload_id,
|
|
54
|
+
batch_num: batch_num,
|
|
55
|
+
final: final,
|
|
47
56
|
}
|
|
48
57
|
end
|
|
49
58
|
|
|
@@ -42,17 +42,20 @@ module Datadog
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Convert symbol to Hash for JSON serialization.
|
|
45
|
-
#
|
|
45
|
+
# The `type` key is always present per the symdb JSON schema — emit nil
|
|
46
|
+
# when the type cannot be determined (the common case in Ruby since
|
|
47
|
+
# parameters and instance variables carry no declared type).
|
|
48
|
+
# `language_specifics` is omitted when nil to reduce payload size.
|
|
46
49
|
# @return [Hash] Symbol as hash with symbol keys
|
|
47
50
|
def to_h
|
|
51
|
+
# @type var h: Hash[::Symbol, untyped]
|
|
48
52
|
h = {
|
|
49
53
|
symbol_type: symbol_type,
|
|
50
54
|
name: name,
|
|
51
55
|
line: line,
|
|
52
56
|
type: type,
|
|
53
|
-
language_specifics: language_specifics,
|
|
54
57
|
}
|
|
55
|
-
h
|
|
58
|
+
h[:language_specifics] = language_specifics if language_specifics
|
|
56
59
|
h
|
|
57
60
|
end
|
|
58
61
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
4
5
|
require 'zlib'
|
|
5
6
|
require 'stringio'
|
|
6
7
|
require_relative '../core/environment/identity'
|
|
@@ -45,6 +46,12 @@ module Datadog
|
|
|
45
46
|
agent_settings: agent_settings,
|
|
46
47
|
logger: @logger,
|
|
47
48
|
)
|
|
49
|
+
|
|
50
|
+
# Protects the lazy initialization of @upload_pid/@upload_id and the
|
|
51
|
+
# @batch_num increment in next_upload_metadata. ScopeBatcher calls
|
|
52
|
+
# upload_scopes outside its own mutex, so two threads (e.g. a size-
|
|
53
|
+
# triggered flush and the timer/shutdown path) can race here.
|
|
54
|
+
@metadata_mutex = Mutex.new
|
|
48
55
|
end
|
|
49
56
|
|
|
50
57
|
# Upload a batch of scopes to the agent.
|
|
@@ -56,7 +63,8 @@ module Datadog
|
|
|
56
63
|
def upload_scopes(scopes)
|
|
57
64
|
return if scopes.empty?
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
upload_id, batch_num = next_upload_metadata
|
|
67
|
+
json_data = build_symbol_payload(scopes, upload_id: upload_id, batch_num: batch_num)
|
|
60
68
|
compressed_data = Zlib.gzip(json_data)
|
|
61
69
|
|
|
62
70
|
# Emitted unconditionally so the rare oversized case is observable.
|
|
@@ -72,7 +80,7 @@ module Datadog
|
|
|
72
80
|
return
|
|
73
81
|
end
|
|
74
82
|
|
|
75
|
-
perform_http_upload(compressed_data, scopes.size)
|
|
83
|
+
perform_http_upload(compressed_data, scopes.size, upload_id: upload_id, batch_num: batch_num)
|
|
76
84
|
rescue => e
|
|
77
85
|
@logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
|
|
78
86
|
@telemetry&.report(e, description: 'symdb: upload failed')
|
|
@@ -82,31 +90,44 @@ module Datadog
|
|
|
82
90
|
|
|
83
91
|
# Build JSON payload from scopes.
|
|
84
92
|
# @param scopes [Array<Scope>] Scopes to serialize
|
|
93
|
+
# @param upload_id [String] UUID identifying the logical upload
|
|
94
|
+
# @param batch_num [Integer] 1-indexed batch number within the upload
|
|
85
95
|
# @return [String] JSON string
|
|
86
|
-
def build_symbol_payload(scopes)
|
|
96
|
+
def build_symbol_payload(scopes, upload_id:, batch_num:)
|
|
87
97
|
ServiceVersion.new(
|
|
88
98
|
service: @settings.service,
|
|
89
99
|
env: @settings.env,
|
|
90
100
|
version: @settings.version,
|
|
91
101
|
scopes: scopes,
|
|
102
|
+
upload_id: upload_id,
|
|
103
|
+
batch_num: batch_num,
|
|
104
|
+
# Always false: the Ruby tracer continuously uploads new code
|
|
105
|
+
# as files are loaded; there is no defined end-of-upload point.
|
|
106
|
+
final: false,
|
|
92
107
|
).to_json
|
|
93
108
|
end
|
|
94
109
|
|
|
95
110
|
# Perform HTTP POST with multipart form-data via transport layer.
|
|
96
111
|
# @param compressed_data [String] GZIP compressed JSON payload
|
|
97
112
|
# @param scope_count [Integer] Number of scopes (for logging)
|
|
113
|
+
# @param upload_id [String] UUID identifying the logical upload
|
|
114
|
+
# @param batch_num [Integer] 1-indexed batch number within the upload
|
|
98
115
|
# @return [void]
|
|
99
|
-
def perform_http_upload(compressed_data, scope_count)
|
|
100
|
-
form = build_multipart_form(compressed_data)
|
|
116
|
+
def perform_http_upload(compressed_data, scope_count, upload_id:, batch_num:)
|
|
117
|
+
form = build_multipart_form(compressed_data, upload_id: upload_id, batch_num: batch_num)
|
|
101
118
|
response = @transport.send_symbols(form)
|
|
102
119
|
handle_response(response, scope_count)
|
|
103
120
|
end
|
|
104
121
|
|
|
105
122
|
# Build multipart form-data with event metadata and compressed symbols.
|
|
106
123
|
# @param compressed_data [String] GZIP compressed JSON payload
|
|
124
|
+
# @param upload_id [String] UUID identifying the logical upload
|
|
125
|
+
# @param batch_num [Integer] 1-indexed batch number within the upload
|
|
107
126
|
# @return [Hash] Form data hash with UploadIO objects
|
|
108
|
-
def build_multipart_form(compressed_data)
|
|
109
|
-
event_io = StringIO.new(
|
|
127
|
+
def build_multipart_form(compressed_data, upload_id:, batch_num:)
|
|
128
|
+
event_io = StringIO.new(
|
|
129
|
+
build_event_metadata(compressed_data.bytesize, upload_id: upload_id, batch_num: batch_num),
|
|
130
|
+
)
|
|
110
131
|
file_io = StringIO.new(compressed_data)
|
|
111
132
|
|
|
112
133
|
event_upload = Datadog::Core::Vendor::Multipart::Post::UploadIO.new(
|
|
@@ -128,17 +149,50 @@ module Datadog
|
|
|
128
149
|
end
|
|
129
150
|
|
|
130
151
|
# Build event.json metadata part.
|
|
152
|
+
# @param attachment_size [Integer] Size of the compressed attachment in bytes
|
|
153
|
+
# @param upload_id [String] UUID identifying the logical upload
|
|
154
|
+
# @param batch_num [Integer] 1-indexed batch number within the upload
|
|
131
155
|
# @return [String] JSON string for event metadata
|
|
132
|
-
def build_event_metadata
|
|
156
|
+
def build_event_metadata(attachment_size, upload_id:, batch_num:)
|
|
133
157
|
JSON.generate(
|
|
134
158
|
ddsource: 'ruby',
|
|
135
159
|
service: @settings.service,
|
|
160
|
+
version: @settings.version,
|
|
161
|
+
language: 'ruby',
|
|
136
162
|
runtimeId: Datadog::Core::Environment::Identity.id,
|
|
137
163
|
parentId: nil, # Fork tracking deferred for MVP
|
|
138
164
|
type: 'symdb',
|
|
165
|
+
uploadId: upload_id,
|
|
166
|
+
batchNum: batch_num,
|
|
167
|
+
# Always false: the Ruby tracer continuously uploads new code
|
|
168
|
+
# as files are loaded; there is no defined end-of-upload point.
|
|
169
|
+
final: false,
|
|
170
|
+
attachmentSize: attachment_size,
|
|
139
171
|
)
|
|
140
172
|
end
|
|
141
173
|
|
|
174
|
+
# Return [upload_id, batch_num] for the current process. upload_id is
|
|
175
|
+
# generated lazily on first use and shared by all batches uploaded from
|
|
176
|
+
# the process. A forked child observes a different Process.pid and
|
|
177
|
+
# therefore gets a fresh upload_id and batch counter.
|
|
178
|
+
# Synchronized: ScopeBatcher releases its mutex before calling
|
|
179
|
+
# upload_scopes, so this can be entered concurrently.
|
|
180
|
+
# @return [Array<(String, Integer)>]
|
|
181
|
+
def next_upload_metadata
|
|
182
|
+
@metadata_mutex.synchronize do
|
|
183
|
+
if @upload_pid != Process.pid
|
|
184
|
+
@upload_pid = Process.pid
|
|
185
|
+
@upload_id = SecureRandom.uuid.freeze
|
|
186
|
+
@batch_num = 0
|
|
187
|
+
end
|
|
188
|
+
@batch_num += 1 # steep:ignore NoMethod
|
|
189
|
+
# @upload_id and @batch_num are non-nil after the branch above; refine for Steep.
|
|
190
|
+
upload_id = @upload_id or raise('unreachable: @upload_id should be set')
|
|
191
|
+
batch_num = @batch_num or raise('unreachable: @batch_num should be set')
|
|
192
|
+
[upload_id, batch_num]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
142
196
|
# Handle HTTP response and track metrics.
|
|
143
197
|
# @param response [Core::Transport::Response] HTTP response from agent
|
|
144
198
|
# @param scope_count [Integer] Number of scopes uploaded
|
|
@@ -11,6 +11,8 @@ module Datadog
|
|
|
11
11
|
module Instrumentation
|
|
12
12
|
SCRIPT_NAME_KEY = 'SCRIPT_NAME'
|
|
13
13
|
FORMAT_SUFFIX = '(.:format)'
|
|
14
|
+
# NOTE: Rails 8.1.1+ natively provides the same object via 'action_dispatch.route'.
|
|
15
|
+
DATADOG_RAILS_ROUTE_ENV_KEY = 'datadog.action_dispatch.route'
|
|
14
16
|
|
|
15
17
|
module_function
|
|
16
18
|
|
|
@@ -48,6 +50,8 @@ module Datadog
|
|
|
48
50
|
result.each do |_, _, route|
|
|
49
51
|
next unless Instrumentation.dispatcher_route?(route)
|
|
50
52
|
|
|
53
|
+
req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
|
|
54
|
+
|
|
51
55
|
http_route = route.path.spec.to_s
|
|
52
56
|
http_route.delete_suffix!(FORMAT_SUFFIX)
|
|
53
57
|
|
|
@@ -66,6 +70,8 @@ module Datadog
|
|
|
66
70
|
def find_routes(req)
|
|
67
71
|
super do |match, parameters, route|
|
|
68
72
|
if Instrumentation.dispatcher_route?(route)
|
|
73
|
+
req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
|
|
74
|
+
|
|
69
75
|
http_route = route.path.spec.to_s
|
|
70
76
|
http_route.delete_suffix!(FORMAT_SUFFIX)
|
|
71
77
|
|
|
@@ -86,6 +92,8 @@ module Datadog
|
|
|
86
92
|
|
|
87
93
|
super do |route, parameters|
|
|
88
94
|
if Instrumentation.dispatcher_route?(route)
|
|
95
|
+
req.env[DATADOG_RAILS_ROUTE_ENV_KEY] = route
|
|
96
|
+
|
|
89
97
|
http_route = route.path.spec.to_s
|
|
90
98
|
http_route = http_route.delete_suffix(FORMAT_SUFFIX)
|
|
91
99
|
|
|
@@ -48,10 +48,6 @@ module Datadog
|
|
|
48
48
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
49
49
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_SQL)
|
|
50
50
|
|
|
51
|
-
if service_name != Datadog.configuration.service
|
|
52
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
51
|
# Set analytics sample rate
|
|
56
52
|
if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
|
|
57
53
|
Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
|
|
@@ -82,10 +82,6 @@ module Datadog
|
|
|
82
82
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
83
83
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CACHE)
|
|
84
84
|
|
|
85
|
-
if span.service != Datadog.configuration.service
|
|
86
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
85
|
span.set_tag(Ext::TAG_CACHE_BACKEND, cache_backend(store))
|
|
90
86
|
|
|
91
87
|
span.set_tag('EVENT', event)
|
|
@@ -54,10 +54,6 @@ module Datadog
|
|
|
54
54
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
55
55
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CACHE)
|
|
56
56
|
|
|
57
|
-
if span.service != Datadog.configuration.service
|
|
58
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
57
|
span.set_tag(Ext::TAG_CACHE_BACKEND, store) if store
|
|
62
58
|
|
|
63
59
|
set_cache_key(span, key, multi_key) if Datadog.configuration.tracing[:active_support][:cache_key].enabled
|
|
@@ -61,11 +61,6 @@ module Datadog
|
|
|
61
61
|
)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# Tag original global service name if not used
|
|
65
|
-
if span.service != Datadog.configuration.service
|
|
66
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
64
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
70
65
|
|
|
71
66
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
@@ -31,11 +31,6 @@ module Datadog
|
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# Tag original global service name if not used
|
|
35
|
-
if span.service != Datadog.configuration.service
|
|
36
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
34
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
40
35
|
|
|
41
36
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
@@ -66,11 +66,6 @@ module Datadog
|
|
|
66
66
|
)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
# Tag original global service name if not used
|
|
70
|
-
if span.service != Datadog.configuration.service
|
|
71
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
69
|
span.type = Datadog::Tracing::Contrib::Elasticsearch::Ext::SPAN_TYPE_QUERY
|
|
75
70
|
|
|
76
71
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
@@ -144,11 +144,6 @@ module Datadog
|
|
|
144
144
|
)
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
# Tag original global service name if not used
|
|
148
|
-
if span.service != Datadog.configuration.service
|
|
149
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
147
|
# Set analytics sample rate
|
|
153
148
|
Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
|
|
154
149
|
|
|
@@ -69,14 +69,6 @@ module Datadog
|
|
|
69
69
|
|
|
70
70
|
@datadog_multi_span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
71
71
|
|
|
72
|
-
# Tag original global service name if not used
|
|
73
|
-
if @datadog_multi_span.service != Datadog.configuration.service
|
|
74
|
-
@datadog_multi_span.set_tag(
|
|
75
|
-
Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE,
|
|
76
|
-
Datadog.configuration.service
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
72
|
# Set analytics sample rate
|
|
81
73
|
Contrib::Analytics.set_sample_rate(@datadog_multi_span, analytics_sample_rate) if analytics_enabled?
|
|
82
74
|
|
|
@@ -128,11 +128,6 @@ module Datadog
|
|
|
128
128
|
)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
# Tag original global service name if not used
|
|
132
|
-
if span.service != Datadog.configuration.service
|
|
133
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
131
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
137
132
|
|
|
138
133
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../metadata/ext'
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
module Tracing
|
|
5
7
|
module Contrib
|
|
@@ -60,9 +62,6 @@ module Datadog
|
|
|
60
62
|
|
|
61
63
|
# Value of tag from which peer.service value was remapped from
|
|
62
64
|
TAG_PEER_SERVICE_REMAP = '_dd.peer.service.remapped_from'
|
|
63
|
-
|
|
64
|
-
# Set equal to the global service when contrib span.service is overriden
|
|
65
|
-
TAG_BASE_SERVICE = '_dd.base_service'
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
end
|
|
@@ -56,11 +56,6 @@ module Datadog
|
|
|
56
56
|
)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
# Tag original global service name if not used
|
|
60
|
-
if span.service != Datadog.configuration.service
|
|
61
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
59
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
65
60
|
|
|
66
61
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
@@ -59,11 +59,6 @@ module Datadog
|
|
|
59
59
|
)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
# Tag original global service name if not used
|
|
63
|
-
if span.service != Datadog.configuration.service
|
|
64
|
-
span.set_tag(Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE, Datadog.configuration.service)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
62
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
68
63
|
span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
|
|
69
64
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CLIENT)
|