ddtrace 1.5.2 → 1.6.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 +44 -1
- data/ext/ddtrace_profiling_loader/ddtrace_profiling_loader.c +9 -2
- data/ext/ddtrace_profiling_loader/extconf.rb +17 -0
- data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +38 -2
- data/ext/ddtrace_profiling_native_extension/clock_id.h +1 -0
- data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +1 -0
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +517 -42
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +3 -0
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +208 -30
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +156 -46
- data/ext/ddtrace_profiling_native_extension/collectors_stack.h +11 -2
- data/ext/ddtrace_profiling_native_extension/extconf.rb +11 -1
- data/ext/ddtrace_profiling_native_extension/http_transport.c +83 -64
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +4 -4
- data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +3 -2
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +59 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +10 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +0 -1
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +4 -2
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +45 -29
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +7 -7
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +4 -0
- data/lib/datadog/appsec/event.rb +6 -0
- data/lib/datadog/core/configuration/components.rb +20 -14
- data/lib/datadog/core/configuration/settings.rb +42 -4
- data/lib/datadog/core/diagnostics/environment_logger.rb +5 -1
- data/lib/datadog/core/utils/compression.rb +5 -1
- data/lib/datadog/core.rb +0 -54
- data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +12 -2
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -3
- data/lib/datadog/profiling/exporter.rb +2 -4
- data/lib/datadog/profiling/http_transport.rb +1 -1
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +4 -0
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +3 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -0
- data/lib/datadog/tracing/contrib/ext.rb +6 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +5 -0
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +7 -1
- data/lib/datadog/tracing/contrib/grpc/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +47 -0
- data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +22 -0
- data/lib/datadog/tracing/contrib/hanami/ext.rb +24 -0
- data/lib/datadog/tracing/contrib/hanami/integration.rb +44 -0
- data/lib/datadog/tracing/contrib/hanami/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/hanami/plugin.rb +23 -0
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +41 -0
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +44 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +2 -0
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +7 -0
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +4 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +12 -0
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -0
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +12 -0
- data/lib/datadog/tracing/contrib/pg/ext.rb +2 -1
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +34 -18
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +43 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +32 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +49 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +11 -5
- data/lib/datadog/tracing/contrib/redis/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/redis/instrumentation.rb +4 -2
- data/lib/datadog/tracing/contrib/redis/patcher.rb +41 -0
- data/lib/datadog/tracing/contrib/redis/tags.rb +5 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +2 -0
- data/lib/datadog/tracing/contrib/sinatra/env.rb +12 -23
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +7 -3
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +8 -80
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +14 -9
- data/lib/datadog/tracing/contrib.rb +1 -0
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +84 -0
- data/lib/datadog/tracing/distributed/headers/datadog.rb +122 -30
- data/lib/datadog/tracing/distributed/headers/ext.rb +2 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/ext.rb +8 -0
- data/lib/datadog/tracing/propagation/http.rb +9 -1
- data/lib/datadog/tracing/sampling/ext.rb +31 -0
- data/lib/datadog/tracing/sampling/priority_sampler.rb +46 -4
- data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +8 -9
- data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +29 -5
- data/lib/datadog/tracing/sampling/rate_sampler.rb +10 -3
- data/lib/datadog/tracing/sampling/rule_sampler.rb +4 -3
- data/lib/datadog/tracing/sampling/span/ext.rb +0 -4
- data/lib/datadog/tracing/sampling/span/rule.rb +1 -1
- data/lib/datadog/tracing/sampling/span/sampler.rb +14 -3
- data/lib/datadog/tracing/trace_digest.rb +3 -0
- data/lib/datadog/tracing/trace_operation.rb +10 -0
- data/lib/datadog/tracing/trace_segment.rb +6 -0
- data/lib/datadog/tracing/tracer.rb +3 -1
- data/lib/datadog/tracing/writer.rb +7 -0
- data/lib/ddtrace/transport/trace_formatter.rb +7 -0
- data/lib/ddtrace/transport/traces.rb +1 -1
- data/lib/ddtrace/version.rb +2 -2
- metadata +18 -14
- data/lib/datadog/profiling/old_ext.rb +0 -42
- data/lib/datadog/profiling/transport/http/api/endpoint.rb +0 -85
- data/lib/datadog/profiling/transport/http/api/instance.rb +0 -38
- data/lib/datadog/profiling/transport/http/api/spec.rb +0 -42
- data/lib/datadog/profiling/transport/http/api.rb +0 -45
- data/lib/datadog/profiling/transport/http/builder.rb +0 -30
- data/lib/datadog/profiling/transport/http/client.rb +0 -37
- data/lib/datadog/profiling/transport/http/response.rb +0 -21
- data/lib/datadog/profiling/transport/http.rb +0 -118
|
@@ -25,10 +25,12 @@ module Datadog
|
|
|
25
25
|
def call(env)
|
|
26
26
|
# Set the trace context (e.g. distributed tracing)
|
|
27
27
|
if configuration[:distributed_tracing] && Tracing.active_trace.nil?
|
|
28
|
-
original_trace = Propagation::HTTP.extract(env)
|
|
28
|
+
original_trace = Tracing::Propagation::HTTP.extract(env)
|
|
29
29
|
Tracing.continue_trace!(original_trace)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
return @app.call(env) if Sinatra::Env.datadog_span(env)
|
|
33
|
+
|
|
32
34
|
Tracing.trace(
|
|
33
35
|
Ext::SPAN_REQUEST,
|
|
34
36
|
service: configuration[:service_name],
|
|
@@ -39,7 +41,7 @@ module Datadog
|
|
|
39
41
|
# the nil signals that there's no good one yet and is also seen by profiler, when sampling the resource
|
|
40
42
|
span.resource = nil
|
|
41
43
|
|
|
42
|
-
Sinatra::Env.set_datadog_span(env,
|
|
44
|
+
Sinatra::Env.set_datadog_span(env, span)
|
|
43
45
|
|
|
44
46
|
response = @app.call(env)
|
|
45
47
|
ensure
|
|
@@ -51,14 +53,18 @@ module Datadog
|
|
|
51
53
|
span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST)
|
|
52
54
|
|
|
53
55
|
request = ::Sinatra::Request.new(env)
|
|
56
|
+
|
|
54
57
|
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, request.path)
|
|
55
58
|
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, request.request_method)
|
|
59
|
+
|
|
60
|
+
datadog_route = Sinatra::Env.route_path(env)
|
|
61
|
+
|
|
62
|
+
span.set_tag(Ext::TAG_ROUTE_PATH, datadog_route) if datadog_route
|
|
63
|
+
|
|
56
64
|
if request.script_name && !request.script_name.empty?
|
|
57
65
|
span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
|
|
58
66
|
end
|
|
59
67
|
|
|
60
|
-
span.set_tag(Ext::TAG_APP_NAME, @app_instance.settings.name)
|
|
61
|
-
|
|
62
68
|
# If this app handled the request, then Contrib::Sinatra::Tracer OR Contrib::Sinatra::Base set the
|
|
63
69
|
# resource; if no resource was set, let's use a fallback
|
|
64
70
|
span.resource = env['REQUEST_METHOD'] if span.resource.nil?
|
|
@@ -79,7 +85,10 @@ module Datadog
|
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
if (headers = response[1])
|
|
82
|
-
Sinatra::Headers.response_header_tags(
|
|
88
|
+
Sinatra::Headers.response_header_tags(
|
|
89
|
+
headers,
|
|
90
|
+
configuration[:headers][:response]
|
|
91
|
+
).each do |name, value|
|
|
83
92
|
span.set_tag(name, value) if span.get_tag(name).nil?
|
|
84
93
|
end
|
|
85
94
|
end
|
|
@@ -111,10 +120,6 @@ module Datadog
|
|
|
111
120
|
def configuration
|
|
112
121
|
Datadog.configuration.tracing[:sinatra]
|
|
113
122
|
end
|
|
114
|
-
|
|
115
|
-
def header_to_rack_header(name)
|
|
116
|
-
"HTTP_#{name.to_s.upcase.gsub(/[-\s]/, '_')}"
|
|
117
|
-
end
|
|
118
123
|
end
|
|
119
124
|
end
|
|
120
125
|
end
|
|
@@ -49,6 +49,7 @@ require_relative 'contrib/faraday/integration'
|
|
|
49
49
|
require_relative 'contrib/grape/integration'
|
|
50
50
|
require_relative 'contrib/graphql/integration'
|
|
51
51
|
require_relative 'contrib/grpc/integration'
|
|
52
|
+
require_relative 'contrib/hanami/integration'
|
|
52
53
|
require_relative 'contrib/http/integration'
|
|
53
54
|
require_relative 'contrib/httpclient/integration'
|
|
54
55
|
require_relative 'contrib/httprb/integration'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Tracing
|
|
5
|
+
module Distributed
|
|
6
|
+
# Encodes and decodes distributed 'x-datadog-tags' tags for transport
|
|
7
|
+
# to and from external processes.
|
|
8
|
+
module DatadogTagsCodec
|
|
9
|
+
# Backport `Regexp::match?` because it is measurably the most performant
|
|
10
|
+
# way to check if a string matches a regular expression.
|
|
11
|
+
module RefineRegexp
|
|
12
|
+
unless Regexp.method_defined?(:match?)
|
|
13
|
+
refine ::Regexp do
|
|
14
|
+
def match?(*args)
|
|
15
|
+
!match(*args).nil?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
using RefineRegexp
|
|
21
|
+
|
|
22
|
+
# ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
|
|
23
|
+
VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
|
|
24
|
+
# ASCII characters 32-126, except `,`. At least one character.
|
|
25
|
+
VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
|
|
26
|
+
|
|
27
|
+
# Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
|
|
28
|
+
# String.
|
|
29
|
+
#
|
|
30
|
+
# @param tags [Hash<String,String>] trace tag hash
|
|
31
|
+
# @return [String] serialized tags hash
|
|
32
|
+
# @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
|
|
33
|
+
def self.encode(tags)
|
|
34
|
+
begin
|
|
35
|
+
tags.map do |raw_key, raw_value|
|
|
36
|
+
key = raw_key.to_s
|
|
37
|
+
value = raw_value.to_s
|
|
38
|
+
|
|
39
|
+
raise EncodingError, "Invalid key `#{key}` for value `#{value}`" unless VALID_KEY_CHARS.match?(key)
|
|
40
|
+
raise EncodingError, "Invalid value `#{value}` for key `#{key}`" unless VALID_VALUE_CHARS.match?(value)
|
|
41
|
+
|
|
42
|
+
"#{key}=#{value.strip}"
|
|
43
|
+
end.join(',')
|
|
44
|
+
rescue => e
|
|
45
|
+
raise EncodingError, "Error encoding tags `#{tags}`: `#{e}`"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
|
|
50
|
+
#
|
|
51
|
+
# @param string [String] tags as serialized by {#encode}
|
|
52
|
+
# @return [Hash<String,String>] decoded input as a hash of strings
|
|
53
|
+
# @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
|
|
54
|
+
def self.decode(string)
|
|
55
|
+
result = Hash[string.split(',').map do |raw_tag|
|
|
56
|
+
raw_tag.split('=', 2).tap do |raw_key, raw_value|
|
|
57
|
+
key = raw_key.to_s
|
|
58
|
+
value = raw_value.to_s
|
|
59
|
+
|
|
60
|
+
raise DecodingError, "Invalid key: #{key}" unless VALID_KEY_CHARS.match?(key)
|
|
61
|
+
raise DecodingError, "Invalid value: #{value}" unless VALID_VALUE_CHARS.match?(value)
|
|
62
|
+
|
|
63
|
+
value.strip!
|
|
64
|
+
end
|
|
65
|
+
end]
|
|
66
|
+
|
|
67
|
+
raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
|
|
68
|
+
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# An error occurred during distributed tags encoding.
|
|
73
|
+
# See {#message} for more information.
|
|
74
|
+
class EncodingError < StandardError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# An error occurred during distributed tags decoding.
|
|
78
|
+
# See {#message} for more information.
|
|
79
|
+
class DecodingError < StandardError
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
# typed: true
|
|
2
3
|
|
|
3
4
|
require_relative 'parser'
|
|
4
5
|
require_relative 'ext'
|
|
6
|
+
require_relative '../../metadata/ext'
|
|
5
7
|
require_relative '../../trace_digest'
|
|
8
|
+
require_relative '../datadog_tags_codec'
|
|
6
9
|
|
|
7
10
|
module Datadog
|
|
8
11
|
module Tracing
|
|
@@ -10,40 +13,129 @@ module Datadog
|
|
|
10
13
|
module Headers
|
|
11
14
|
# Datadog provides helpers to inject or extract headers for Datadog style headers
|
|
12
15
|
module Datadog
|
|
13
|
-
|
|
16
|
+
class << self
|
|
17
|
+
include Ext
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
def inject!(digest, env)
|
|
20
|
+
return if digest.nil?
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
env[HTTP_HEADER_TRACE_ID] = digest.trace_id.to_s
|
|
23
|
+
env[HTTP_HEADER_PARENT_ID] = digest.span_id.to_s
|
|
24
|
+
env[HTTP_HEADER_SAMPLING_PRIORITY] = digest.trace_sampling_priority.to_s if digest.trace_sampling_priority
|
|
25
|
+
env[HTTP_HEADER_ORIGIN] = digest.trace_origin.to_s unless digest.trace_origin.nil?
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
inject_tags(digest, env)
|
|
28
|
+
|
|
29
|
+
env
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def extract(env)
|
|
33
|
+
# Extract values from headers
|
|
34
|
+
headers = Parser.new(env)
|
|
35
|
+
trace_id = headers.id(HTTP_HEADER_TRACE_ID)
|
|
36
|
+
parent_id = headers.id(HTTP_HEADER_PARENT_ID)
|
|
37
|
+
origin = headers.header(HTTP_HEADER_ORIGIN)
|
|
38
|
+
sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
|
|
39
|
+
|
|
40
|
+
# Return early if this propagation is not valid
|
|
41
|
+
# DEV: To be valid we need to have a trace id and a parent id
|
|
42
|
+
# or when it is a synthetics trace, just the trace id.
|
|
43
|
+
# DEV: `Parser#id` will not return 0
|
|
44
|
+
return unless (trace_id && parent_id) || (origin && trace_id)
|
|
45
|
+
|
|
46
|
+
trace_distributed_tags = extract_tags(headers)
|
|
47
|
+
|
|
48
|
+
# Return new trace headers
|
|
49
|
+
TraceDigest.new(
|
|
50
|
+
span_id: parent_id,
|
|
51
|
+
trace_id: trace_id,
|
|
52
|
+
trace_origin: origin,
|
|
53
|
+
trace_sampling_priority: sampling_priority,
|
|
54
|
+
trace_distributed_tags: trace_distributed_tags,
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Export trace distributed tags through the `x-datadog-tags` header.
|
|
61
|
+
#
|
|
62
|
+
# DEV: This method accesses global state (the active trace) to record its error state as a trace tag.
|
|
63
|
+
# DEV: This means errors cannot be reported if there's not active span.
|
|
64
|
+
# DEV: Ideally, we'd have a dedicated error reporting stream for all of ddtrace.
|
|
65
|
+
# DEV: The same comment applies to the {.extract_tags}.
|
|
66
|
+
def inject_tags(digest, env)
|
|
67
|
+
return if digest.trace_distributed_tags.nil? || digest.trace_distributed_tags.empty?
|
|
68
|
+
|
|
69
|
+
if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
|
|
70
|
+
active_trace = Tracing.active_trace
|
|
71
|
+
active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
encoded_tags = DatadogTagsCodec.encode(digest.trace_distributed_tags)
|
|
76
|
+
|
|
77
|
+
if encoded_tags.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
|
|
78
|
+
active_trace = Tracing.active_trace
|
|
79
|
+
active_trace.set_tag('_dd.propagation_error', 'inject_max_size') if active_trace
|
|
80
|
+
|
|
81
|
+
::Datadog.logger.warn(
|
|
82
|
+
"Failed to inject x-datadog-tags: tags are too large (size:#{encoded_tags.size} " \
|
|
83
|
+
"limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
|
|
84
|
+
'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
env[HTTP_HEADER_TAGS] = encoded_tags
|
|
90
|
+
rescue => e
|
|
91
|
+
active_trace = Tracing.active_trace
|
|
92
|
+
active_trace.set_tag('_dd.propagation_error', 'encoding_error') if active_trace
|
|
93
|
+
::Datadog.logger.warn(
|
|
94
|
+
"Failed to inject x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Import `x-datadog-tags` header tags as trace distributed tags.
|
|
99
|
+
# Only tags that have the `_dd.p.` prefix are processed.
|
|
100
|
+
def extract_tags(headers)
|
|
101
|
+
tags_header = headers.header(HTTP_HEADER_TAGS)
|
|
102
|
+
return unless tags_header
|
|
103
|
+
|
|
104
|
+
if ::Datadog.configuration.tracing.x_datadog_tags_max_length <= 0
|
|
105
|
+
active_trace = Tracing.active_trace
|
|
106
|
+
active_trace.set_tag('_dd.propagation_error', 'disabled') if active_trace
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if tags_header.size > ::Datadog.configuration.tracing.x_datadog_tags_max_length
|
|
111
|
+
active_trace = Tracing.active_trace
|
|
112
|
+
active_trace.set_tag('_dd.propagation_error', 'extract_max_size') if active_trace
|
|
113
|
+
|
|
114
|
+
::Datadog.logger.warn(
|
|
115
|
+
"Failed to extract x-datadog-tags: tags are too large (size:#{tags_header.size} " \
|
|
116
|
+
"limit:#{::Datadog.configuration.tracing.x_datadog_tags_max_length}). This limit can be configured " \
|
|
117
|
+
'through the DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH environment variable.'
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
tags = DatadogTagsCodec.decode(tags_header)
|
|
123
|
+
# Only extract keys with the expected Datadog prefix
|
|
124
|
+
tags.select! do |key, _|
|
|
125
|
+
key.start_with?(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX) && key != EXCLUDED_TAG
|
|
126
|
+
end
|
|
127
|
+
tags
|
|
128
|
+
rescue => e
|
|
129
|
+
active_trace = Tracing.active_trace
|
|
130
|
+
active_trace.set_tag('_dd.propagation_error', 'decoding_error') if active_trace
|
|
131
|
+
::Datadog.logger.warn(
|
|
132
|
+
"Failed to extract x-datadog-tags: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
|
|
133
|
+
)
|
|
134
|
+
end
|
|
25
135
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
trace_id = headers.id(HTTP_HEADER_TRACE_ID)
|
|
30
|
-
parent_id = headers.id(HTTP_HEADER_PARENT_ID)
|
|
31
|
-
origin = headers.header(HTTP_HEADER_ORIGIN)
|
|
32
|
-
sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY)
|
|
33
|
-
|
|
34
|
-
# Return early if this propagation is not valid
|
|
35
|
-
# DEV: To be valid we need to have a trace id and a parent id
|
|
36
|
-
# or when it is a synthetics trace, just the trace id.
|
|
37
|
-
# DEV: `Parser#id` will not return 0
|
|
38
|
-
return unless (trace_id && parent_id) || (origin && trace_id)
|
|
39
|
-
|
|
40
|
-
# Return new trace headers
|
|
41
|
-
TraceDigest.new(
|
|
42
|
-
span_id: parent_id,
|
|
43
|
-
trace_id: trace_id,
|
|
44
|
-
trace_origin: origin,
|
|
45
|
-
trace_sampling_priority: sampling_priority
|
|
46
|
-
)
|
|
136
|
+
# We want to exclude tags that we don't want to propagate downstream.
|
|
137
|
+
EXCLUDED_TAG = '_dd.p.upstream_services'
|
|
138
|
+
private_constant :EXCLUDED_TAG
|
|
47
139
|
end
|
|
48
140
|
end
|
|
49
141
|
end
|
|
@@ -12,6 +12,8 @@ module Datadog
|
|
|
12
12
|
HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze
|
|
13
13
|
HTTP_HEADER_SAMPLING_PRIORITY = 'x-datadog-sampling-priority'.freeze
|
|
14
14
|
HTTP_HEADER_ORIGIN = 'x-datadog-origin'.freeze
|
|
15
|
+
# Distributed trace-level tags
|
|
16
|
+
HTTP_HEADER_TAGS = 'x-datadog-tags'.freeze
|
|
15
17
|
|
|
16
18
|
# B3 headers used for distributed tracing
|
|
17
19
|
B3_HEADER_TRACE_ID = 'x-b3-traceid'.freeze
|
|
@@ -48,7 +48,7 @@ module Datadog
|
|
|
48
48
|
# Single Span Sampling has chosen to keep this span
|
|
49
49
|
# regardless of the trace-level sampling decision
|
|
50
50
|
def single_sampled?(span)
|
|
51
|
-
span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::
|
|
51
|
+
span.get_metric(Sampling::Span::Ext::TAG_MECHANISM) == Sampling::Ext::Mechanism::SPAN_SAMPLING_RATE
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -45,8 +45,16 @@ module Datadog
|
|
|
45
45
|
# @public_api
|
|
46
46
|
# Tags related to distributed tracing
|
|
47
47
|
module Distributed
|
|
48
|
+
# What mechanism was used to make this trace's sampling decision.
|
|
49
|
+
# @see Datadog::Tracing::Sampling::Ext::Mechanism
|
|
50
|
+
TAG_DECISION_MAKER = '_dd.p.dm'
|
|
51
|
+
|
|
48
52
|
TAG_ORIGIN = '_dd.origin'
|
|
49
53
|
TAG_SAMPLING_PRIORITY = '_sampling_priority_v1'
|
|
54
|
+
|
|
55
|
+
# Trace tags with this prefix will propagate from a trace through distributed tracing.
|
|
56
|
+
# Distributed headers tags with this prefix will be injected into the active trace.
|
|
57
|
+
TAGS_PREFIX = '_dd.p.'
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
# @public_api
|
|
@@ -23,7 +23,15 @@ module Datadog
|
|
|
23
23
|
Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG => Distributed::Headers::Datadog
|
|
24
24
|
}.freeze
|
|
25
25
|
|
|
26
|
-
# inject!
|
|
26
|
+
# inject! populates the env with span ID, trace ID and sampling priority
|
|
27
|
+
#
|
|
28
|
+
# DEV-2.0: inject! should work without arguments, injecting the active_trace's digest
|
|
29
|
+
# DEV-2.0: and returning a new Hash with the injected headers.
|
|
30
|
+
# DEV-2.0: inject! should also accept either a `trace` or a `digest`, as a `trace`
|
|
31
|
+
# DEV-2.0: argument is the common use case, but also allows us to set error tags in the `trace`
|
|
32
|
+
# DEV-2.0: if needed.
|
|
33
|
+
# DEV-2.0: Ideally, we'd have a separate stream to report tracer errors and never
|
|
34
|
+
# DEV-2.0: touch the active span.
|
|
27
35
|
def self.inject!(digest, env)
|
|
28
36
|
# Prevent propagation from being attempted if trace headers provided are nil.
|
|
29
37
|
if digest.nil?
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# typed: strict
|
|
2
4
|
|
|
3
5
|
module Datadog
|
|
@@ -21,6 +23,35 @@ module Datadog
|
|
|
21
23
|
# through the {Datadog::Tracing::Sampling::RuleSampler}.
|
|
22
24
|
USER_KEEP = 2
|
|
23
25
|
end
|
|
26
|
+
|
|
27
|
+
# List of what mechanism was used to make the trace-level sampling decision.
|
|
28
|
+
module Mechanism
|
|
29
|
+
# Single Span Sampled.
|
|
30
|
+
SPAN_SAMPLING_RATE = 8
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# List of how the decision was made for the trace-level sampling.
|
|
34
|
+
#
|
|
35
|
+
# These values used to populate the {Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER} tag.
|
|
36
|
+
#
|
|
37
|
+
# The decision has two parts, separated by a `-`:
|
|
38
|
+
# `part1-sampling_mechanism`. `part1` is currently not populated, thus
|
|
39
|
+
# this tag is currently formatted as `"-sampling_mechanism"`.
|
|
40
|
+
module Decision
|
|
41
|
+
# Used before the tracer receives any rates from agent and there are no rules configured.
|
|
42
|
+
DEFAULT = '-0'
|
|
43
|
+
# The sampling rate received in the agent's http response.
|
|
44
|
+
AGENT_RATE = '-1'
|
|
45
|
+
# Sampling rule or sampling rate based on tracer config.
|
|
46
|
+
TRACE_SAMPLING_RULE = '-3'
|
|
47
|
+
# User directly sets sampling priority via {Tracing.reject!} or {Tracing.keep!},
|
|
48
|
+
# or by a custom sampler implementation.
|
|
49
|
+
MANUAL = '-4'
|
|
50
|
+
# Formerly AppSec.
|
|
51
|
+
ASM = '-5'
|
|
52
|
+
# Single Span Sampled.
|
|
53
|
+
SPAN_SAMPLING_RATE = '-8'
|
|
54
|
+
end
|
|
24
55
|
end
|
|
25
56
|
end
|
|
26
57
|
end
|
|
@@ -21,17 +21,26 @@ module Datadog
|
|
|
21
21
|
|
|
22
22
|
def initialize(opts = {})
|
|
23
23
|
@pre_sampler = opts[:base_sampler] || AllSampler.new
|
|
24
|
-
@priority_sampler = opts[:post_sampler] || RateByServiceSampler.new
|
|
24
|
+
@priority_sampler = opts[:post_sampler] || RateByServiceSampler.new(decision: Sampling::Ext::Decision::AGENT_RATE)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def sample?(trace)
|
|
28
28
|
@pre_sampler.sample?(trace)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# DEV-2.0:We should get rid of this complicated interaction between @pre_sampler and @priority_sampler.
|
|
32
|
+
# DEV-2.0:If the user wants to configure a custom sampler, we should only allow them to provide a complete
|
|
33
|
+
# DEV-2.0:sampling suite, not having this convoluted support for mixing arbitrary provided samplers in
|
|
34
|
+
# DEV-2.0:the PrioritySampler. Ideally, the PrioritySampler is only used by Datadog.
|
|
35
|
+
# DEV-2.0:There are too many edge cases and combinations to work around currently in this class.
|
|
31
36
|
def sample!(trace)
|
|
37
|
+
# The priority that was set before the sampler ran.
|
|
38
|
+
# This comes from distributed tracing priority propagation.
|
|
39
|
+
distributed_sampling_priority = priority_assigned?(trace)
|
|
40
|
+
|
|
32
41
|
# If pre-sampling is configured, do it first. (By default, this will sample at 100%.)
|
|
33
42
|
# NOTE: Pre-sampling at rates < 100% may result in partial traces; not recommended.
|
|
34
|
-
trace.sampled = pre_sample?(trace) ? @pre_sampler.sample!(trace) : true
|
|
43
|
+
trace.sampled = pre_sample?(trace) ? preserving_priority_sampling(trace) { @pre_sampler.sample!(trace) } : true
|
|
35
44
|
|
|
36
45
|
if trace.sampled?
|
|
37
46
|
# If priority sampling has already been applied upstream, use that value.
|
|
@@ -53,11 +62,27 @@ module Datadog
|
|
|
53
62
|
end
|
|
54
63
|
|
|
55
64
|
trace.sampled?
|
|
65
|
+
ensure
|
|
66
|
+
if trace.sampling_priority && trace.sampling_priority > 0
|
|
67
|
+
# Don't modify decision if priority was set upstream.
|
|
68
|
+
if !distributed_sampling_priority && !trace.has_tag?(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
|
69
|
+
# If no sampling priority being assigned at this point, a custom
|
|
70
|
+
# sampler implementation is configured: this means the user has
|
|
71
|
+
# full control over the sampling decision.
|
|
72
|
+
trace.set_tag(
|
|
73
|
+
Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
|
|
74
|
+
Sampling::Ext::Decision::MANUAL
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
# The sampler decided to not keep this span, removing sampling decision.
|
|
79
|
+
trace.clear_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
|
80
|
+
end
|
|
56
81
|
end
|
|
57
82
|
|
|
58
83
|
# (see Datadog::Tracing::Sampling::RateByServiceSampler#update)
|
|
59
|
-
def update(rate_by_service)
|
|
60
|
-
@priority_sampler.update(rate_by_service)
|
|
84
|
+
def update(rate_by_service, decision: nil)
|
|
85
|
+
@priority_sampler.update(rate_by_service, decision: decision)
|
|
61
86
|
end
|
|
62
87
|
|
|
63
88
|
private
|
|
@@ -83,6 +108,23 @@ module Datadog
|
|
|
83
108
|
end
|
|
84
109
|
end
|
|
85
110
|
|
|
111
|
+
# Ensures the trace's priority sampling decision is not changed by the @pre_sampler.
|
|
112
|
+
# The @pre_sampler should only change `trace.sampled`.
|
|
113
|
+
def preserving_priority_sampling(trace)
|
|
114
|
+
sampling_priority = trace.sampling_priority
|
|
115
|
+
sampling_decision = trace.get_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
|
116
|
+
|
|
117
|
+
yield.tap do
|
|
118
|
+
trace.sampling_priority = sampling_priority
|
|
119
|
+
|
|
120
|
+
if sampling_decision
|
|
121
|
+
trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, sampling_decision)
|
|
122
|
+
else
|
|
123
|
+
trace.clear_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
86
128
|
# Ensures the trace is always propagated to the writer and that
|
|
87
129
|
# the sample rate metric represents the true client-side sampling.
|
|
88
130
|
def preserving_sampling(trace)
|
|
@@ -12,7 +12,7 @@ module Datadog
|
|
|
12
12
|
attr_reader \
|
|
13
13
|
:default_key
|
|
14
14
|
|
|
15
|
-
def initialize(default_key, default_rate = 1.0, &block)
|
|
15
|
+
def initialize(default_key, default_rate = 1.0, decision: nil, &block)
|
|
16
16
|
super()
|
|
17
17
|
|
|
18
18
|
raise ArgumentError, 'No resolver given!' unless block
|
|
@@ -22,7 +22,7 @@ module Datadog
|
|
|
22
22
|
@mutex = Mutex.new
|
|
23
23
|
@samplers = {}
|
|
24
24
|
|
|
25
|
-
set_rate(default_key, default_rate)
|
|
25
|
+
set_rate(default_key, default_rate, decision)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def resolve(trace)
|
|
@@ -57,15 +57,15 @@ module Datadog
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def update(key, rate)
|
|
60
|
+
def update(key, rate, decision: nil)
|
|
61
61
|
@mutex.synchronize do
|
|
62
|
-
set_rate(key, rate)
|
|
62
|
+
set_rate(key, rate, decision)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def update_all(rate_by_key)
|
|
66
|
+
def update_all(rate_by_key, decision: nil)
|
|
67
67
|
@mutex.synchronize do
|
|
68
|
-
rate_by_key.each { |key, rate| set_rate(key, rate) }
|
|
68
|
+
rate_by_key.each { |key, rate| set_rate(key, rate, decision) }
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -87,9 +87,8 @@ module Datadog
|
|
|
87
87
|
|
|
88
88
|
private
|
|
89
89
|
|
|
90
|
-
def set_rate(key, rate)
|
|
91
|
-
@samplers[key]
|
|
92
|
-
@samplers[key].sample_rate = rate
|
|
90
|
+
def set_rate(key, rate, decision)
|
|
91
|
+
@samplers[key] = RateSampler.new(rate, decision: decision)
|
|
93
92
|
end
|
|
94
93
|
end
|
|
95
94
|
end
|
|
@@ -11,17 +11,23 @@ module Datadog
|
|
|
11
11
|
class RateByServiceSampler < RateByKeySampler
|
|
12
12
|
DEFAULT_KEY = 'service:,env:'.freeze
|
|
13
13
|
|
|
14
|
-
def initialize(default_rate = 1.0,
|
|
15
|
-
super(
|
|
16
|
-
|
|
14
|
+
def initialize(default_rate = 1.0, env: nil, decision: Datadog::Tracing::Sampling::Ext::Decision::DEFAULT)
|
|
15
|
+
super(
|
|
16
|
+
DEFAULT_KEY,
|
|
17
|
+
default_rate,
|
|
18
|
+
decision: decision,
|
|
19
|
+
&method(:key_for)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@env = env
|
|
17
23
|
end
|
|
18
24
|
|
|
19
|
-
def update(rate_by_service)
|
|
25
|
+
def update(rate_by_service, decision: nil)
|
|
20
26
|
# Remove any old services
|
|
21
27
|
delete_if { |key, _| key != DEFAULT_KEY && !rate_by_service.key?(key) }
|
|
22
28
|
|
|
23
29
|
# Update each service rate
|
|
24
|
-
update_all(rate_by_service)
|
|
30
|
+
update_all(rate_by_service, decision: decision)
|
|
25
31
|
|
|
26
32
|
# Emit metric for service cache size
|
|
27
33
|
Datadog.health_metrics.sampling_service_cache_length(length)
|
|
@@ -29,6 +35,24 @@ module Datadog
|
|
|
29
35
|
|
|
30
36
|
private
|
|
31
37
|
|
|
38
|
+
# DEV: Creating a string on every trace to perform a single Hash lookup is expensive.
|
|
39
|
+
#
|
|
40
|
+
# Using 2 nested hashes: 1 for env and 1 for service is the fastest option.
|
|
41
|
+
# This approach requires large API changes to `RateByKeySampler`.
|
|
42
|
+
#
|
|
43
|
+
# Reducing the interpolated string size, by using a 1 character separator,
|
|
44
|
+
# is also measurably faster than the current method. This approach does not
|
|
45
|
+
# require changes to `RateByKeySampler`.
|
|
46
|
+
#
|
|
47
|
+
# Keep in mind that these changes also require changes to `#update`.
|
|
48
|
+
#
|
|
49
|
+
# Comparison:
|
|
50
|
+
# 2 nested hashes: `service_hash.fetch(service, {}).fetch(env, default_rate)`
|
|
51
|
+
# 7730045 i/s
|
|
52
|
+
# 1 char separator: `hash.fetch("#{service}\0#{env}", default_rate)`
|
|
53
|
+
# 4302801 i/s - 1.80x slower
|
|
54
|
+
# current: `hash.fetch("service:#{service},env:#{env}", default_rate)`
|
|
55
|
+
# 2720459 i/s - 2.84x slower
|
|
32
56
|
def key_for(trace)
|
|
33
57
|
# Resolve env dynamically, if Proc is given.
|
|
34
58
|
env = @env.is_a?(Proc) ? @env.call : @env
|
|
@@ -30,7 +30,7 @@ module Datadog
|
|
|
30
30
|
# DEV-2.0: sampler = RateSampler.new
|
|
31
31
|
# DEV-2.0: sampler.sample_rate = sample_rate
|
|
32
32
|
# DEV-2.0: ```
|
|
33
|
-
def initialize(sample_rate = 1.0)
|
|
33
|
+
def initialize(sample_rate = 1.0, decision: nil)
|
|
34
34
|
super()
|
|
35
35
|
|
|
36
36
|
unless sample_rate > 0.0 && sample_rate <= 1.0
|
|
@@ -39,6 +39,8 @@ module Datadog
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
self.sample_rate = sample_rate
|
|
42
|
+
|
|
43
|
+
@decision = decision
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def sample_rate(*_)
|
|
@@ -56,8 +58,13 @@ module Datadog
|
|
|
56
58
|
|
|
57
59
|
def sample!(trace)
|
|
58
60
|
sampled = trace.sampled = sample?(trace)
|
|
59
|
-
|
|
60
|
-
sampled
|
|
61
|
+
|
|
62
|
+
return false unless sampled
|
|
63
|
+
|
|
64
|
+
trace.sample_rate = @sample_rate
|
|
65
|
+
trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, @decision) if @decision
|
|
66
|
+
|
|
67
|
+
true
|
|
61
68
|
end
|
|
62
69
|
end
|
|
63
70
|
end
|