datadog 2.41.0 → 2.43.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
  9. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
  10. data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  13. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
  16. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
  19. data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
  21. data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
  22. data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
  23. data/ext/libdatadog_api/di.c +10 -0
  24. data/ext/libdatadog_api/extconf.rb +3 -0
  25. data/ext/libdatadog_api/init.c +2 -0
  26. data/ext/libdatadog_api/otel_thread_context.c +247 -0
  27. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  28. data/ext/libdatadog_api/trace_exporter.c +642 -22
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/blocked.html +1 -108
  31. data/lib/datadog/core/configuration/components.rb +1 -0
  32. data/lib/datadog/core/crashtracking/component.rb +5 -1
  33. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  34. data/lib/datadog/data_streams/processor.rb +31 -0
  35. data/lib/datadog/di/instrumenter.rb +41 -1
  36. data/lib/datadog/di/logger.rb +2 -2
  37. data/lib/datadog/di/probe.rb +9 -1
  38. data/lib/datadog/di/probe_notification_builder.rb +1 -0
  39. data/lib/datadog/di/remote.rb +3 -3
  40. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  41. data/lib/datadog/open_feature/exposures/event.rb +10 -3
  42. data/lib/datadog/open_feature/ext.rb +19 -0
  43. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  44. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  45. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  46. data/lib/datadog/open_feature/native_evaluator.rb +33 -6
  47. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  48. data/lib/datadog/open_feature/provider.rb +11 -2
  49. data/lib/datadog/open_feature/transport.rb +5 -0
  50. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  51. data/lib/datadog/opentelemetry/trace.rb +3 -0
  52. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
  53. data/lib/datadog/profiling/collectors/info.rb +2 -6
  54. data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
  55. data/lib/datadog/profiling/component.rb +8 -16
  56. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  57. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  58. data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
  59. data/lib/datadog/tracing/distributed/baggage.rb +0 -1
  60. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  61. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  62. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  63. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  64. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  65. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  66. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  67. data/lib/datadog/tracing/otel_thread_context.rb +34 -0
  68. data/lib/datadog/tracing/remote.rb +195 -27
  69. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  70. data/lib/datadog/tracing/trace_digest.rb +22 -4
  71. data/lib/datadog/tracing/trace_operation.rb +22 -10
  72. data/lib/datadog/tracing/tracer.rb +5 -5
  73. data/lib/datadog/tracing/transport/native.rb +17 -25
  74. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  75. data/lib/datadog/tracing/transport/traces.rb +2 -22
  76. data/lib/datadog/version.rb +1 -1
  77. metadata +16 -8
  78. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -5,6 +5,7 @@ require_relative "../core/utils"
5
5
  require_relative "event"
6
6
  require_relative "metadata/tagging"
7
7
  require_relative "sampling/ext"
8
+ require_relative "distributed/trace_state"
8
9
  require_relative "span_operation"
9
10
  require_relative "trace_digest"
10
11
  require_relative "correlation"
@@ -48,8 +49,7 @@ module Datadog
48
49
  :id,
49
50
  :max_length,
50
51
  :parent_span_id,
51
- :trace_state,
52
- :trace_state_unknown_fields
52
+ :trace_state
53
53
 
54
54
  attr_writer \
55
55
  :name,
@@ -86,8 +86,8 @@ module Datadog
86
86
  tags: nil,
87
87
  metrics: nil,
88
88
  trace_state: nil,
89
- trace_state_unknown_fields: nil,
90
89
  remote_parent: false,
90
+ distributed_sampling_priority: false,
91
91
  tracer: nil, # DEV-3.0: deprecated, remove in 3.0
92
92
  baggage: nil,
93
93
  auto_finish: true
@@ -100,6 +100,7 @@ module Datadog
100
100
  @parent_span_id = parent_span_id
101
101
  @sampled = sampled.nil? || sampled
102
102
  @remote_parent = remote_parent
103
+ @distributed_sampling_priority = distributed_sampling_priority
103
104
  @span_links = span_links
104
105
  # Tags
105
106
  @agent_sample_rate = agent_sample_rate
@@ -114,8 +115,7 @@ module Datadog
114
115
  @service = service
115
116
  @profiling_enabled = profiling_enabled
116
117
  @apm_tracing_enabled = apm_tracing_enabled
117
- @trace_state = trace_state
118
- @trace_state_unknown_fields = trace_state_unknown_fields
118
+ @trace_state = trace_state || Distributed::TraceState.new
119
119
  @baggage = baggage
120
120
 
121
121
  # Generic tags
@@ -391,6 +391,15 @@ module Datadog
391
391
  @propagated = true
392
392
  events.trace_propagated.publish(self)
393
393
 
394
+ open_telemetry = @trace_state.open_telemetry.outbound(
395
+ trace_id: @id,
396
+ sampling_priority: @sampling_priority,
397
+ decision_maker: get_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER),
398
+ applied_rate: @rule_sample_rate || @agent_sample_rate,
399
+ rate_limiter_rate: @rate_limiter_rate,
400
+ distributed_sampling_priority: @distributed_sampling_priority,
401
+ )
402
+
394
403
  TraceDigest.new(
395
404
  span_id: span_id,
396
405
  span_name: @active_span && @active_span.name,
@@ -407,8 +416,11 @@ module Datadog
407
416
  trace_runtime_id: Core::Environment::Identity.id,
408
417
  trace_sampling_priority: @sampling_priority,
409
418
  trace_service: service,
410
- trace_state: @trace_state,
411
- trace_state_unknown_fields: @trace_state_unknown_fields,
419
+ trace_state: @trace_state.unknown_vendors,
420
+ trace_state_unknown_fields: @trace_state.datadog.unknown_fields,
421
+ trace_otel_random_value: open_telemetry.random_value,
422
+ trace_otel_threshold: open_telemetry.threshold,
423
+ trace_otel_unknown_fields: open_telemetry.unknown_fields,
412
424
  span_remote: @remote_parent && @active_span.nil?,
413
425
  baggage: (@baggage.nil? || @baggage.empty?) ? nil : @baggage
414
426
  ).freeze
@@ -444,11 +456,11 @@ module Datadog
444
456
  sampled: @sampled,
445
457
  sampling_priority: @sampling_priority,
446
458
  service: service&.dup,
447
- trace_state: @trace_state&.dup,
448
- trace_state_unknown_fields: @trace_state_unknown_fields&.dup,
459
+ trace_state: @trace_state,
449
460
  tags: meta.dup,
450
461
  metrics: metrics.dup,
451
- remote_parent: @remote_parent
462
+ remote_parent: @remote_parent,
463
+ distributed_sampling_priority: @distributed_sampling_priority
452
464
  )
453
465
  end
454
466
 
@@ -375,9 +375,9 @@ module Datadog
375
375
  hostname = Core::Environment::Socket.resolved_hostname(Datadog.configuration)
376
376
 
377
377
  if digest
378
- sampling_priority = if propagate_sampling_priority?(upstream_tags: digest.trace_distributed_tags)
379
- digest.trace_sampling_priority
380
- end
378
+ propagate_sampling = propagate_sampling_priority?(upstream_tags: digest.trace_distributed_tags)
379
+ sampling_priority = digest.trace_sampling_priority if propagate_sampling
380
+ trace_state = Distributed::TraceState.from_digest(digest, propagate_sampling: propagate_sampling)
381
381
  TraceOperation.new(
382
382
  logger: logger,
383
383
  hostname: hostname,
@@ -390,9 +390,9 @@ module Datadog
390
390
  span_links: digest.span_links,
391
391
  # Distributed tags are just regular trace tags with special meaning to Datadog
392
392
  tags: digest.trace_distributed_tags,
393
- trace_state: digest.trace_state,
394
- trace_state_unknown_fields: digest.trace_state_unknown_fields,
393
+ trace_state: trace_state,
395
394
  remote_parent: digest.span_remote,
395
+ distributed_sampling_priority: !!sampling_priority,
396
396
  tracer: self,
397
397
  baggage: digest.baggage,
398
398
  auto_finish: auto_finish
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
  require_relative "trace_formatter"
5
5
  require_relative "statistics"
6
+ require_relative "span_events_negotiation"
6
7
 
7
8
  module Datadog
8
9
  module Tracing
@@ -44,6 +45,7 @@ module Datadog
44
45
  # Drop-in transport that delegates to the native trace exporter.
45
46
  class Transport
46
47
  include Statistics
48
+ include SpanEventsNegotiation
47
49
 
48
50
  attr_reader :logger
49
51
 
@@ -57,10 +59,6 @@ module Datadog
57
59
 
58
60
  @logger = logger
59
61
 
60
- # Guards the one-shot warning about span fields the native exporter
61
- # does not yet convert (see #warn_unsupported_fields!).
62
- @unsupported_fields_warned = false
63
-
64
62
  # Serializes native sends and is held across a fork. See the
65
63
  # fork-safety note below.
66
64
  @send_mutex = Mutex.new
@@ -272,9 +270,8 @@ module Datadog
272
270
  # Each trace segment becomes one inner array (one trace chunk).
273
271
  chunks = traces.map(&:spans)
274
272
 
275
- # Span events and span links are not yet converted and would be
276
- # dropped. Warn (once) so the loss is visible.
277
- warn_unsupported_fields!(chunks)
273
+ native_events_supported = native_events_supported?
274
+ apply_legacy_span_events!(chunks, native_events_supported)
278
275
 
279
276
  # Serialize the native send and hold the mutex across it so a
280
277
  # concurrent fork's :before hook blocks until this send drains
@@ -287,7 +284,7 @@ module Datadog
287
284
  exporter = @exporter
288
285
  raise "Native transport has been closed" if exporter.nil?
289
286
 
290
- exporter._native_send_traces(chunks)
287
+ exporter._native_send_traces(chunks, native_events_supported)
291
288
  end
292
289
 
293
290
  # Update statistics from the response
@@ -302,29 +299,24 @@ module Datadog
302
299
 
303
300
  private
304
301
 
305
- # Warn, at most once per transport, when a batch contains span fields
306
- # the native exporter does not yet convert (span events and span
307
- # links). These are silently dropped by the native path; full support
308
- # is tracked separately. The check is cheap: the fields are already-
309
- # materialized collections on each Span.
310
- def warn_unsupported_fields!(chunks)
311
- return if @unsupported_fields_warned
302
+ # Writes each span's events into the legacy JSON +events+ meta tag
303
+ # when the agent lacks typed-event support, mutating spans in place.
304
+ #
305
+ # @param chunks [Array<Array<Datadog::Tracing::Span>>] trace chunks to serialize
306
+ # @param native_events_supported [Boolean] whether the agent accepts typed events
307
+ # @return [void]
308
+ def apply_legacy_span_events!(chunks, native_events_supported)
309
+ return if native_events_supported
312
310
 
313
- unsupported = []
314
311
  chunks.each do |spans|
315
312
  spans.each do |span|
316
- unsupported << "span events" if span.events.any?
317
- unsupported << "span links" if span.links.any?
313
+ next if span.events.empty?
314
+
315
+ span.set_tag("events", span.events.map(&:to_hash).to_json)
318
316
  end
319
317
  end
320
- return if unsupported.empty?
321
318
 
322
- @unsupported_fields_warned = true
323
- fields = unsupported.uniq.join(", ")
324
- logger.warn do
325
- "Native transport does not yet support: #{fields}. This data will not be sent to Datadog. " \
326
- "Unset DD_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED to use the default transport if you rely on these."
327
- end
319
+ nil
328
320
  end
329
321
 
330
322
  def tracer_version_string
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Tracing
5
+ module Transport
6
+ # Shared agent capability negotiation for span event wire formats.
7
+ module SpanEventsNegotiation
8
+ private
9
+
10
+ # Queries whether the agent accepts typed span events, which selects
11
+ # between the typed field and legacy JSON metadata. Only successful
12
+ # capability responses are cached so a later flush can recover.
13
+ #
14
+ # The memo is read and written without synchronization, independent of
15
+ # any lock the including transport holds for its own sends (the native
16
+ # transport's +@send_mutex+, for example). Two concurrent sends can both
17
+ # observe it unset and each issue an +agent_info.fetch+; the duplicate
18
+ # fetch is self-correcting because the last writer wins.
19
+ #
20
+ # @return [Boolean] true if typed span events are supported
21
+ def native_events_supported?
22
+ return @native_events_supported if defined?(@native_events_supported)
23
+
24
+ option = Datadog.configuration.tracing.native_span_events
25
+ unless option.nil?
26
+ @native_events_supported = option
27
+ return option
28
+ end
29
+
30
+ components = Datadog.send(:components, allow_initialization: false)
31
+ return false unless components
32
+
33
+ response = components.agent_info.fetch
34
+ return false unless response
35
+
36
+ @native_events_supported = response.span_events == true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -7,6 +7,7 @@ require_relative "../../core/transport/transport"
7
7
  require_relative "../../core/utils/enumerable_compat"
8
8
  require_relative "http/client"
9
9
  require_relative "serializable_trace"
10
+ require_relative "span_events_negotiation"
10
11
  require_relative "trace_formatter"
11
12
 
12
13
  module Datadog
@@ -118,6 +119,7 @@ module Datadog
118
119
  # batches of traces into smaller chunks and handles
119
120
  # API version downgrade handshake.
120
121
  class Transport < Core::Transport::Transport
122
+ include SpanEventsNegotiation
121
123
  self.http_client_class = Tracing::Transport::HTTP::Client
122
124
 
123
125
  def send_traces(traces)
@@ -161,28 +163,6 @@ module Datadog
161
163
  def stats
162
164
  client.stats
163
165
  end
164
-
165
- private
166
-
167
- # Queries the agent for native span events serialization support.
168
- # This changes how the serialization of span events performed.
169
- def native_events_supported?
170
- return @native_events_supported if defined?(@native_events_supported)
171
-
172
- # Check for an explicit override
173
- option = Datadog.configuration.tracing.native_span_events
174
- unless option.nil?
175
- @native_events_supported = option
176
- return option
177
- end
178
-
179
- # Otherwise, check for agent support, to ensure a configuration-less setup.
180
- if (res = Datadog.send(:components).agent_info.fetch)
181
- @native_events_supported = res.span_events == true
182
- else
183
- false
184
- end
185
- end
186
166
  end
187
167
  end
188
168
  end
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 41
6
+ MINOR = 43
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.41.0
4
+ version: 2.43.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '3.5'
33
33
  - - ">="
34
34
  - !ruby/object:Gem::Version
35
- version: 3.5.4
35
+ version: 3.5.5
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  version: '3.5'
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: 3.5.4
45
+ version: 3.5.5
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: libddwaf
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -63,14 +63,14 @@ dependencies:
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: 40.0.0.1.0
66
+ version: 40.0.0.2.0
67
67
  type: :runtime
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: 40.0.0.1.0
73
+ version: 40.0.0.2.0
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: logger
76
76
  requirement: !ruby/object:Gem::Requirement
@@ -181,6 +181,8 @@ files:
181
181
  - ext/libdatadog_api/init.c
182
182
  - ext/libdatadog_api/library_config.c
183
183
  - ext/libdatadog_api/library_config.h
184
+ - ext/libdatadog_api/otel_thread_context.c
185
+ - ext/libdatadog_api/otel_thread_context.h
184
186
  - ext/libdatadog_api/process_discovery.c
185
187
  - ext/libdatadog_api/process_discovery.h
186
188
  - ext/libdatadog_api/trace_exporter.c
@@ -1023,6 +1025,7 @@ files:
1023
1025
  - lib/datadog/tracing/contrib/sequel/dataset.rb
1024
1026
  - lib/datadog/tracing/contrib/sequel/ext.rb
1025
1027
  - lib/datadog/tracing/contrib/sequel/integration.rb
1028
+ - lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb
1026
1029
  - lib/datadog/tracing/contrib/sequel/patcher.rb
1027
1030
  - lib/datadog/tracing/contrib/sequel/utils.rb
1028
1031
  - lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb
@@ -1096,13 +1099,16 @@ files:
1096
1099
  - lib/datadog/tracing/distributed/b3_single.rb
1097
1100
  - lib/datadog/tracing/distributed/baggage.rb
1098
1101
  - lib/datadog/tracing/distributed/datadog.rb
1099
- - lib/datadog/tracing/distributed/datadog_tags_codec.rb
1100
1102
  - lib/datadog/tracing/distributed/fetcher.rb
1101
1103
  - lib/datadog/tracing/distributed/helpers.rb
1102
1104
  - lib/datadog/tracing/distributed/none.rb
1103
1105
  - lib/datadog/tracing/distributed/propagation.rb
1104
1106
  - lib/datadog/tracing/distributed/propagation_policy.rb
1105
1107
  - lib/datadog/tracing/distributed/trace_context.rb
1108
+ - lib/datadog/tracing/distributed/trace_state.rb
1109
+ - lib/datadog/tracing/distributed/trace_state/datadog.rb
1110
+ - lib/datadog/tracing/distributed/trace_state/ext.rb
1111
+ - lib/datadog/tracing/distributed/trace_state/open_telemetry.rb
1106
1112
  - lib/datadog/tracing/event.rb
1107
1113
  - lib/datadog/tracing/ext.rb
1108
1114
  - lib/datadog/tracing/flush.rb
@@ -1113,6 +1119,7 @@ files:
1113
1119
  - lib/datadog/tracing/metadata/metastruct.rb
1114
1120
  - lib/datadog/tracing/metadata/metastruct_tagging.rb
1115
1121
  - lib/datadog/tracing/metadata/tagging.rb
1122
+ - lib/datadog/tracing/otel_thread_context.rb
1116
1123
  - lib/datadog/tracing/pipeline.rb
1117
1124
  - lib/datadog/tracing/pipeline/span_filter.rb
1118
1125
  - lib/datadog/tracing/pipeline/span_processor.rb
@@ -1153,6 +1160,7 @@ files:
1153
1160
  - lib/datadog/tracing/transport/native.rb
1154
1161
  - lib/datadog/tracing/transport/native/response.rb
1155
1162
  - lib/datadog/tracing/transport/serializable_trace.rb
1163
+ - lib/datadog/tracing/transport/span_events_negotiation.rb
1156
1164
  - lib/datadog/tracing/transport/statistics.rb
1157
1165
  - lib/datadog/tracing/transport/trace_formatter.rb
1158
1166
  - lib/datadog/tracing/transport/traces.rb
@@ -1166,8 +1174,8 @@ licenses:
1166
1174
  - Apache-2.0
1167
1175
  metadata:
1168
1176
  allowed_push_host: https://rubygems.org
1169
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.41.0/CHANGELOG.md
1170
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.41.0
1177
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.43.0/CHANGELOG.md
1178
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.43.0
1171
1179
  post_install_message: 'JRuby support in the datadog gem is deprecated. Details: https://dtdg.co/jruby-deprecation'
1172
1180
  rdoc_options: []
1173
1181
  require_paths:
@@ -1,69 +0,0 @@
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
- # ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
10
- VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
11
- # ASCII characters 32-126, except `,`. At least one character.
12
- VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
13
-
14
- # Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
15
- # String.
16
- #
17
- # @param tags [Hash<String,String>] trace tag hash
18
- # @return [String] serialized tags hash
19
- # @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
20
- def self.encode(tags)
21
- tags.map do |raw_key, raw_value|
22
- key = raw_key.to_s
23
- value = raw_value.to_s
24
-
25
- raise EncodingError, "Invalid key `#{key}` for value `#{value}`" unless VALID_KEY_CHARS.match?(key)
26
- raise EncodingError, "Invalid value `#{value}` for key `#{key}`" unless VALID_VALUE_CHARS.match?(value)
27
-
28
- "#{key}=#{value.strip}"
29
- end.join(",")
30
- rescue => e
31
- raise EncodingError, "Error encoding tags `#{tags}`: `#{e.class}: #{e.message}`"
32
- end
33
-
34
- # Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
35
- #
36
- # @param string [String] tags as serialized by {#encode}
37
- # @return [Hash<String,String>] decoded input as a hash of strings
38
- # @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
39
- def self.decode(string)
40
- result = (string.split(",").map do |raw_tag|
41
- raw_tag.split("=", 2).tap do |raw_key, raw_value|
42
- key = raw_key.to_s
43
- value = raw_value.to_s
44
-
45
- raise DecodingError, "Invalid key: #{key}" unless VALID_KEY_CHARS.match?(key)
46
- raise DecodingError, "Invalid value: #{value}" unless VALID_VALUE_CHARS.match?(value)
47
-
48
- value.strip!
49
- end
50
- end).to_h
51
-
52
- raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
53
-
54
- result
55
- end
56
-
57
- # An error occurred during distributed tags encoding.
58
- # See {#message} for more information.
59
- class EncodingError < StandardError
60
- end
61
-
62
- # An error occurred during distributed tags decoding.
63
- # See {#message} for more information.
64
- class DecodingError < StandardError
65
- end
66
- end
67
- end
68
- end
69
- end