ddtrace 1.5.2 → 1.6.1
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 +56 -2
- 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/integration.rb +2 -1
- data/lib/datadog/tracing/contrib/redis/patcher.rb +40 -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
|
@@ -17,69 +17,12 @@ module Datadog
|
|
|
17
17
|
# Datadog::Tracing::Contrib::Sinatra::Tracer is a Sinatra extension which traces
|
|
18
18
|
# requests.
|
|
19
19
|
module Tracer
|
|
20
|
-
def route(verb, action, *)
|
|
21
|
-
# Keep track of the route name when the app is instantiated for an
|
|
22
|
-
# incoming request.
|
|
23
|
-
condition do
|
|
24
|
-
# If the option to prepend script names is enabled, then
|
|
25
|
-
# prepend the script name from the request onto the action.
|
|
26
|
-
#
|
|
27
|
-
# DEV: env['sinatra.route'] already exists with very similar information,
|
|
28
|
-
# DEV: but doesn't account for our `resource_script_names` logic.
|
|
29
|
-
#
|
|
30
|
-
@datadog_route = if Datadog.configuration.tracing[:sinatra][:resource_script_names]
|
|
31
|
-
"#{request.script_name}#{action}"
|
|
32
|
-
else
|
|
33
|
-
action
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
super
|
|
38
|
-
end
|
|
39
|
-
|
|
40
20
|
def self.registered(app)
|
|
41
21
|
app.use TracerMiddleware, app_instance: app
|
|
42
|
-
|
|
43
|
-
app.after do
|
|
44
|
-
next unless Tracing.enabled?
|
|
45
|
-
|
|
46
|
-
span = Sinatra::Env.datadog_span(env, app)
|
|
47
|
-
|
|
48
|
-
# TODO: `route` should *only* be populated if @datadog_route is defined.
|
|
49
|
-
# TODO: If @datadog_route is not defined, then this Sinatra app is not responsible
|
|
50
|
-
# TODO: for handling this request.
|
|
51
|
-
# TODO:
|
|
52
|
-
# TODO: This change would be BREAKING for any Sinatra app (classic or modular),
|
|
53
|
-
# TODO: as it affects the `resource` value for requests not handled by the Sinatra app.
|
|
54
|
-
# TODO: Currently we use "#{method} #{path}" in such aces, but `path` is the raw,
|
|
55
|
-
# TODO: high-cardinality HTTP path, and can contain PII.
|
|
56
|
-
# TODO:
|
|
57
|
-
# TODO: The value we should use as the `resource` when the Sinatra app is not
|
|
58
|
-
# TODO: responsible for the request is a tricky subject.
|
|
59
|
-
# TODO: The best option is a value that clearly communicates that this app did not
|
|
60
|
-
# TODO: handle this request. It's important to keep in mind that an unhandled request
|
|
61
|
-
# TODO: by this Sinatra app might still be handled by another Rack middleware (which can
|
|
62
|
-
# TODO: be a Sinatra app itself) or it might just 404 if not handled at all.
|
|
63
|
-
# TODO:
|
|
64
|
-
# TODO: A possible value for `resource` could set a high level description, e.g.
|
|
65
|
-
# TODO: `request.request_method`, given we don't have the response object available yet.
|
|
66
|
-
route = if defined?(@datadog_route)
|
|
67
|
-
@datadog_route
|
|
68
|
-
else
|
|
69
|
-
# Fallback in case no routes have matched
|
|
70
|
-
request.path
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
span.resource = "#{request.request_method} #{route}"
|
|
74
|
-
span.set_tag(Ext::TAG_ROUTE_PATH, route)
|
|
75
|
-
end
|
|
76
22
|
end
|
|
77
23
|
|
|
78
24
|
# Method overrides for Sinatra::Base
|
|
79
25
|
module Base
|
|
80
|
-
MISSING_REQUEST_SPAN_ONLY_ONCE = Core::Utils::OnlyOnce.new
|
|
81
|
-
private_constant :MISSING_REQUEST_SPAN_ONLY_ONCE
|
|
82
|
-
|
|
83
26
|
def render(engine, data, *)
|
|
84
27
|
return super unless Tracing.enabled?
|
|
85
28
|
|
|
@@ -102,19 +45,21 @@ module Datadog
|
|
|
102
45
|
|
|
103
46
|
# Invoked when a matching route is found.
|
|
104
47
|
# This method yields directly to user code.
|
|
105
|
-
# rubocop:disable Metrics/MethodLength
|
|
106
48
|
def route_eval
|
|
107
49
|
configuration = Datadog.configuration.tracing[:sinatra]
|
|
108
50
|
return super unless Tracing.enabled?
|
|
109
51
|
|
|
52
|
+
datadog_route = Sinatra::Env.route_path(env)
|
|
53
|
+
|
|
110
54
|
Tracing.trace(
|
|
111
55
|
Ext::SPAN_ROUTE,
|
|
112
56
|
service: configuration[:service_name],
|
|
113
57
|
span_type: Tracing::Metadata::Ext::HTTP::TYPE_INBOUND,
|
|
114
|
-
resource: "#{request.request_method} #{
|
|
58
|
+
resource: "#{request.request_method} #{datadog_route}",
|
|
115
59
|
) do |span, trace|
|
|
116
60
|
span.set_tag(Ext::TAG_APP_NAME, settings.name || settings.superclass.name)
|
|
117
|
-
span.set_tag(Ext::TAG_ROUTE_PATH,
|
|
61
|
+
span.set_tag(Ext::TAG_ROUTE_PATH, datadog_route)
|
|
62
|
+
|
|
118
63
|
if request.script_name && !request.script_name.empty?
|
|
119
64
|
span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name)
|
|
120
65
|
end
|
|
@@ -124,32 +69,15 @@ module Datadog
|
|
|
124
69
|
|
|
125
70
|
trace.resource = span.resource
|
|
126
71
|
|
|
127
|
-
sinatra_request_span =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
else
|
|
131
|
-
Sinatra::Env.datadog_span(env, self.class)
|
|
132
|
-
end
|
|
133
|
-
if sinatra_request_span
|
|
134
|
-
sinatra_request_span.resource = span.resource
|
|
135
|
-
else
|
|
136
|
-
MISSING_REQUEST_SPAN_ONLY_ONCE.run do
|
|
137
|
-
Datadog.logger.warn do
|
|
138
|
-
'Sinatra integration is misconfigured, reported traces will be missing request metadata ' \
|
|
139
|
-
'such as path and HTTP status code. ' \
|
|
140
|
-
'Did you forget to add `register Datadog::Tracing::Contrib::Sinatra::Tracer` to your ' \
|
|
141
|
-
'`Sinatra::Base` subclass? ' \
|
|
142
|
-
'See <https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#sinatra> for more details.'
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
72
|
+
sinatra_request_span = Sinatra::Env.datadog_span(env)
|
|
73
|
+
|
|
74
|
+
sinatra_request_span.resource = span.resource
|
|
146
75
|
|
|
147
76
|
Contrib::Analytics.set_measured(span)
|
|
148
77
|
|
|
149
78
|
super
|
|
150
79
|
end
|
|
151
80
|
end
|
|
152
|
-
# rubocop:enable Metrics/MethodLength
|
|
153
81
|
end
|
|
154
82
|
end
|
|
155
83
|
end
|
|
@@ -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)
|