datadog 2.0.0 → 2.2.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -2
  3. data/README.md +1 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +19 -1
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +41 -0
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1 -1
  7. data/ext/datadog_profiling_native_extension/crashtracker.c +1 -1
  8. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  9. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +47 -1
  10. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +1 -1
  11. data/ext/datadog_profiling_native_extension/stack_recorder.c +13 -6
  12. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  13. data/lib/datadog/appsec/configuration/settings.rb +5 -0
  14. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +0 -1
  15. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
  16. data/lib/datadog/appsec/extensions.rb +1 -0
  17. data/lib/datadog/core/configuration/components.rb +6 -3
  18. data/lib/datadog/core/configuration/ext.rb +1 -0
  19. data/lib/datadog/core/configuration/option.rb +21 -14
  20. data/lib/datadog/core/configuration/options.rb +5 -1
  21. data/lib/datadog/core/configuration/settings.rb +68 -5
  22. data/lib/datadog/core/configuration.rb +3 -17
  23. data/lib/datadog/core/deprecations.rb +58 -0
  24. data/lib/datadog/core/environment/ext.rb +2 -0
  25. data/lib/datadog/core/environment/yjit.rb +5 -0
  26. data/lib/datadog/core/runtime/ext.rb +2 -0
  27. data/lib/datadog/core/runtime/metrics.rb +6 -0
  28. data/lib/datadog/core/telemetry/component.rb +107 -0
  29. data/lib/datadog/core/telemetry/event.rb +124 -31
  30. data/lib/datadog/core/telemetry/ext.rb +2 -0
  31. data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
  32. data/lib/datadog/core/telemetry/metric.rb +167 -0
  33. data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
  34. data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
  35. data/lib/datadog/core/telemetry/request.rb +1 -1
  36. data/lib/datadog/core/telemetry/worker.rb +173 -0
  37. data/lib/datadog/core/utils/only_once_successful.rb +76 -0
  38. data/lib/datadog/core.rb +2 -19
  39. data/lib/datadog/opentelemetry/sdk/propagator.rb +5 -10
  40. data/lib/datadog/opentelemetry/sdk/span_processor.rb +5 -2
  41. data/lib/datadog/profiling/collectors/code_provenance.rb +18 -5
  42. data/lib/datadog/profiling/component.rb +18 -1
  43. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
  44. data/lib/datadog/profiling.rb +1 -0
  45. data/lib/datadog/tracing/configuration/ext.rb +7 -0
  46. data/lib/datadog/tracing/configuration/settings.rb +52 -3
  47. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  48. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +1 -1
  49. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +1 -1
  50. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +1 -1
  51. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -6
  52. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +9 -4
  53. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +3 -2
  54. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +1 -5
  55. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  56. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  57. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  58. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  59. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  60. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  61. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  62. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  63. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  64. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  65. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  66. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
  67. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +156 -0
  68. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
  69. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +45 -41
  70. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +17 -40
  71. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -1
  72. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +29 -6
  73. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +16 -4
  74. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +33 -29
  75. data/lib/datadog/tracing/contrib/analytics.rb +5 -0
  76. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +5 -0
  77. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -2
  78. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +166 -0
  79. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +25 -0
  80. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -1
  81. data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +1 -1
  82. data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
  83. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +3 -3
  84. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +3 -3
  85. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +3 -3
  86. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +3 -3
  87. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +3 -3
  88. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +3 -3
  89. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -2
  90. data/lib/datadog/tracing/contrib/rails/ext.rb +9 -0
  91. data/lib/datadog/tracing/contrib/rails/patcher.rb +7 -0
  92. data/lib/datadog/tracing/contrib/rails/runner.rb +95 -0
  93. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  94. data/lib/datadog/tracing/distributed/b3_single.rb +3 -1
  95. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  96. data/lib/datadog/tracing/distributed/propagation.rb +39 -4
  97. data/lib/datadog/tracing/distributed/trace_context.rb +5 -3
  98. data/lib/datadog/tracing/metadata/ext.rb +1 -0
  99. data/lib/datadog/tracing/span_operation.rb +3 -2
  100. data/lib/datadog/tracing/trace_operation.rb +7 -3
  101. data/lib/datadog/tracing/trace_segment.rb +4 -1
  102. data/lib/datadog/tracing/tracer.rb +9 -2
  103. data/lib/datadog/tracing.rb +5 -1
  104. data/lib/datadog/version.rb +2 -2
  105. metadata +21 -8
  106. data/lib/datadog/core/telemetry/client.rb +0 -95
  107. data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Telemetry
6
+ # Telemetry metrics data model (internal Datadog metrics for client libraries)
7
+ module Metric
8
+ # Base class for all metric types
9
+ class Base
10
+ attr_reader :name, :tags, :values, :common
11
+
12
+ # @param name [String] metric name
13
+ # @param tags [Array<String>|Hash{String=>String}] metric tags as hash or array of "tag:val" strings
14
+ # @param common [Boolean] true if the metric is common for all languages, false for Ruby-specific metric
15
+ def initialize(name, tags: {}, common: true)
16
+ @name = name
17
+ @values = []
18
+ @tags = tags_to_array(tags)
19
+ @common = common
20
+ end
21
+
22
+ def id
23
+ @id ||= "#{type}::#{name}::#{tags.join(',')}"
24
+ end
25
+
26
+ def track(value)
27
+ raise NotImplementedError, 'method must be implemented in subclasses'
28
+ end
29
+
30
+ def type
31
+ raise NotImplementedError, 'method must be implemented in subclasses'
32
+ end
33
+
34
+ def to_h
35
+ {
36
+ metric: name,
37
+ points: values,
38
+ type: type,
39
+ tags: tags,
40
+ common: common
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def tags_to_array(tags)
47
+ return tags if tags.is_a?(Array)
48
+
49
+ tags.map { |k, v| "#{k}:#{v}" }
50
+ end
51
+ end
52
+
53
+ # Base class for metrics that require aggregation interval
54
+ class IntervalMetric < Base
55
+ attr_reader :interval
56
+
57
+ # @param name [String] metric name
58
+ # @param tags [Array<String>|Hash{String=>String}] metric tags as hash of array of "tag:val" strings
59
+ # @param common [Boolean] true if the metric is common for all languages, false for Ruby-specific metric
60
+ # @param interval [Integer] metrics aggregation interval in seconds
61
+ def initialize(name, interval:, tags: {}, common: true)
62
+ raise ArgumentError, 'interval must be a positive number' if interval.nil? || interval <= 0
63
+
64
+ super(name, tags: tags, common: common)
65
+
66
+ @interval = interval
67
+ end
68
+
69
+ def to_h
70
+ res = super
71
+ res[:interval] = interval
72
+ res
73
+ end
74
+ end
75
+
76
+ # Count metric adds up all the submitted values in a time interval. This would be suitable for a
77
+ # metric tracking the number of website hits, for instance.
78
+ class Count < Base
79
+ TYPE = 'count'
80
+
81
+ def type
82
+ TYPE
83
+ end
84
+
85
+ def track(value)
86
+ value = value.to_i
87
+
88
+ if values.empty?
89
+ values << [Time.now.to_i, value]
90
+ else
91
+ values[0][0] = Time.now.to_i
92
+ values[0][1] += value
93
+ end
94
+ nil
95
+ end
96
+ end
97
+
98
+ # A gauge type takes the last value reported during the interval. This type would make sense for tracking RAM or
99
+ # CPU usage, where taking the last value provides a representative picture of the host’s behavior during the time
100
+ # interval.
101
+ class Gauge < IntervalMetric
102
+ TYPE = 'gauge'
103
+
104
+ def type
105
+ TYPE
106
+ end
107
+
108
+ def track(value)
109
+ if values.empty?
110
+ values << [Time.now.to_i, value]
111
+ else
112
+ values[0][0] = Time.now.to_i
113
+ values[0][1] = value
114
+ end
115
+ nil
116
+ end
117
+ end
118
+
119
+ # The rate type takes the count and divides it by the length of the time interval. This is useful if you’re
120
+ # interested in the number of hits per second.
121
+ class Rate < IntervalMetric
122
+ TYPE = 'rate'
123
+
124
+ def initialize(name, interval:, tags: {}, common: true)
125
+ super
126
+
127
+ @value = 0.0
128
+ end
129
+
130
+ def type
131
+ TYPE
132
+ end
133
+
134
+ def track(value = 1.0)
135
+ @value += value
136
+ @values = [[Time.now.to_i, @value / interval]]
137
+ nil
138
+ end
139
+ end
140
+
141
+ # Distribution metric represents the global statistical distribution of a set of values.
142
+ class Distribution < Base
143
+ TYPE = 'distributions'
144
+
145
+ def type
146
+ TYPE
147
+ end
148
+
149
+ def track(value)
150
+ values << value
151
+ nil
152
+ end
153
+
154
+ # distribution metric data does not have type field
155
+ def to_h
156
+ {
157
+ metric: name,
158
+ points: values,
159
+ tags: tags,
160
+ common: common
161
+ }
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+ require_relative 'metric'
5
+
6
+ module Datadog
7
+ module Core
8
+ module Telemetry
9
+ # MetricsCollection is a thread-safe collection of metrics per namespace
10
+ class MetricsCollection
11
+ attr_reader :namespace, :interval
12
+
13
+ def initialize(namespace, aggregation_interval:)
14
+ @namespace = namespace
15
+ @interval = aggregation_interval
16
+
17
+ @mutex = Mutex.new
18
+
19
+ @metrics = {}
20
+ @distributions = {}
21
+ end
22
+
23
+ def inc(metric_name, value, tags: {}, common: true)
24
+ metric = Metric::Count.new(metric_name, tags: tags, common: common)
25
+ fetch_or_add_metric(metric, value)
26
+ end
27
+
28
+ def dec(metric_name, value, tags: {}, common: true)
29
+ metric = Metric::Count.new(metric_name, tags: tags, common: common)
30
+ fetch_or_add_metric(metric, -value)
31
+ end
32
+
33
+ def gauge(metric_name, value, tags: {}, common: true)
34
+ metric = Metric::Gauge.new(metric_name, tags: tags, common: common, interval: @interval)
35
+ fetch_or_add_metric(metric, value)
36
+ end
37
+
38
+ def rate(metric_name, value, tags: {}, common: true)
39
+ metric = Metric::Rate.new(metric_name, tags: tags, common: common, interval: @interval)
40
+ fetch_or_add_metric(metric, value)
41
+ end
42
+
43
+ def distribution(metric_name, value, tags: {}, common: true)
44
+ metric = Metric::Distribution.new(metric_name, tags: tags, common: common)
45
+ fetch_or_add_distribution(metric, value)
46
+ end
47
+
48
+ def flush!
49
+ @mutex.synchronize do
50
+ events = []
51
+ events << Event::GenerateMetrics.new(@namespace, @metrics.values) if @metrics.any?
52
+ events << Event::Distributions.new(@namespace, @distributions.values) if @distributions.any?
53
+
54
+ @metrics = {}
55
+ @distributions = {}
56
+
57
+ events
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def fetch_or_add_metric(metric, value)
64
+ @mutex.synchronize do
65
+ m = (@metrics[metric.id] ||= metric)
66
+ m.track(value)
67
+ end
68
+ nil
69
+ end
70
+
71
+ def fetch_or_add_distribution(metric, value)
72
+ @mutex.synchronize do
73
+ m = (@distributions[metric.id] ||= metric)
74
+ m.track(value)
75
+ end
76
+ nil
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metrics_collection'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Telemetry
8
+ # MetricsManager aggregates and flushes metrics and distributions
9
+ class MetricsManager
10
+ attr_reader :enabled
11
+
12
+ def initialize(aggregation_interval:, enabled:)
13
+ @interval = aggregation_interval
14
+ @enabled = enabled
15
+ @mutex = Mutex.new
16
+
17
+ @collections = {}
18
+ end
19
+
20
+ def inc(namespace, metric_name, value, tags: {}, common: true)
21
+ return unless @enabled
22
+
23
+ # collection is thread-safe internally
24
+ collection = fetch_or_create_collection(namespace)
25
+ collection.inc(metric_name, value, tags: tags, common: common)
26
+ end
27
+
28
+ def dec(namespace, metric_name, value, tags: {}, common: true)
29
+ return unless @enabled
30
+
31
+ # collection is thread-safe internally
32
+ collection = fetch_or_create_collection(namespace)
33
+ collection.dec(metric_name, value, tags: tags, common: common)
34
+ end
35
+
36
+ def gauge(namespace, metric_name, value, tags: {}, common: true)
37
+ return unless @enabled
38
+
39
+ # collection is thread-safe internally
40
+ collection = fetch_or_create_collection(namespace)
41
+ collection.gauge(metric_name, value, tags: tags, common: common)
42
+ end
43
+
44
+ def rate(namespace, metric_name, value, tags: {}, common: true)
45
+ return unless @enabled
46
+
47
+ # collection is thread-safe internally
48
+ collection = fetch_or_create_collection(namespace)
49
+ collection.rate(metric_name, value, tags: tags, common: common)
50
+ end
51
+
52
+ def distribution(namespace, metric_name, value, tags: {}, common: true)
53
+ return unless @enabled
54
+
55
+ # collection is thread-safe internally
56
+ collection = fetch_or_create_collection(namespace)
57
+ collection.distribution(metric_name, value, tags: tags, common: common)
58
+ end
59
+
60
+ def flush!
61
+ return [] unless @enabled
62
+
63
+ collections = @mutex.synchronize { @collections.values }
64
+ collections.reduce([]) { |events, collection| events + collection.flush! }
65
+ end
66
+
67
+ def disable!
68
+ @enabled = false
69
+ end
70
+
71
+ private
72
+
73
+ def fetch_or_create_collection(namespace)
74
+ @mutex.synchronize do
75
+ @collections[namespace] ||= MetricsCollection.new(namespace, aggregation_interval: @interval)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -17,7 +17,7 @@ module Datadog
17
17
  application: application,
18
18
  debug: false,
19
19
  host: host,
20
- payload: event.payload(seq_id),
20
+ payload: event.payload,
21
21
  request_type: event.type,
22
22
  runtime_id: Core::Environment::Identity.id,
23
23
  seq_id: seq_id,
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+
5
+ require_relative '../utils/only_once_successful'
6
+ require_relative '../workers/polling'
7
+ require_relative '../workers/queue'
8
+
9
+ module Datadog
10
+ module Core
11
+ module Telemetry
12
+ # Accumulates events and sends them to the API at a regular interval, including heartbeat event.
13
+ class Worker
14
+ include Core::Workers::Queue
15
+ include Core::Workers::Polling
16
+
17
+ DEFAULT_BUFFER_MAX_SIZE = 1000
18
+ APP_STARTED_EVENT_RETRIES = 10
19
+
20
+ TELEMETRY_STARTED_ONCE = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
21
+
22
+ def initialize(
23
+ heartbeat_interval_seconds:,
24
+ metrics_aggregation_interval_seconds:,
25
+ emitter:,
26
+ metrics_manager:,
27
+ dependency_collection:,
28
+ enabled: true,
29
+ shutdown_timeout: Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT,
30
+ buffer_size: DEFAULT_BUFFER_MAX_SIZE
31
+ )
32
+ @emitter = emitter
33
+ @metrics_manager = metrics_manager
34
+ @dependency_collection = dependency_collection
35
+
36
+ @ticks_per_heartbeat = (heartbeat_interval_seconds / metrics_aggregation_interval_seconds).to_i
37
+ @current_ticks = 0
38
+
39
+ # Workers::Polling settings
40
+ self.enabled = enabled
41
+ # Workers::IntervalLoop settings
42
+ self.loop_base_interval = metrics_aggregation_interval_seconds
43
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
44
+
45
+ @shutdown_timeout = shutdown_timeout
46
+ @buffer_size = buffer_size
47
+
48
+ self.buffer = buffer_klass.new(@buffer_size)
49
+ end
50
+
51
+ def start
52
+ return if !enabled? || forked?
53
+
54
+ # starts async worker
55
+ perform
56
+ end
57
+
58
+ def stop(force_stop = false, timeout = @shutdown_timeout)
59
+ buffer.close if running?
60
+
61
+ super
62
+ end
63
+
64
+ def enqueue(event)
65
+ return if !enabled? || forked?
66
+
67
+ buffer.push(event)
68
+ end
69
+
70
+ def sent_started_event?
71
+ TELEMETRY_STARTED_ONCE.success?
72
+ end
73
+
74
+ def failed_to_start?
75
+ TELEMETRY_STARTED_ONCE.failed?
76
+ end
77
+
78
+ private
79
+
80
+ def perform(*events)
81
+ return if !enabled? || forked?
82
+
83
+ started! unless sent_started_event?
84
+
85
+ metric_events = @metrics_manager.flush!
86
+ events = [] if events.nil?
87
+ flush_events(events + metric_events)
88
+
89
+ @current_ticks += 1
90
+ return if @current_ticks < @ticks_per_heartbeat
91
+
92
+ @current_ticks = 0
93
+ heartbeat!
94
+ end
95
+
96
+ def flush_events(events)
97
+ return if events.empty?
98
+ return if !enabled? || !sent_started_event?
99
+
100
+ Datadog.logger.debug { "Sending #{events&.count} telemetry events" }
101
+ send_event(Event::MessageBatch.new(events))
102
+ end
103
+
104
+ def heartbeat!
105
+ return if !enabled? || !sent_started_event?
106
+
107
+ send_event(Event::AppHeartbeat.new)
108
+ end
109
+
110
+ def started!
111
+ return unless enabled?
112
+
113
+ if failed_to_start?
114
+ Datadog.logger.debug('Telemetry app-started event exhausted retries, disabling telemetry worker')
115
+ disable!
116
+ return
117
+ end
118
+
119
+ TELEMETRY_STARTED_ONCE.run do
120
+ res = send_event(Event::AppStarted.new)
121
+
122
+ if res.ok?
123
+ Datadog.logger.debug('Telemetry app-started event is successfully sent')
124
+
125
+ send_event(Event::AppDependenciesLoaded.new) if @dependency_collection
126
+
127
+ true
128
+ else
129
+ Datadog.logger.debug('Error sending telemetry app-started event, retry after heartbeat interval...')
130
+ false
131
+ end
132
+ end
133
+ end
134
+
135
+ def send_event(event)
136
+ res = @emitter.request(event)
137
+
138
+ disable_on_not_found!(res)
139
+
140
+ res
141
+ end
142
+
143
+ def dequeue
144
+ buffer.pop
145
+ end
146
+
147
+ def work_pending?
148
+ run_loop? || !buffer.empty?
149
+ end
150
+
151
+ def buffer_klass
152
+ if Core::Environment::Ext::RUBY_ENGINE == 'ruby'
153
+ Core::Buffer::CRuby
154
+ else
155
+ Core::Buffer::ThreadSafe
156
+ end
157
+ end
158
+
159
+ def disable!
160
+ self.enabled = false
161
+ @metrics_manager.disable!
162
+ end
163
+
164
+ def disable_on_not_found!(response)
165
+ return unless response.not_found?
166
+
167
+ Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
168
+ disable!
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'only_once'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Utils
8
+ # Helper class to execute something with only one success.
9
+ #
10
+ # This is useful for cases where we want to ensure that a block of code is only executed once, and only if it
11
+ # succeeds. One such example is sending app-started telemetry event.
12
+ #
13
+ # Successful execution is determined by the return value of the block: any truthy value is considered success.
14
+ #
15
+ # Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class).
16
+ #
17
+ # Note: In its current state, this class is not Ractor-safe.
18
+ # In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives,
19
+ # including an alternative implementation that is Ractor-safe once spent.
20
+ class OnlyOnceSuccessful < OnlyOnce
21
+ def initialize(limit = 0)
22
+ super()
23
+
24
+ @limit = limit
25
+ @failed = false
26
+ @retries = 0
27
+ end
28
+
29
+ def run
30
+ @mutex.synchronize do
31
+ return if @ran_once
32
+
33
+ result = yield
34
+ @ran_once = !!result
35
+
36
+ if !@ran_once && limited?
37
+ @retries += 1
38
+ check_limit!
39
+ end
40
+
41
+ result
42
+ end
43
+ end
44
+
45
+ def success?
46
+ @mutex.synchronize { @ran_once && !@failed }
47
+ end
48
+
49
+ def failed?
50
+ @mutex.synchronize { @ran_once && @failed }
51
+ end
52
+
53
+ private
54
+
55
+ def check_limit!
56
+ if @retries >= @limit
57
+ @failed = true
58
+ @ran_once = true
59
+ end
60
+ end
61
+
62
+ def limited?
63
+ !@limit.nil? && @limit.positive?
64
+ end
65
+
66
+ def reset_ran_once_state_for_tests
67
+ @mutex.synchronize do
68
+ @ran_once = false
69
+ @failed = false
70
+ @retries = 0
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
data/lib/datadog/core.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'core/deprecations'
3
4
  require_relative 'core/extensions'
4
5
 
5
6
  # We must load core extensions to make certain global APIs
@@ -9,25 +10,7 @@ module Datadog
9
10
  # products. It is a dependency of each product. Contrast with Datadog::Kit
10
11
  # for higher-level features.
11
12
  module Core
12
- class << self
13
- # Records the occurrence of a deprecated operation in this library.
14
- #
15
- # Currently, these operations are logged to `Datadog.logger` at `warn` level.
16
- #
17
- # `disallowed_next_major` adds a message informing that the deprecated operation
18
- # won't be allowed in the next major release.
19
- #
20
- # @yieldreturn [String] a String with the lazily evaluated deprecation message.
21
- # @param [Boolean] disallowed_next_major whether this deprecation will be enforced in the next major release.
22
- def log_deprecation(disallowed_next_major: true)
23
- Datadog.logger.warn do
24
- message = yield
25
- message += ' This will be enforced in the next major release.' if disallowed_next_major
26
- message
27
- end
28
- nil
29
- end
30
- end
13
+ extend Core::Deprecations
31
14
  end
32
15
 
33
16
  extend Core::Extensions
@@ -41,8 +41,11 @@ module Datadog
41
41
  digest = @datadog_propagator.extract(carrier)
42
42
  return context unless digest
43
43
 
44
- trace_id = to_otel_id(digest.trace_id)
45
- span_id = to_otel_id(digest.span_id)
44
+ # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
45
+ # 128-bit unsigned, big-endian integer
46
+ trace_id = [digest.trace_id >> 64, digest.trace_id & 0xFFFFFFFFFFFFFFFF].pack('Q>Q>')
47
+ # 64-bit unsigned, big-endian integer
48
+ span_id = [digest.span_id].pack('Q>')
46
49
 
47
50
  if digest.trace_state || digest.trace_flags
48
51
  trace_flags = ::OpenTelemetry::Trace::TraceFlags.from_byte(digest.trace_flags)
@@ -78,14 +81,6 @@ module Datadog
78
81
  def fields
79
82
  []
80
83
  end
81
-
82
- private
83
-
84
- # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
85
- # This method currently converts an unsigned 64-bit Integer to a binary String.
86
- def to_otel_id(dd_id)
87
- Array(dd_id).pack('Q')
88
- end
89
84
  end
90
85
  end
91
86
  end
@@ -70,7 +70,8 @@ module Datadog
70
70
  if parent_context.trace
71
71
  Tracing.send(:tracer).send(:call_context).activate!(parent_context.ensure_trace)
72
72
  else
73
- Tracing.continue_trace!(nil)
73
+ otel_trace_id = span.context.hex_trace_id.to_i(16)
74
+ Tracing.continue_trace!(Datadog::Tracing::TraceDigest.new(trace_id: otel_trace_id, span_remote: false))
74
75
  end
75
76
 
76
77
  datadog_span = start_datadog_span(span)
@@ -85,7 +86,6 @@ module Datadog
85
86
  name, kwargs = span_arguments(span, attributes)
86
87
 
87
88
  datadog_span = Tracing.trace(name, **kwargs)
88
-
89
89
  datadog_span.set_error([nil, span.status.description]) unless span.status.ok?
90
90
  datadog_span.set_tags(span.attributes)
91
91
 
@@ -143,6 +143,9 @@ module Datadog
143
143
 
144
144
  kwargs[:tags] = attributes.to_h
145
145
 
146
+ # DEV: The datadog span must have the same ID as the OpenTelemetry span
147
+ kwargs[:id] = span.context.hex_span_id.to_i(16)
148
+
146
149
  [name, kwargs]
147
150
  end
148
151