datadog 2.18.0 → 2.19.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +51 -10
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +58 -49
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -1
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -6
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -1
  8. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +37 -26
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +0 -1
  10. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -1
  11. data/lib/datadog/appsec/api_security/route_extractor.rb +7 -1
  12. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
  13. data/lib/datadog/core/configuration/settings.rb +20 -0
  14. data/lib/datadog/core/telemetry/component.rb +8 -4
  15. data/lib/datadog/core/telemetry/event/app_started.rb +21 -3
  16. data/lib/datadog/di/instrumenter.rb +11 -18
  17. data/lib/datadog/di/probe_notification_builder.rb +21 -16
  18. data/lib/datadog/di/serializer.rb +6 -2
  19. data/lib/datadog/di.rb +0 -6
  20. data/lib/datadog/kit/appsec/events/v2.rb +195 -0
  21. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -0
  22. data/lib/datadog/profiling/collectors/info.rb +41 -0
  23. data/lib/datadog/profiling/component.rb +1 -0
  24. data/lib/datadog/profiling/exporter.rb +9 -3
  25. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  26. data/lib/datadog/profiling/tag_builder.rb +2 -0
  27. data/lib/datadog/profiling.rb +1 -0
  28. data/lib/datadog/single_step_instrument.rb +9 -0
  29. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
  30. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  31. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
  32. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
  33. data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
  34. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  35. data/lib/datadog/tracing/span_event.rb +1 -1
  36. data/lib/datadog/tracing/span_operation.rb +22 -0
  37. data/lib/datadog/version.rb +1 -1
  38. data/lib/datadog.rb +7 -0
  39. metadata +8 -6
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../identity'
4
+
5
+ module Datadog
6
+ module Kit
7
+ module AppSec
8
+ module Events
9
+ # The second version of Business Logic Events SDK
10
+ module V2
11
+ LOGIN_SUCCESS_EVENT = 'users.login.success'
12
+ LOGIN_FAILURE_EVENT = 'users.login.failure'
13
+ TELEMETRY_METRICS_NAMESPACE = 'appsec'
14
+ TELEMETRY_METRICS_SDK_EVENT = 'sdk.event'
15
+ TELEMETRY_METRICS_SDK_VERSION = 'v2'
16
+ TELEMETRY_METRICS_EVENTS_INTO_TYPES = {
17
+ LOGIN_SUCCESS_EVENT => 'login_success',
18
+ LOGIN_FAILURE_EVENT => 'login_failure'
19
+ }.freeze
20
+
21
+ class << self
22
+ # Attach user login success information to the service entry span
23
+ # and trigger AppSec event processing.
24
+ #
25
+ # @param login [String] The user login (e.g., username or email).
26
+ # @param user_or_id [String, Hash<Symbol, String>] (optional) If a
27
+ # String, considered as a user ID, if a Hash, considered as a user
28
+ # attributes. The Hash must include `:id` as a key.
29
+ # @param metadata [Hash<Symbol, String>] Additional flat free-form
30
+ # metadata to attach to the event.
31
+ #
32
+ # @example Login only
33
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_success('alice@example.com')
34
+ #
35
+ # @example Login and user attributes
36
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_success(
37
+ # 'alice@example.com',
38
+ # { id: 'user-123', email: 'alice@example.com', name: 'Alice' },
39
+ # ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
40
+ # )
41
+ #
42
+ # @return [void]
43
+ def track_user_login_success(login, user_or_id = nil, metadata = {})
44
+ trace = service_entry_trace
45
+ span = service_entry_span
46
+
47
+ if trace.nil? || span.nil?
48
+ return Datadog.logger.warn(
49
+ 'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
50
+ )
51
+ end
52
+
53
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
54
+ raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
55
+
56
+ user_attributes = build_user_attributes(user_or_id, login)
57
+
58
+ set_span_tags(span, metadata, namespace: LOGIN_SUCCESS_EVENT)
59
+ set_span_tags(span, user_attributes, namespace: "#{LOGIN_SUCCESS_EVENT}.usr")
60
+ span.set_tag('appsec.events.users.login.success.track', 'true')
61
+ span.set_tag('_dd.appsec.events.users.login.success.sdk', 'true')
62
+
63
+ trace.keep!
64
+
65
+ record_event_telemetry_metric(LOGIN_SUCCESS_EVENT)
66
+ ::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_SUCCESS_EVENT)
67
+
68
+ # NOTE: Guard-clause will not work with Steep typechecking
69
+ return Kit::Identity.set_user(trace, span, **user_attributes) if user_attributes.key?(:id) # steep:ignore
70
+
71
+ # NOTE: This is a fallback for the case when we don't have an ID,
72
+ # but need to trigger WAF.
73
+ user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
74
+ ::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
75
+ end
76
+
77
+ # Attach user login failure information to the service entry span
78
+ # and trigger AppSec event processing.
79
+ #
80
+ # @param login [String] The user login (e.g., username or email).
81
+ # @param user_exists [Boolean] Whether the user exists in the system.
82
+ # @param metadata [Hash<Symbol, String>] Additional flat free-form
83
+ # metadata to attach to the event.
84
+ #
85
+ # @example Login only
86
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_failure('alice@example.com')
87
+ #
88
+ # @example With user existence and metadata
89
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_failure(
90
+ # 'alice@example.com',
91
+ # true,
92
+ # ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
93
+ # )
94
+ #
95
+ # @return [void]
96
+ def track_user_login_failure(login, user_exists = false, metadata = {})
97
+ trace = service_entry_trace
98
+ span = service_entry_span
99
+
100
+ if trace.nil? || span.nil?
101
+ return Datadog.logger.warn(
102
+ 'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
103
+ )
104
+ end
105
+
106
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
107
+ raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
108
+
109
+ unless user_exists.is_a?(TrueClass) || user_exists.is_a?(FalseClass)
110
+ raise TypeError, '`user_exists` argument must be a boolean'
111
+ end
112
+
113
+ set_span_tags(span, metadata, namespace: LOGIN_FAILURE_EVENT)
114
+ span.set_tag('appsec.events.users.login.failure.track', 'true')
115
+ span.set_tag('_dd.appsec.events.users.login.failure.sdk', 'true')
116
+ span.set_tag('appsec.events.users.login.failure.usr.login', login)
117
+ span.set_tag('appsec.events.users.login.failure.usr.exists', user_exists.to_s)
118
+
119
+ trace.keep!
120
+
121
+ record_event_telemetry_metric(LOGIN_FAILURE_EVENT)
122
+ ::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_FAILURE_EVENT)
123
+
124
+ user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
125
+ ::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
126
+ end
127
+
128
+ private
129
+
130
+ # NOTE: Current tracer implementation does not provide a way to
131
+ # get the service entry span. This is a shortcut we take now.
132
+ def service_entry_trace
133
+ return Datadog::Tracing.active_trace unless Datadog::AppSec.active_context
134
+
135
+ Datadog::AppSec.active_context&.trace
136
+ end
137
+
138
+ # NOTE: Current tracer implementation does not provide a way to
139
+ # get the service entry span. This is a shortcut we take now.
140
+ def service_entry_span
141
+ return Datadog::Tracing.active_span unless Datadog::AppSec.active_context
142
+
143
+ Datadog::AppSec.active_context&.span
144
+ end
145
+
146
+ def build_user_attributes(user_or_id, login)
147
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
148
+
149
+ case user_or_id
150
+ when nil
151
+ { login: login }
152
+ when String
153
+ { login: login, id: user_or_id }
154
+ when Hash
155
+ raise ArgumentError, 'missing required user key `:id`' unless user_or_id.key?(:id)
156
+ raise TypeError, 'user key `:id` must be a String' unless user_or_id[:id].is_a?(String)
157
+
158
+ user_or_id.merge(login: login)
159
+ else
160
+ raise TypeError, '`user_or_id` argument must be either String or Hash'
161
+ end
162
+ end
163
+
164
+ def set_span_tags(span, tags, namespace:)
165
+ tags.each do |name, value|
166
+ next if value.nil?
167
+
168
+ span.set_tag("appsec.events.#{namespace}.#{name}", value)
169
+ end
170
+ end
171
+
172
+ # TODO: In case if we need to introduce telemetry metrics to the SDK v1
173
+ # or highly increase the number of metrics, this method should be
174
+ # extracted into a proper module.
175
+ def record_event_telemetry_metric(event)
176
+ telemetry = ::Datadog.send(:components, allow_initialization: false)&.telemetry
177
+
178
+ if telemetry.nil?
179
+ return Datadog.logger.debug(
180
+ 'Kit::AppSec: Telemetry component is unavailable. Skip recording SDK metrics'
181
+ )
182
+ end
183
+
184
+ tags = {
185
+ event_type: TELEMETRY_METRICS_EVENTS_INTO_TYPES[event],
186
+ sdk_version: TELEMETRY_METRICS_SDK_VERSION
187
+ }
188
+ telemetry.inc(TELEMETRY_METRICS_NAMESPACE, TELEMETRY_METRICS_SDK_EVENT, 1, tags: tags)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -23,6 +23,7 @@ module Datadog
23
23
  allocation_profiling_enabled:,
24
24
  allocation_counting_enabled:,
25
25
  gvl_profiling_enabled:,
26
+ sighandler_sampling_enabled:,
26
27
  # **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
27
28
  # profiler overhead!
28
29
  dynamic_sampling_rate_enabled: true,
@@ -49,6 +50,7 @@ module Datadog
49
50
  allocation_profiling_enabled: allocation_profiling_enabled,
50
51
  allocation_counting_enabled: allocation_counting_enabled,
51
52
  gvl_profiling_enabled: gvl_profiling_enabled,
53
+ sighandler_sampling_enabled: sighandler_sampling_enabled,
52
54
  skip_idle_samples_for_testing: skip_idle_samples_for_testing,
53
55
  )
54
56
  @worker_thread = nil
@@ -28,6 +28,37 @@ module Datadog
28
28
 
29
29
  private
30
30
 
31
+ # Ruby GC tuning environment variables
32
+ RUBY_GC_TUNING_ENV_VARS = [
33
+ "RUBY_GC_HEAP_FREE_SLOTS",
34
+ "RUBY_GC_HEAP_GROWTH_FACTOR",
35
+ "RUBY_GC_HEAP_GROWTH_MAX_SLOTS",
36
+ "RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO",
37
+ "RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO",
38
+ "RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO",
39
+ "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR",
40
+ "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO",
41
+ "RUBY_GC_MALLOC_LIMIT",
42
+ "RUBY_GC_MALLOC_LIMIT_MAX",
43
+ "RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR",
44
+ "RUBY_GC_OLDMALLOC_LIMIT",
45
+ "RUBY_GC_OLDMALLOC_LIMIT_MAX",
46
+ "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR",
47
+ # INIT_SLOTS changed for Ruby 3.3+:
48
+ # * https://bugs.ruby-lang.org/issues/19785
49
+ # * https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/#:~:text=Removed%20environment%20variables
50
+ "RUBY_GC_HEAP_0_INIT_SLOTS",
51
+ "RUBY_GC_HEAP_1_INIT_SLOTS",
52
+ "RUBY_GC_HEAP_2_INIT_SLOTS",
53
+ "RUBY_GC_HEAP_3_INIT_SLOTS",
54
+ "RUBY_GC_HEAP_4_INIT_SLOTS",
55
+ # There was only one setting for older Rubies:
56
+ "RUBY_GC_HEAP_INIT_SLOTS",
57
+ # Ruby 2.x only, alias for others:
58
+ "RUBY_FREE_MIN",
59
+ "RUBY_HEAP_MIN_SLOTS",
60
+ ].freeze
61
+
31
62
  # Instead of trying to figure out real process start time by checking
32
63
  # /proc or some other complex/non-portable way, approximate start time
33
64
  # by time of requirement of this file.
@@ -51,6 +82,7 @@ module Datadog
51
82
  engine: Datadog::Core::Environment::Identity.lang_engine,
52
83
  version: Datadog::Core::Environment::Identity.lang_version,
53
84
  platform: Datadog::Core::Environment::Identity.lang_platform,
85
+ gc_tuning: collect_gc_tuning_info,
54
86
  }.freeze
55
87
  end
56
88
 
@@ -109,6 +141,15 @@ module Datadog
109
141
  v.inspect
110
142
  end
111
143
  end
144
+
145
+ def collect_gc_tuning_info
146
+ return @gc_tuning_info if defined?(@gc_tuning_info)
147
+
148
+ @gc_tuning_info = RUBY_GC_TUNING_ENV_VARS.each_with_object({}) do |var, hash|
149
+ current_value = ENV[var]
150
+ hash[var.to_sym] = current_value if current_value
151
+ end.freeze
152
+ end
112
153
  end
113
154
  end
114
155
  end
@@ -67,6 +67,7 @@ module Datadog
67
67
  allocation_profiling_enabled: allocation_profiling_enabled,
68
68
  allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled,
69
69
  gvl_profiling_enabled: enable_gvl_profiling?(settings, logger),
70
+ sighandler_sampling_enabled: settings.profiling.advanced.sighandler_sampling_enabled,
70
71
  )
71
72
 
72
73
  internal_metadata = {
@@ -26,7 +26,8 @@ module Datadog
26
26
  :last_flush_finish_at,
27
27
  :created_at,
28
28
  :internal_metadata,
29
- :info_json
29
+ :info_json,
30
+ :sequence_tracker
30
31
 
31
32
  public
32
33
 
@@ -37,7 +38,8 @@ module Datadog
37
38
  code_provenance_collector:,
38
39
  internal_metadata:,
39
40
  minimum_duration_seconds: PROFILE_DURATION_THRESHOLD_SECONDS,
40
- time_provider: Time
41
+ time_provider: Time,
42
+ sequence_tracker: Datadog::Profiling::SequenceTracker
41
43
  )
42
44
  @pprof_recorder = pprof_recorder
43
45
  @worker = worker
@@ -50,6 +52,7 @@ module Datadog
50
52
  # NOTE: At the time of this comment collected info does not change over time so we'll hardcode
51
53
  # it on startup to prevent serializing the same info on every flush.
52
54
  @info_json = JSON.generate(info_collector.info).freeze
55
+ @sequence_tracker = sequence_tracker
53
56
  end
54
57
 
55
58
  def flush
@@ -73,7 +76,10 @@ module Datadog
73
76
  encoded_profile: encoded_profile,
74
77
  code_provenance_file_name: Datadog::Profiling::Ext::Transport::HTTP::CODE_PROVENANCE_FILENAME,
75
78
  code_provenance_data: uncompressed_code_provenance,
76
- tags_as_array: Datadog::Profiling::TagBuilder.call(settings: Datadog.configuration).to_a,
79
+ tags_as_array: Datadog::Profiling::TagBuilder.call(
80
+ settings: Datadog.configuration,
81
+ profile_seq: sequence_tracker.get_next,
82
+ ).to_a,
77
83
  internal_metadata: internal_metadata.merge(
78
84
  {
79
85
  worker_stats: worker_stats,
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/utils/forking'
4
+
5
+ module Datadog
6
+ module Profiling
7
+ # Used to generate the `profile_seq` tag, which effectively counts how many profiles we've attempted to report
8
+ # from a given runtime-id.
9
+ #
10
+ # Note that the above implies a few things:
11
+ # 1. The sequence number only gets incremented when we decide to report a profile and create a `Flush` for it
12
+ # 2. The `SequenceTracker` must live across profiler reconfigurations and resets, since no matter how many
13
+ # profiler instances get created due to reconfiguration, the runtime-id is still the same, so the sequence number
14
+ # should be kept and not restarted from 0
15
+ # 3. The `SequenceTracker` must be reset after a fork, since the runtime-id will change, and we want to start
16
+ # counting from 0 again
17
+ #
18
+ # This is why this module is implemented as a singleton that we reuse, not as an instance that we recreate.
19
+ #
20
+ # Note that this module is not thread-safe, so it's up to the callers to make sure
21
+ # it's only used by a single thread at a time (which is what the `Profiling::Exporter`)
22
+ # is doing.
23
+ module SequenceTracker
24
+ class << self
25
+ include Core::Utils::Forking
26
+
27
+ def get_next
28
+ reset! unless defined?(@sequence_number)
29
+ after_fork! { reset! }
30
+
31
+ next_seq = @sequence_number
32
+ @sequence_number += 1
33
+ next_seq
34
+ end
35
+
36
+ private
37
+
38
+ def reset!
39
+ @sequence_number = 0
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -12,10 +12,12 @@ module Datadog
12
12
  def self.call(
13
13
  settings:,
14
14
  # Other metadata
15
+ profile_seq:,
15
16
  profiler_version: Core::Environment::Identity.gem_datadog_version
16
17
  )
17
18
  hash = Core::TagBuilder.tags(settings).merge(
18
19
  FORM_FIELD_TAG_PROFILER_VERSION => profiler_version,
20
+ 'profile_seq' => profile_seq.to_s,
19
21
  )
20
22
  Core::Utils.encode_tags(hash)
21
23
  end
@@ -157,6 +157,7 @@ module Datadog
157
157
  require_relative 'profiling/native_extension'
158
158
  require_relative 'profiling/tag_builder'
159
159
  require_relative 'profiling/http_transport'
160
+ require_relative 'profiling/sequence_tracker'
160
161
 
161
162
  replace_noop_allocation_count
162
163
 
@@ -5,8 +5,17 @@
5
5
  #
6
6
  # This file's path is private. Do not reference this file.
7
7
  #
8
+
9
+ module Datadog
10
+ # This module handles conditional loading of single step auto-instrumentation,
11
+ # which enables Datadog tracing and profiling features when available.
12
+ module SingleStepInstrument
13
+ end
14
+ end
15
+
8
16
  begin
9
17
  require_relative 'auto_instrument'
18
+ Datadog::SingleStepInstrument::LOADED = true
10
19
  rescue StandardError, LoadError => e
11
20
  warn "Single step instrumentation failed: #{e.class}:#{e.message}\n\tSource:\n\t#{Array(e.backtrace).join("\n\t")}"
12
21
  end
@@ -45,9 +45,15 @@ module Datadog
45
45
  'cache_write_multi.active_support' => { resource: Ext::RESOURCE_CACHE_MSET, multi_key: true }
46
46
  }.freeze
47
47
 
48
- def trace?(event, _payload)
48
+ def trace?(event, payload)
49
49
  return false if !Tracing.enabled? || !configuration.enabled
50
50
 
51
+ if (cache_store = configuration[:cache_store])
52
+ store = cache_backend(payload[:store])
53
+
54
+ return false unless cache_store.include?(store)
55
+ end
56
+
51
57
  # DEV-3.0: Backwards compatibility code for the 2.x gem series.
52
58
  # DEV-3.0: See documentation at {Datadog::Tracing::Contrib::ActiveSupport::Cache::Instrumentation}
53
59
  # DEV-3.0: for the complete information about this backwards compatibility code.
@@ -49,6 +49,19 @@ module Datadog
49
49
  o.default true
50
50
  end
51
51
  end
52
+
53
+ # Specifies which cache stores to trace.
54
+ # Accepts a list, with the same format as `config.cache_store`
55
+ # (e.g. `memory_store`, `file_store`, or symbols like `:file_store`).
56
+ # Defaults to `nil`, which traces all cache stores.
57
+ # @see https://github.com/rails/rails/blob/b7520a13adda46c0cc5f3fb4a4c1726633af2bba/guides/source/caching_with_rails.md?plain=1#L576-L582
58
+ option :cache_store do |o|
59
+ o.type :array, nilable: true
60
+ o.default nil
61
+ o.after_set do |stores|
62
+ stores&.map!(&:to_s) # Convert symbols to strings to match the Rails configuration format
63
+ end
64
+ end
52
65
  end
53
66
  end
54
67
  end
@@ -45,16 +45,13 @@ module Datadog
45
45
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
46
46
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_QUERY)
47
47
 
48
- span.set_tag(Tracing::Metadata::Ext::TAG_PEER_HOSTNAME, query_options[:host])
48
+ tag_database_instance(span, query_options[:database])
49
+
50
+ set_span_tags(span, query_options)
49
51
 
50
52
  # Set analytics sample rate
51
53
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
52
54
 
53
- span.set_tag(Contrib::Ext::DB::TAG_INSTANCE, query_options[:database])
54
- span.set_tag(Ext::TAG_DB_NAME, query_options[:database])
55
- span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, query_options[:host])
56
- span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, query_options[:port])
57
-
58
55
  Contrib::SpanAttributeSchema.set_peer_service!(span, Ext::PEER_SERVICE_SOURCES)
59
56
 
60
57
  sql = inject_propagation(span, sql, trace_op)
@@ -91,6 +88,19 @@ module Datadog
91
88
  def analytics_sample_rate
92
89
  datadog_configuration[:analytics_sample_rate]
93
90
  end
91
+
92
+ def tag_database_instance(span, database)
93
+ return if database.nil? || database.empty?
94
+
95
+ span.set_tag(Contrib::Ext::DB::TAG_INSTANCE, database)
96
+ span.set_tag(Ext::TAG_DB_NAME, database)
97
+ end
98
+
99
+ def set_span_tags(span, query_options)
100
+ span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, query_options[:host])
101
+ span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, query_options[:port])
102
+ span.set_tag(Tracing::Metadata::Ext::TAG_PEER_HOSTNAME, query_options[:host])
103
+ end
94
104
  end
95
105
  end
96
106
  end
@@ -86,7 +86,10 @@ module Datadog
86
86
 
87
87
  # Instruments the `bin/rails runner` command.
88
88
  def patch_rails_runner
89
- ::Rails::Command.singleton_class.prepend(Command) if defined?(::Rails::Command)
89
+ # The `RunnerCommand` class is only available in Rails 5.1 and later.
90
+ if defined?(::Rails::Command::RunnerCommand) && Integration.version >= Gem::Version.new('5.1')
91
+ ::Rails::Command::RunnerCommand.prepend(Runner)
92
+ end
90
93
  end
91
94
  end
92
95
  end
@@ -10,34 +10,48 @@ module Datadog
10
10
  # * `-`: for code provided through the STDIN.
11
11
  # * File path: for code provided through a local file.
12
12
  # * `inline code`: for code provided directly as a command line argument.
13
+ #
14
+ # The difficulty in instrumenting the Rails Runner is that
15
+ # the Rails application (and as a consequence the Datadog tracing library)
16
+ # is loaded very late in the runner execution.
17
+ # The Rails application is loaded inside the same method the method
18
+ # that directly executes the code the user wants the runner to execute:
19
+ #
20
+ # ```ruby
21
+ # def perform(code_or_file = nil, *command_argv)
22
+ # boot_application! # Loads the Rails and Datadog
23
+ #
24
+ # if code_or_file == "-"
25
+ # eval($stdin.read, TOPLEVEL_BINDING, "stdin") # Calls the user code for this Runner
26
+ # # ...
27
+ # ```
28
+ #
29
+ # This means that there's no time to instrument the calling method, `perform`, which
30
+ # would be ideal. Instead, we resort to instrumenting `eval` and `load`, but
31
+ # only for calls from the `Rails::Command::RunnerCommand` class.
32
+ #
13
33
  # @see https://guides.rubyonrails.org/v6.1/command_line.html#bin-rails-runner
14
34
  module Runner
15
35
  # Limit the maximum size of the source code captured in the source tag.
16
36
  MAX_TAG_VALUE_SIZE = 4096
17
37
  private_constant :MAX_TAG_VALUE_SIZE
18
38
 
19
- def runner(code_or_file = nil, *_command_argv)
20
- if code_or_file == '-'
39
+ # Instruments the `Kernel.eval` method, but only for the
40
+ # `Rails::Command::RunnerCommand` class.
41
+ def eval(*args)
42
+ source = args[0]
43
+
44
+ if args[2] == 'stdin'
21
45
  name = Ext::SPAN_RUNNER_STDIN
22
- resource = nil
23
46
  operation = Ext::TAG_OPERATION_STDIN
24
- # The source is not yet available for STDIN, but it will be captured in `eval`.
25
- elsif File.exist?(code_or_file)
26
- name = Ext::SPAN_RUNNER_FILE
27
- resource = code_or_file
28
- operation = Ext::TAG_OPERATION_FILE
29
- source = File.read(code_or_file)
30
47
  else
31
48
  name = Ext::SPAN_RUNNER_INLINE
32
- resource = nil
33
49
  operation = Ext::TAG_OPERATION_INLINE
34
- source = code_or_file
35
50
  end
36
51
 
37
52
  Tracing.trace(
38
53
  name,
39
54
  service: Datadog.configuration.tracing[:rails][:service_name],
40
- resource: resource,
41
55
  tags: {
42
56
  Tracing::Metadata::Ext::TAG_COMPONENT => Ext::TAG_COMPONENT,
43
57
  Tracing::Metadata::Ext::TAG_OPERATION => operation,
@@ -55,39 +69,46 @@ module Datadog
55
69
  end
56
70
  end
57
71
 
58
- # Capture the executed source code when provided from STDIN.
59
- def eval(*args)
60
- span = Datadog::Tracing.active_span
61
- if span&.name == Ext::SPAN_RUNNER_STDIN
62
- source = args[0]
63
- span.set_tag(
64
- Ext::TAG_RUNNER_SOURCE,
65
- Core::Utils.truncate(source, MAX_TAG_VALUE_SIZE)
66
- )
67
- end
68
-
69
- super
72
+ def self.prepend(base)
73
+ base.const_set(:Kernel, Kernel)
70
74
  end
71
75
 
72
- ruby2_keywords :eval if respond_to?(:ruby2_keywords, true)
73
- end
76
+ # Instruments the `Kernel.load` method, but only for the
77
+ # `Rails::Command::RunnerCommand` class.
78
+ module Kernel
79
+ def self.load(file, wrap = true)
80
+ name = Ext::SPAN_RUNNER_FILE
81
+ resource = file
82
+ operation = Ext::TAG_OPERATION_FILE
83
+
84
+ begin
85
+ # Reads one more byte than the limit to allow us to check if the source exceeds the limit.
86
+ source = File.read(file, MAX_TAG_VALUE_SIZE + 1)
87
+ rescue => e
88
+ Datadog.logger.debug("Failed to read file '#{file}' for Rails runner: #{e.message}")
89
+ end
74
90
 
75
- # The instrumentation target, {Rails::Command::RunnerCommand} is only loaded
76
- # right before `bin/rails runner` is executed. This means there's not much
77
- # opportunity to patch it ahead of time.
78
- # To ensure we can patch it successfully, we patch it's caller, {Rails::Command}
79
- # and promptly patch {Rails::Command::RunnerCommand} when it is loaded.
80
- module Command
81
- def find_by_namespace(*args)
82
- ret = super
83
- # Patch RunnerCommand if it is loaded and not already patched.
84
- if defined?(::Rails::Command::RunnerCommand) && !(::Rails::Command::RunnerCommand < Runner)
85
- ::Rails::Command::RunnerCommand.prepend(Runner)
91
+ Tracing.trace(
92
+ name,
93
+ service: Datadog.configuration.tracing[:rails][:service_name],
94
+ resource: resource,
95
+ tags: {
96
+ Tracing::Metadata::Ext::TAG_COMPONENT => Ext::TAG_COMPONENT,
97
+ Tracing::Metadata::Ext::TAG_OPERATION => operation,
98
+ }
99
+ ) do |span|
100
+ if source
101
+ span.set_tag(
102
+ Ext::TAG_RUNNER_SOURCE,
103
+ Core::Utils.truncate(source, MAX_TAG_VALUE_SIZE)
104
+ )
105
+ end
106
+ Contrib::Analytics.set_rate!(span, Datadog.configuration.tracing[:rails])
107
+
108
+ super
109
+ end
86
110
  end
87
- ret
88
111
  end
89
-
90
- ruby2_keywords :find_by_namespace if respond_to?(:ruby2_keywords, true)
91
112
  end
92
113
  end
93
114
  end
@@ -84,7 +84,7 @@ module Datadog
84
84
  # @return [Numeric, nil] tracer sample rate configured
85
85
  def sample_rate
86
86
  sampler = Datadog.configuration.tracing.sampler
87
- return nil unless sampler
87
+ return Datadog.configuration.tracing.sampling.default_rate unless sampler
88
88
 
89
89
  sampler.sample_rate(nil) rescue nil
90
90
  end
@@ -97,6 +97,8 @@ module Datadog
97
97
  # @return [Hash, nil] sample rules configured
98
98
  def sampling_rules
99
99
  sampler = Datadog.configuration.tracing.sampler
100
+ return Datadog.configuration.tracing.sampling.rules unless sampler
101
+
100
102
  return nil unless sampler.is_a?(Tracing::Sampling::PrioritySampler) &&
101
103
  sampler.priority_sampler.is_a?(Tracing::Sampling::RuleSampler)
102
104
 
@@ -56,7 +56,7 @@ module Datadog
56
56
  attr = {}
57
57
  @attributes.each do |key, value|
58
58
  attr[key] = if value.is_a?(Array)
59
- { type: ARRAY_TYPE, array_value: value.map { |v| serialize_native_attribute(v) } }
59
+ { type: ARRAY_TYPE, array_value: { values: value.map { |v| serialize_native_attribute(v) } } }
60
60
  else
61
61
  serialize_native_attribute(value)
62
62
  end