datadog 2.0.0.beta1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +181 -1
  3. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +1 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +40 -32
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -12
  6. data/ext/datadog_profiling_native_extension/crashtracker.c +108 -0
  7. data/ext/datadog_profiling_native_extension/extconf.rb +9 -23
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +81 -4
  9. data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
  10. data/ext/datadog_profiling_native_extension/http_transport.c +1 -94
  11. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +86 -0
  12. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +4 -0
  13. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +2 -12
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +25 -86
  15. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -5
  17. data/ext/datadog_profiling_native_extension/stack_recorder.c +161 -62
  18. data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
  19. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -13
  20. data/lib/datadog/appsec/event.rb +2 -2
  21. data/lib/datadog/core/configuration/components.rb +2 -1
  22. data/lib/datadog/core/configuration/option.rb +7 -5
  23. data/lib/datadog/core/configuration/settings.rb +34 -79
  24. data/lib/datadog/core/configuration.rb +20 -4
  25. data/lib/datadog/core/environment/platform.rb +7 -1
  26. data/lib/datadog/core/remote/client/capabilities.rb +2 -1
  27. data/lib/datadog/core/remote/client.rb +1 -5
  28. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  29. data/lib/datadog/core/remote/dispatcher.rb +3 -3
  30. data/lib/datadog/core/remote/transport/http/config.rb +5 -5
  31. data/lib/datadog/core/telemetry/client.rb +18 -10
  32. data/lib/datadog/core/telemetry/emitter.rb +9 -13
  33. data/lib/datadog/core/telemetry/event.rb +247 -57
  34. data/lib/datadog/core/telemetry/ext.rb +1 -0
  35. data/lib/datadog/core/telemetry/heartbeat.rb +1 -3
  36. data/lib/datadog/core/telemetry/http/ext.rb +4 -1
  37. data/lib/datadog/core/telemetry/http/response.rb +4 -0
  38. data/lib/datadog/core/telemetry/http/transport.rb +9 -4
  39. data/lib/datadog/core/telemetry/request.rb +59 -0
  40. data/lib/datadog/core/utils/base64.rb +22 -0
  41. data/lib/datadog/opentelemetry/sdk/span_processor.rb +19 -2
  42. data/lib/datadog/opentelemetry/sdk/trace/span.rb +3 -17
  43. data/lib/datadog/profiling/collectors/code_provenance.rb +10 -4
  44. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +25 -0
  45. data/lib/datadog/profiling/component.rb +49 -17
  46. data/lib/datadog/profiling/crashtracker.rb +91 -0
  47. data/lib/datadog/profiling/exporter.rb +6 -3
  48. data/lib/datadog/profiling/http_transport.rb +7 -11
  49. data/lib/datadog/profiling/load_native_extension.rb +14 -1
  50. data/lib/datadog/profiling/profiler.rb +9 -2
  51. data/lib/datadog/profiling/stack_recorder.rb +6 -2
  52. data/lib/datadog/profiling.rb +12 -0
  53. data/lib/datadog/tracing/component.rb +5 -1
  54. data/lib/datadog/tracing/configuration/dynamic.rb +39 -1
  55. data/lib/datadog/tracing/configuration/settings.rb +1 -0
  56. data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
  57. data/lib/datadog/tracing/contrib/action_view/integration.rb +1 -1
  58. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -0
  59. data/lib/datadog/tracing/contrib/active_record/integration.rb +11 -1
  60. data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
  61. data/lib/datadog/tracing/contrib/configuration/resolver.rb +43 -0
  62. data/lib/datadog/tracing/contrib/grape/endpoint.rb +43 -5
  63. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +1 -1
  64. data/lib/datadog/tracing/correlation.rb +3 -4
  65. data/lib/datadog/tracing/remote.rb +5 -1
  66. data/lib/datadog/tracing/sampling/ext.rb +5 -1
  67. data/lib/datadog/tracing/sampling/matcher.rb +75 -26
  68. data/lib/datadog/tracing/sampling/rule.rb +27 -4
  69. data/lib/datadog/tracing/sampling/rule_sampler.rb +19 -1
  70. data/lib/datadog/tracing/sampling/span/matcher.rb +13 -41
  71. data/lib/datadog/tracing/span.rb +7 -2
  72. data/lib/datadog/tracing/span_link.rb +92 -0
  73. data/lib/datadog/tracing/span_operation.rb +6 -4
  74. data/lib/datadog/tracing/trace_operation.rb +12 -0
  75. data/lib/datadog/tracing/tracer.rb +4 -3
  76. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -1
  77. data/lib/datadog/tracing/utils.rb +16 -0
  78. data/lib/datadog/version.rb +1 -1
  79. metadata +10 -31
  80. data/lib/datadog/core/telemetry/collector.rb +0 -248
  81. data/lib/datadog/core/telemetry/v1/app_event.rb +0 -59
  82. data/lib/datadog/core/telemetry/v1/application.rb +0 -94
  83. data/lib/datadog/core/telemetry/v1/configuration.rb +0 -27
  84. data/lib/datadog/core/telemetry/v1/dependency.rb +0 -45
  85. data/lib/datadog/core/telemetry/v1/host.rb +0 -59
  86. data/lib/datadog/core/telemetry/v1/install_signature.rb +0 -38
  87. data/lib/datadog/core/telemetry/v1/integration.rb +0 -66
  88. data/lib/datadog/core/telemetry/v1/product.rb +0 -36
  89. data/lib/datadog/core/telemetry/v1/telemetry_request.rb +0 -108
  90. data/lib/datadog/core/telemetry/v2/app_client_configuration_change.rb +0 -41
  91. data/lib/datadog/core/telemetry/v2/request.rb +0 -29
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'utils'
4
- require_relative 'metadata/ext'
5
4
  require_relative '../core/logging/ext'
6
5
 
7
6
  module Datadog
@@ -36,11 +35,11 @@ module Datadog
36
35
  version: nil
37
36
  )
38
37
  # Dup and freeze strings so they aren't modified by reference.
39
- @env = Core::Utils::SafeDup.frozen_dup(env || Datadog.configuration.env)
40
- @service = Core::Utils::SafeDup.frozen_dup(service || Datadog.configuration.service)
38
+ @env = env || Datadog.configuration.env
39
+ @service = service || Datadog.configuration.service
41
40
  @span_id = (span_id || 0).to_s
42
41
  @trace_id = trace_id || 0
43
- @version = Core::Utils::SafeDup.frozen_dup(version || Datadog.configuration.version)
42
+ @version = version || Datadog.configuration.version
44
43
  end
45
44
 
46
45
  def to_h
@@ -12,12 +12,16 @@ module Datadog
12
12
  class << self
13
13
  PRODUCT = 'APM_TRACING'
14
14
 
15
+ CAPABILITIES = [
16
+ 1 << 29 # APM_TRACING_SAMPLE_RULES: Dynamic trace sampling rules configuration
17
+ ].freeze
18
+
15
19
  def products
16
20
  [PRODUCT]
17
21
  end
18
22
 
19
23
  def capabilities
20
- [] # No capabilities advertised
24
+ CAPABILITIES
21
25
  end
22
26
 
23
27
  def process_config(config, content)
@@ -40,7 +40,7 @@ module Datadog
40
40
  DEFAULT = '-0'
41
41
  # The sampling rate received in the agent's http response.
42
42
  AGENT_RATE = '-1'
43
- # Sampling rule or sampling rate based on tracer config.
43
+ # Locally configured rule.
44
44
  TRACE_SAMPLING_RULE = '-3'
45
45
  # User directly sets sampling priority via {Tracing.reject!} or {Tracing.keep!},
46
46
  # or by a custom sampler implementation.
@@ -49,6 +49,10 @@ module Datadog
49
49
  ASM = '-5'
50
50
  # Single Span Sampled.
51
51
  SPAN_SAMPLING_RATE = '-8'
52
+ # Dynamically configured rule, explicitly created by the user.
53
+ REMOTE_USER_RULE = '-11'
54
+ # Dynamically configured rule, automatically generated by Datadog.
55
+ REMOTE_DYNAMIC_RULE = '-12'
52
56
  end
53
57
  end
54
58
  end
@@ -6,6 +6,9 @@ module Datadog
6
6
  # Checks if a trace conforms to a matching criteria.
7
7
  # @abstract
8
8
  class Matcher
9
+ # Pattern that matches any string
10
+ MATCH_ALL_PATTERN = '*'
11
+
9
12
  # Returns `true` if the trace should conforms to this rule, `false` otherwise
10
13
  #
11
14
  # @param [TraceOperation] trace
@@ -13,51 +16,97 @@ module Datadog
13
16
  def match?(trace)
14
17
  raise NotImplementedError
15
18
  end
16
- end
17
19
 
18
- # A {Datadog::Sampling::Matcher} that supports matching a trace by
19
- # trace name and/or service name.
20
- class SimpleMatcher < Matcher
21
- # Returns `true` for case equality (===) with any object
20
+ # Converts a glob pattern String to a case-insensitive String matcher object.
21
+ # The match object will only return `true` if it matches the complete String.
22
+ #
23
+ # The following special characters are supported:
24
+ # - `?` matches any single character
25
+ # - `*` matches any substring
26
+ #
27
+ # @param glob [String]
28
+ # @return [#match?(String)]
29
+ def self.glob_to_regex(glob)
30
+ # Optimization for match-all case
31
+ return MATCH_ALL if glob == MATCH_ALL_PATTERN
32
+
33
+ # Ensure no undesired characters are treated as regex.
34
+ glob = Regexp.quote(glob)
35
+
36
+ # Our valid special characters, `?` and `*`, were just escaped
37
+ # by `Regexp.quote` above. We need to unescape them:
38
+ glob.gsub!('\?', '.') # Any single character
39
+ glob.gsub!('\*', '.*') # Any substring
40
+
41
+ # Patterns have to match the whole input string
42
+ glob = "\\A#{glob}\\z"
43
+
44
+ Regexp.new(glob, Regexp::IGNORECASE)
45
+ end
46
+
47
+ # Returns `true` for any input
22
48
  MATCH_ALL = Class.new do
23
- # DEV: A class that implements `#===` is ~20% faster than
24
- # DEV: a `Proc` that always returns `true`.
25
- def ===(other)
49
+ def match?(_other)
26
50
  true
27
51
  end
52
+
53
+ def inspect
54
+ "MATCH_ALL:Matcher('*')"
55
+ end
28
56
  end.new
57
+ end
29
58
 
30
- attr_reader :name, :service
59
+ # A {Datadog::Sampling::Matcher} that supports matching a trace by
60
+ # trace name and/or service name.
61
+ class SimpleMatcher < Matcher
62
+ attr_reader :name, :service, :resource, :tags
31
63
 
32
64
  # @param name [String,Regexp,Proc] Matcher for case equality (===) with the trace name,
33
65
  # defaults to always match
34
66
  # @param service [String,Regexp,Proc] Matcher for case equality (===) with the service name,
35
67
  # defaults to always match
36
- def initialize(name: MATCH_ALL, service: MATCH_ALL)
68
+ # @param resource [String,Regexp,Proc] Matcher for case equality (===) with the resource name,
69
+ # defaults to always match
70
+ def initialize(
71
+ name: MATCH_ALL_PATTERN,
72
+ service: MATCH_ALL_PATTERN,
73
+ resource: MATCH_ALL_PATTERN,
74
+ tags: {}
75
+ )
37
76
  super()
38
- @name = name
39
- @service = service
77
+
78
+ name = Matcher.glob_to_regex(name)
79
+ service = Matcher.glob_to_regex(service)
80
+ resource = Matcher.glob_to_regex(resource)
81
+ tags = tags.transform_values { |matcher| Matcher.glob_to_regex(matcher) }
82
+
83
+ @name = name || Datadog::Tracing::Sampling::Matcher::MATCH_ALL
84
+ @service = service || Datadog::Tracing::Sampling::Matcher::MATCH_ALL
85
+ @resource = resource || Datadog::Tracing::Sampling::Matcher::MATCH_ALL
86
+ @tags = tags
40
87
  end
41
88
 
42
89
  def match?(trace)
43
- name === trace.name && service === trace.service
90
+ @name.match?(trace.name) &&
91
+ @service.match?(trace.service) &&
92
+ @resource.match?(trace.resource) &&
93
+ tags_match?(trace)
44
94
  end
45
- end
46
95
 
47
- # A {Datadog::Tracing::Sampling::Matcher} that allows for arbitrary trace matching
48
- # based on the return value of a provided block.
49
- class ProcMatcher < Matcher
50
- attr_reader :block
96
+ private
51
97
 
52
- # @yield [name, service] Provides trace name and service to the block
53
- # @yieldreturn [Boolean] Whether the trace conforms to this matcher
54
- def initialize(&block)
55
- super()
56
- @block = block
57
- end
98
+ # Match against the trace tags and metrics.
99
+ def tags_match?(trace)
100
+ @tags.all? do |name, matcher|
101
+ tag = trace.get_tag(name)
58
102
 
59
- def match?(trace)
60
- block.call(trace.name, trace.service)
103
+ # Format metrics as strings, to allow for partial number matching (/4.*/ matching '400', '404', etc.).
104
+ # Because metrics are floats, we use the '%g' format specifier to avoid trailing zeros, which
105
+ # can affect exact string matching (e.g. '400' matching '400.0').
106
+ tag = format('%g', tag) if tag.is_a?(Numeric)
107
+
108
+ matcher.match?(tag)
109
+ end
61
110
  end
62
111
  end
63
112
  end
@@ -10,13 +10,18 @@ module Datadog
10
10
  # a specific criteria and what sampling strategy to
11
11
  # apply in case of a positive match.
12
12
  class Rule
13
- attr_reader :matcher, :sampler
13
+ PROVENANCE_LOCAL = :local
14
+ PROVENANCE_REMOTE_USER = :customer
15
+ PROVENANCE_REMOTE_DYNAMIC = :dynamic
16
+
17
+ attr_reader :matcher, :sampler, :provenance
14
18
 
15
19
  # @param [Matcher] matcher A matcher to verify trace conformity against
16
20
  # @param [Sampler] sampler A sampler to be consulted on a positive match
17
- def initialize(matcher, sampler)
21
+ def initialize(matcher, sampler, provenance)
18
22
  @matcher = matcher
19
23
  @sampler = sampler
24
+ @provenance = provenance
20
25
  end
21
26
 
22
27
  # Evaluates if the provided `trace` conforms to the `matcher`.
@@ -51,9 +56,27 @@ module Datadog
51
56
  # @param name [String,Regexp,Proc] Matcher for case equality (===) with the trace name, defaults to always match
52
57
  # @param service [String,Regexp,Proc] Matcher for case equality (===) with the service name,
53
58
  # defaults to always match
59
+ # @param resource [String,Regexp,Proc] Matcher for case equality (===) with the resource name,
60
+ # defaults to always match
54
61
  # @param sample_rate [Float] Sampling rate between +[0,1]+
55
- def initialize(name: SimpleMatcher::MATCH_ALL, service: SimpleMatcher::MATCH_ALL, sample_rate: 1.0)
56
- super(SimpleMatcher.new(name: name, service: service), RateSampler.new(sample_rate))
62
+ def initialize(
63
+ name: SimpleMatcher::MATCH_ALL_PATTERN, service: SimpleMatcher::MATCH_ALL_PATTERN,
64
+ resource: SimpleMatcher::MATCH_ALL_PATTERN, tags: {},
65
+ provenance: Rule::PROVENANCE_LOCAL,
66
+ sample_rate: 1.0
67
+ )
68
+ # We want to allow 0.0 to drop all traces, but {Datadog::Tracing::Sampling::RateSampler}
69
+ # considers 0.0 an invalid rate and falls back to 100% sampling.
70
+ #
71
+ # We address that here by not setting the rate in the constructor,
72
+ # but using the setter method.
73
+ #
74
+ # We don't want to make this change directly to {Datadog::Tracing::Sampling::RateSampler}
75
+ # because it breaks its current contract to existing users.
76
+ sampler = RateSampler.new
77
+ sampler.sample_rate = sample_rate
78
+
79
+ super(SimpleMatcher.new(name: name, service: service, resource: resource, tags: tags), sampler, provenance)
57
80
  end
58
81
  end
59
82
  end
@@ -62,7 +62,15 @@ module Datadog
62
62
  kwargs = {
63
63
  name: rule['name'],
64
64
  service: rule['service'],
65
+ resource: rule['resource'],
66
+ tags: rule['tags'],
65
67
  sample_rate: sample_rate,
68
+ provenance: if (provenance = rule['provenance'])
69
+ # `Rule::PROVENANCE_*` values are symbols, so convert strings to match
70
+ provenance.to_sym
71
+ else
72
+ Rule::PROVENANCE_LOCAL
73
+ end,
66
74
  }
67
75
 
68
76
  kwargs.compact!
@@ -116,7 +124,17 @@ module Datadog
116
124
  rate_limiter.allow?(1).tap do |allowed|
117
125
  set_priority(trace, allowed)
118
126
  set_limiter_metrics(trace, rate_limiter.effective_rate)
119
- trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, Ext::Decision::TRACE_SAMPLING_RULE)
127
+
128
+ provenance = case rule.provenance
129
+ when Rule::PROVENANCE_REMOTE_USER
130
+ Ext::Decision::REMOTE_USER_RULE
131
+ when Rule::PROVENANCE_REMOTE_DYNAMIC
132
+ Ext::Decision::REMOTE_DYNAMIC_RULE
133
+ else
134
+ Ext::Decision::TRACE_SAMPLING_RULE
135
+ end
136
+
137
+ trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, provenance)
120
138
  end
121
139
  rescue StandardError => e
122
140
  Datadog.logger.error(
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../matcher'
4
+
3
5
  module Datadog
4
6
  module Tracing
5
7
  module Sampling
@@ -31,29 +33,19 @@ module Datadog
31
33
  # @param name_pattern [String] a pattern to be matched against {SpanOperation#name}
32
34
  # @param service_pattern [String] a pattern to be matched against {SpanOperation#service}
33
35
  def initialize(name_pattern: MATCH_ALL_PATTERN, service_pattern: MATCH_ALL_PATTERN)
34
- @name = pattern_to_regex(name_pattern)
35
- @service = pattern_to_regex(service_pattern)
36
+ @name = Sampling::Matcher.glob_to_regex(name_pattern)
37
+ @service = Sampling::Matcher.glob_to_regex(service_pattern)
36
38
  end
37
39
 
38
- # {Regexp#match?} was added in Ruby 2.4, and it's measurably
39
- # the least costly way to get a boolean result for a Regexp match.
40
- # @see https://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/
41
- if Regexp.method_defined?(:match?)
42
- # Returns `true` if the span conforms to the configured patterns,
43
- # `false` otherwise
44
- #
45
- # @param [SpanOperation] span
46
- # @return [Boolean]
47
- def match?(span)
48
- # Matching is performed at the end of the lifecycle of a Span,
49
- # thus both `name` and `service` are guaranteed to be not `nil`.
50
- @name.match?(span.name) && @service.match?(span.service)
51
- end
52
- else
53
- # DEV: Remove when support for Ruby 2.3 and older is removed.
54
- def match?(span)
55
- @name === span.name && @service === span.service
56
- end
40
+ # Returns `true` if the span conforms to the configured patterns,
41
+ # `false` otherwise
42
+ #
43
+ # @param [SpanOperation] span
44
+ # @return [Boolean]
45
+ def match?(span)
46
+ # Matching is performed at the end of the lifecycle of a Span,
47
+ # thus both `name` and `service` are guaranteed to be not `nil`.
48
+ @name.match?(span.name) && @service.match?(span.service)
57
49
  end
58
50
 
59
51
  def ==(other)
@@ -62,26 +54,6 @@ module Datadog
62
54
  name == other.name &&
63
55
  service == other.service
64
56
  end
65
-
66
- private
67
-
68
- # @param pattern [String]
69
- # @return [Regexp]
70
- def pattern_to_regex(pattern)
71
- # Ensure no undesired characters are treated as regex.
72
- # Our valid special characters, `?` and `*`,
73
- # will be escaped so...
74
- pattern = Regexp.quote(pattern)
75
-
76
- # ...we account for that here:
77
- pattern.gsub!('\?', '.') # Any single character
78
- pattern.gsub!('\*', '.*') # Any substring
79
-
80
- # Patterns have to match the whole input string
81
- pattern = "\\A#{pattern}\\z"
82
-
83
- Regexp.new(pattern)
84
- end
85
57
  end
86
58
  end
87
59
  end
@@ -26,6 +26,7 @@ module Datadog
26
26
  :parent_id,
27
27
  :resource,
28
28
  :service,
29
+ :links,
29
30
  :type,
30
31
  :start_time,
31
32
  :status,
@@ -58,7 +59,8 @@ module Datadog
58
59
  status: 0,
59
60
  type: nil,
60
61
  trace_id: nil,
61
- service_entry: nil
62
+ service_entry: nil,
63
+ links: nil
62
64
  )
63
65
  @name = Core::Utils::SafeDup.frozen_or_dup(name)
64
66
  @service = Core::Utils::SafeDup.frozen_or_dup(service)
@@ -86,6 +88,8 @@ module Datadog
86
88
 
87
89
  @service_entry = service_entry
88
90
 
91
+ @links = links || []
92
+
89
93
  # Mark with the service entry span metric, if applicable
90
94
  set_metric(Metadata::Ext::TAG_TOP_LEVEL, 1.0) if service_entry
91
95
  end
@@ -136,7 +140,8 @@ module Datadog
136
140
  service: @service,
137
141
  span_id: @id,
138
142
  trace_id: @trace_id,
139
- type: @type
143
+ type: @type,
144
+ span_links: @links.map(&:to_hash)
140
145
  }
141
146
 
142
147
  if stopped?
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ # SpanLink represents a causal link between two spans.
6
+ # @public_api
7
+ class SpanLink
8
+ # @!attribute [r] span_id
9
+ # Datadog id for the currently active span.
10
+ # @return [Integer]
11
+ attr_reader :span_id
12
+
13
+ # @!attribute [r] trace_id
14
+ # Datadog id for the currently active trace.
15
+ # @return [Integer]
16
+ attr_reader :trace_id
17
+
18
+ # @!attribute [r] attributes
19
+ # Datadog-specific tags that support richer distributed tracing association.
20
+ # @return [Hash<String,String>]
21
+ attr_reader :attributes
22
+
23
+ # @!attribute [r] trace_flags
24
+ # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer.
25
+ # @return [Integer]
26
+ # @see https://www.w3.org/TR/trace-context/#trace-flags
27
+ attr_reader :trace_flags
28
+
29
+ # @!attribute [r] trace_state
30
+ # The W3C "tracestate" extracted from a distributed context.
31
+ # This field is a string representing vendor-specific distribution data.
32
+ # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated
33
+ # on every propagation injection.
34
+ # @return [String]
35
+ # @see https://www.w3.org/TR/trace-context/#tracestate-header
36
+ attr_reader :trace_state
37
+
38
+ # @!attribute [r] dropped_attributes
39
+ # The number of attributes that were discarded due to serialization limits.
40
+ # @return [Integer]
41
+ attr_reader :dropped_attributes
42
+
43
+ def initialize(
44
+ digest,
45
+ attributes: nil
46
+ )
47
+ @span_id = digest.span_id
48
+ @trace_id = digest.trace_id
49
+ @trace_flags = if digest.trace_sampling_priority.nil?
50
+ nil
51
+ elsif digest.trace_sampling_priority > 0
52
+ 1
53
+ else
54
+ 0
55
+ end
56
+ @trace_state = digest.trace_state && digest.trace_state.dup
57
+ @dropped_attributes = 0
58
+ @attributes = (attributes && attributes.dup) || {}
59
+ end
60
+
61
+ def to_hash
62
+ h = {
63
+ span_id: @span_id || 0,
64
+ trace_id: Tracing::Utils::TraceId.to_low_order(@trace_id) || 0,
65
+ }
66
+ # Optimization: Hash non empty attributes
67
+ if @trace_id.to_i > Tracing::Utils::EXTERNAL_MAX_ID
68
+ h[:trace_id_high] =
69
+ Tracing::Utils::TraceId.to_high_order(@trace_id)
70
+ end
71
+ unless @attributes&.empty?
72
+ h[:attributes] = {}
73
+ @attributes.each do |k1, v1|
74
+ Tracing::Utils.serialize_attribute(k1, v1).each do |new_k1, value|
75
+ h[:attributes][new_k1] = value.to_s
76
+ end
77
+ end
78
+ end
79
+ h[:dropped_attributes_count] = @dropped_attributes if @dropped_attributes > 0
80
+ h[:tracestate] = @trace_state if @trace_state
81
+ # If traceflags set, the high bit (bit 31) should be set to 1 (uint32).
82
+ # This helps us distinguish between when the sample decision is zero or not set
83
+ h[:flags] = if @trace_flags.nil?
84
+ 0
85
+ else
86
+ @trace_flags | (1 << 31)
87
+ end
88
+ h
89
+ end
90
+ end
91
+ end
92
+ end
@@ -35,9 +35,7 @@ module Datadog
35
35
  :start_time,
36
36
  :trace_id,
37
37
  :type
38
-
39
- attr_accessor \
40
- :status
38
+ attr_accessor :links, :status
41
39
 
42
40
  def initialize(
43
41
  name,
@@ -49,7 +47,8 @@ module Datadog
49
47
  start_time: nil,
50
48
  tags: nil,
51
49
  trace_id: nil,
52
- type: nil
50
+ type: nil,
51
+ links: nil
53
52
  )
54
53
  # Ensure dynamically created strings are UTF-8 encoded.
55
54
  #
@@ -66,6 +65,8 @@ module Datadog
66
65
  @trace_id = trace_id || Tracing::Utils::TraceId.next_id
67
66
 
68
67
  @status = 0
68
+ # stores array of span links
69
+ @links = links || []
69
70
 
70
71
  # start_time and end_time track wall clock. In Ruby, wall clock
71
72
  # has less accuracy than monotonic clock, so if possible we look to only use wall clock
@@ -452,6 +453,7 @@ module Datadog
452
453
  status: @status,
453
454
  type: @type,
454
455
  trace_id: @trace_id,
456
+ links: @links,
455
457
  service_entry: parent.nil? || (service && parent.service != service)
456
458
  )
457
459
  end
@@ -8,6 +8,7 @@ require_relative 'metadata/tagging'
8
8
  require_relative 'sampling/ext'
9
9
  require_relative 'span_operation'
10
10
  require_relative 'trace_digest'
11
+ require_relative 'correlation'
11
12
  require_relative 'trace_segment'
12
13
  require_relative 'utils'
13
14
 
@@ -306,6 +307,17 @@ module Datadog
306
307
  ).freeze
307
308
  end
308
309
 
310
+ def to_correlation
311
+ # Resolve current span ID
312
+ span_id = @active_span && @active_span.id
313
+ span_id ||= @parent_span_id unless finished?
314
+
315
+ Correlation::Identifier.new(
316
+ trace_id: @id,
317
+ span_id: span_id
318
+ )
319
+ end
320
+
309
321
  # Returns a copy of this trace suitable for forks (w/o spans.)
310
322
  # Used for continuation of traces across forks.
311
323
  def fork_clone
@@ -224,9 +224,10 @@ module Datadog
224
224
  # @return [Datadog::Tracing::Correlation::Identifier] correlation object
225
225
  def active_correlation(key = nil)
226
226
  trace = active_trace(key)
227
- Correlation.identifier_from_digest(
228
- trace && trace.to_digest
229
- )
227
+
228
+ return Datadog::Tracing::Correlation::Identifier.new unless trace
229
+
230
+ trace.to_correlation
230
231
  end
231
232
 
232
233
  # Setup a new trace to continue from where another
@@ -58,7 +58,7 @@ module Datadog
58
58
  def to_msgpack(packer = nil)
59
59
  packer ||= MessagePack::Packer.new
60
60
 
61
- number_of_elements_to_write = 10
61
+ number_of_elements_to_write = 11
62
62
 
63
63
  if span.stopped?
64
64
  packer.write_map_header(number_of_elements_to_write + 2) # Set header with how many elements in the map
@@ -93,6 +93,8 @@ module Datadog
93
93
  packer.write(span.meta)
94
94
  packer.write('metrics')
95
95
  packer.write(span.metrics)
96
+ packer.write('span_links')
97
+ packer.write(span.links.map(&:to_hash))
96
98
  packer.write('error')
97
99
  packer.write(span.status)
98
100
  packer
@@ -45,6 +45,22 @@ module Datadog
45
45
  @id_rng = Random.new
46
46
  end
47
47
 
48
+ # Serialize values into Datadog span tags and metrics.
49
+ # Notably, arrays are exploded into many keys, each with
50
+ # a numeric suffix representing the array index, for example:
51
+ # `'foo' => ['a','b']` becomes `'foo.0' => 'a', 'foo.1' => 'b'`
52
+ def self.serialize_attribute(key, value)
53
+ if value.is_a?(Array)
54
+ value.flat_map.with_index do |v, idx|
55
+ serialize_attribute("#{key}.#{idx}", v)
56
+ end
57
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
58
+ [[key, value.to_s]]
59
+ else
60
+ [[key, value]]
61
+ end
62
+ end
63
+
48
64
  private_class_method :id_rng, :reset!
49
65
 
50
66
  # The module handles bitwise operation for trace id
@@ -5,7 +5,7 @@ module Datadog
5
5
  MAJOR = 2
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = 'beta1'
8
+ PRE = 'rc1'
9
9
  BUILD = nil
10
10
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
11
11