newrelic_rpm 9.12.0 → 9.17.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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +217 -1
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +16 -20
  5. data/lib/boot/strap.rb +4 -3
  6. data/lib/new_relic/agent/agent.rb +4 -0
  7. data/lib/new_relic/agent/agent_helpers/connect.rb +3 -0
  8. data/lib/new_relic/agent/agent_helpers/harvest.rb +3 -0
  9. data/lib/new_relic/agent/agent_helpers/shutdown.rb +3 -0
  10. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -0
  11. data/lib/new_relic/agent/agent_helpers/startup.rb +7 -0
  12. data/lib/new_relic/agent/aws.rb +6 -0
  13. data/lib/new_relic/agent/configuration/default_source.rb +363 -31
  14. data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
  15. data/lib/new_relic/agent/configuration/manager.rb +23 -0
  16. data/lib/new_relic/agent/configuration/yaml_source.rb +6 -1
  17. data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
  18. data/lib/new_relic/agent/database.rb +41 -1
  19. data/lib/new_relic/agent/distributed_tracing.rb +2 -2
  20. data/lib/new_relic/agent/health_check.rb +136 -0
  21. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  22. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -8
  23. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +5 -1
  24. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +9 -16
  25. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
  26. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
  27. data/lib/new_relic/agent/instrumentation/async_http.rb +1 -2
  28. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/chain.rb +21 -0
  29. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/instrumentation.rb +66 -0
  30. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/prepend.rb +15 -0
  31. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose.rb +22 -0
  32. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/chain.rb +21 -0
  33. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/instrumentation.rb +91 -0
  34. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/prepend.rb +15 -0
  35. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis.rb +22 -0
  36. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
  37. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
  38. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
  39. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
  40. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +0 -2
  41. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
  42. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +0 -2
  43. data/lib/new_relic/agent/instrumentation/curb.rb +3 -4
  44. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
  45. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +1 -1
  46. data/lib/new_relic/agent/instrumentation/dynamodb.rb +0 -2
  47. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +0 -2
  48. data/lib/new_relic/agent/instrumentation/ethon.rb +0 -4
  49. data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
  50. data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
  51. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +0 -3
  52. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  53. data/lib/new_relic/agent/instrumentation/httpclient.rb +0 -1
  54. data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
  55. data/lib/new_relic/agent/instrumentation/httpx.rb +0 -4
  56. data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
  57. data/lib/new_relic/agent/instrumentation/logstasher.rb +0 -2
  58. data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
  59. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  60. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  61. data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
  62. data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
  63. data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
  64. data/lib/new_relic/agent/instrumentation/rake.rb +0 -1
  65. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
  66. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  67. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
  68. data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
  69. data/lib/new_relic/agent/instrumentation/redis.rb +7 -6
  70. data/lib/new_relic/agent/instrumentation/resque.rb +7 -5
  71. data/lib/new_relic/agent/instrumentation/roda.rb +4 -4
  72. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  73. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  74. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
  75. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
  76. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delayed_class.rb +1 -1
  77. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
  78. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
  79. data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
  80. data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
  81. data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
  82. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +11 -5
  83. data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
  84. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  85. data/lib/new_relic/agent/local_log_decorator.rb +12 -2
  86. data/lib/new_relic/agent/log_event_aggregator.rb +28 -2
  87. data/lib/new_relic/agent/messaging.rb +11 -5
  88. data/lib/new_relic/agent/new_relic_service.rb +8 -2
  89. data/lib/new_relic/agent/serverless_handler.rb +241 -12
  90. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  91. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  92. data/lib/new_relic/agent/span_event_primitive.rb +4 -2
  93. data/lib/new_relic/agent/system_info.rb +14 -0
  94. data/lib/new_relic/agent/threading/backtrace_node.rb +10 -1
  95. data/lib/new_relic/agent/transaction/message_broker_segment.rb +3 -0
  96. data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
  97. data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
  98. data/lib/new_relic/agent.rb +95 -2
  99. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  100. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  101. data/lib/new_relic/control/frameworks/rails4.rb +1 -3
  102. data/lib/new_relic/dependency_detection.rb +11 -13
  103. data/lib/new_relic/environment_report.rb +2 -2
  104. data/lib/new_relic/helper.rb +15 -0
  105. data/lib/new_relic/language_support.rb +3 -1
  106. data/lib/new_relic/local_environment.rb +1 -4
  107. data/lib/new_relic/version.rb +1 -1
  108. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  109. data/lib/tasks/helpers/newrelicyml.rb +73 -11
  110. data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -1
  111. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +11 -8
  112. data/newrelic.yml +224 -79
  113. data/test/agent_helper.rb +8 -1
  114. metadata +32 -6
@@ -52,6 +52,8 @@ module NewRelic
52
52
  DATASTORE_CATEGORY = 'datastore'
53
53
  CLIENT = 'client'
54
54
 
55
+ DB_STATEMENT_MAX_BYTES = 4096
56
+
55
57
  # Builds a Hash of error attributes as well as the Span ID when
56
58
  # an error is present. Otherwise, returns nil when no error present.
57
59
  def error_attributes(segment)
@@ -114,9 +116,9 @@ module NewRelic
114
116
  agent_attributes[DB_SYSTEM_KEY] = segment.product if allowed?(DB_SYSTEM_KEY)
115
117
 
116
118
  if segment.sql_statement && allowed?(DB_STATEMENT_KEY)
117
- agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql, 2000)
119
+ agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql, DB_STATEMENT_MAX_BYTES)
118
120
  elsif segment.nosql_statement && allowed?(DB_STATEMENT_KEY)
119
- agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, 2000)
121
+ agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, DB_STATEMENT_MAX_BYTES)
120
122
  end
121
123
 
122
124
  [intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
@@ -19,6 +19,20 @@ module NewRelic
19
19
  RbConfig::CONFIG['target_os']
20
20
  end
21
21
 
22
+ def self.os_distribution
23
+ case
24
+ when darwin? then :darwin
25
+ when linux? then :linux
26
+ when bsd? then :bsd
27
+ when windows? then :windows
28
+ else ruby_os_identifier
29
+ end
30
+ end
31
+
32
+ def self.windows?
33
+ !!(ruby_os_identifier[/mingw|mswin/i])
34
+ end
35
+
22
36
  def self.darwin?
23
37
  !!(ruby_os_identifier =~ /darwin/i)
24
38
  end
@@ -125,7 +125,16 @@ module NewRelic
125
125
 
126
126
  # Returns [filename, method, line number]
127
127
  def parse_backtrace_frame(frame)
128
- frame =~ /([^:]*)(\:(\d+))?\:in `(.*)'/
128
+ # TODO: OLD RUBIES - Ruby 3.3
129
+ # The (?:`|') non-capturing group can be removed when the agent
130
+ # drops support for Ruby 3.3
131
+ # This group is used to capture the pre-Ruby 3.4.0 backtrace syntax.
132
+ # Example frame:
133
+ # Ruby 3.3.0 and below
134
+ # "irb.rb:69:in `catch'"
135
+ # Ruby 3.4.0+
136
+ # "irb.rb:69:in 'Kernel#catch'"
137
+ frame =~ /([^:]*)(\:(\d+))?\:in (?:`|')(.*)'/
129
138
  [$1, $4, $3] # sic
130
139
  end
131
140
  end
@@ -15,6 +15,7 @@ module NewRelic
15
15
  PRODUCE = 'Produce'.freeze
16
16
  QUEUE = 'Queue'.freeze
17
17
  PURGE = 'Purge'.freeze
18
+ STREAM = 'Stream'.freeze
18
19
  TEMP = 'Temp'.freeze
19
20
  TOPIC = 'Topic'.freeze
20
21
  UNKNOWN = 'Unknown'.freeze
@@ -22,6 +23,7 @@ module NewRelic
22
23
  DESTINATION_TYPES = [
23
24
  :exchange,
24
25
  :queue,
26
+ :stream,
25
27
  :topic,
26
28
  :temporary_queue,
27
29
  :temporary_topic,
@@ -37,6 +39,7 @@ module NewRelic
37
39
  TYPES = {
38
40
  exchange: EXCHANGE,
39
41
  temporary_queue: QUEUE,
42
+ stream: STREAM,
40
43
  queue: QUEUE,
41
44
  temporary_topic: TOPIC,
42
45
  topic: TOPIC,
@@ -24,7 +24,7 @@ module NewRelic
24
24
  @referer = referer_from_request(request)
25
25
  @accept = attribute_from_env(request, HTTP_ACCEPT_HEADER_KEY)
26
26
  @content_length = content_length_from_request(request)
27
- @content_type = attribute_from_request(request, :content_type)
27
+ @content_type = content_type_attribute_from_request(request)
28
28
  @host = attribute_from_request(request, :host)
29
29
  @port = port_from_request(request)
30
30
  @user_agent = attribute_from_request(request, :user_agent)
@@ -127,6 +127,18 @@ module NewRelic
127
127
  end
128
128
  end
129
129
 
130
+ def content_type_attribute_from_request(request)
131
+ # Rails 7.0 changed the behavior of `content_type`. We want just the MIME type, so use `media_type` if available.
132
+ # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is
133
+ content_type = if request.respond_to?(:media_type)
134
+ :media_type
135
+ elsif request.respond_to?(:content_type)
136
+ :content_type
137
+ end
138
+
139
+ request.send(content_type) if content_type
140
+ end
141
+
130
142
  def attribute_from_env(request, key)
131
143
  if env = attribute_from_request(request, :env)
132
144
  env[key]
@@ -95,7 +95,7 @@ module NewRelic
95
95
 
96
96
  def create_trace_state_payload
97
97
  unless Agent.config[:'distributed_tracing.enabled']
98
- NewRelic::Agent.logger.warn('Not configured to create WC3 trace context payload')
98
+ NewRelic::Agent.logger.warn('Not configured to create W3C trace context payload')
99
99
  return
100
100
  end
101
101
 
@@ -85,6 +85,10 @@ module NewRelic
85
85
  # An exception that forces an agent to stop reporting until its mongrel is restarted.
86
86
  class ForceDisconnectException < StandardError; end
87
87
 
88
+ # Error handling for the automated custom instrumentation tracer logic
89
+ class AutomaticTracerParseException < StandardError; end
90
+ class AutomaticTracerTraceException < StandardError; end
91
+
88
92
  # An exception that forces an agent to restart.
89
93
  class ForceRestartException < StandardError
90
94
  def message
@@ -109,6 +113,9 @@ module NewRelic
109
113
  # placeholder name used when we cannot determine a transaction's name
110
114
  UNKNOWN_METRIC = '(unknown)'.freeze
111
115
  LLM_FEEDBACK_MESSAGE = 'LlmFeedbackMessage'
116
+ # give the observed app time to load the code that automatic tracers have
117
+ # been configured for
118
+ AUTOMATIC_TRACER_MAX_ATTEMPTS = 60 # 60 = try about twice a second for 30 seconds
112
119
 
113
120
  attr_reader :error_group_callback
114
121
  attr_reader :llm_token_count_callback
@@ -125,8 +132,8 @@ module NewRelic
125
132
  def agent # :nodoc:
126
133
  return @agent if @agent
127
134
 
128
- NewRelic::Agent.logger.warn("Agent unavailable as it hasn't been started.")
129
- NewRelic::Agent.logger.warn(caller.join("\n"))
135
+ NewRelic::Agent.logger.debug("Agent unavailable as it hasn't been started.")
136
+ NewRelic::Agent.logger.debug(caller.join("\n"))
130
137
  nil
131
138
  end
132
139
 
@@ -163,6 +170,92 @@ module NewRelic
163
170
  end
164
171
  end
165
172
 
173
+ # @api private
174
+ def self.add_automatic_method_tracers(arr)
175
+ return unless arr
176
+ return arr if arr.respond_to?(:empty?) && arr.empty?
177
+
178
+ arr = arr.split(/\s*,\s*/) if arr.is_a?(String)
179
+
180
+ add_tracers_once_methods_are_defined(arr.dup)
181
+
182
+ arr
183
+ end
184
+
185
+ # spawn a thread that will attempt to establish a tracer for each of the
186
+ # configured methods. the thread will continue to keep trying with each
187
+ # tracer until one of the following happens:
188
+ # - the tracer is successfully established
189
+ # - the configured method string couldn't be parsed
190
+ # - establishing a tracer for a successfully parsed string failed
191
+ # - the maximum number of attempts has been reached
192
+ # the thread will only be spawned once per agent initialization, to account
193
+ # for configuration reloading scenarios.
194
+ #
195
+ # @api private
196
+ def self.add_tracers_once_methods_are_defined(notations)
197
+ # this class method can be invoked multiple times at agent startup, so
198
+ # we return asap here instead of using a traditional memoization of
199
+ # waiting for the method's body to finish being executed
200
+ if defined?(@add_tracers_once_methods_are_defined)
201
+ return
202
+ else
203
+ @add_tracers_once_methods_are_defined = true
204
+ end
205
+
206
+ Thread.new do
207
+ AUTOMATIC_TRACER_MAX_ATTEMPTS.times do
208
+ notations.delete_if { |notation| prep_tracer_for(notation) }
209
+
210
+ break if notations.empty?
211
+
212
+ sleep 0.5
213
+ end
214
+ end
215
+ end
216
+
217
+ # returns `true` if the notation string has either been successfully
218
+ # processed or raised an error during processing. returns `false` if the
219
+ # string seems good but the (customer) code to be traced has not yet been
220
+ # loaded into the Ruby VM
221
+ #
222
+ # @api private
223
+ def self.prep_tracer_for(fully_qualified_method_notation)
224
+ delimiters = fully_qualified_method_notation.scan(/\.|#/)
225
+ raise AutomaticTracerParseException.new("Expected exactly one '.' or '#' delimiter.") unless delimiters.size == 1
226
+
227
+ delimiter = delimiters.first
228
+ namespace, method_name = fully_qualified_method_notation.split(delimiter)
229
+ unless namespace && !namespace.empty?
230
+ raise AutomaticTracerParseException.new("Nothing found to the left of the #{delimiter} delimiter.")
231
+ end
232
+ unless method_name && !method_name.empty?
233
+ raise AutomaticTracerParseException.new("Nothing found to the right of the #{delimiter} delimiter.")
234
+ end
235
+
236
+ begin
237
+ klass = ::NewRelic::LanguageSupport.constantize(namespace)
238
+ return false unless klass
239
+
240
+ klass_to_trace = delimiter.eql?('.') ? klass.singleton_class : klass
241
+ add_or_defer_method_tracer(klass_to_trace, method_name, nil, {})
242
+ rescue StandardError => e
243
+ raise AutomaticTracerTraceException.new("#{e.class} - #{e.message}")
244
+ end
245
+
246
+ true
247
+ rescue AutomaticTracerParseException => e
248
+ NewRelic::Agent.logger.error('Unable to parse out a usable method name to trace. Expected a valid, fully ' \
249
+ "qualified method notation. Got: '#{fully_qualified_method_notation}'. " \
250
+ "Error: #{e.message}")
251
+ true
252
+ rescue AutomaticTracerTraceException => e
253
+ NewRelic::Agent.logger.error('Unable to automatically apply a tracer to method ' \
254
+ "'#{fully_qualified_method_notation}'. Error: #{e.message}")
255
+ true
256
+ end
257
+
258
+ # @api private
166
259
  def add_deferred_method_tracers_now
167
260
  @tracer_lock.synchronize do
168
261
  @tracer_queue.each do |receiver, method_name, metric_name, options|
@@ -0,0 +1,14 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ require 'new_relic/control/frameworks/ruby'
6
+ module NewRelic
7
+ class Control
8
+ module Frameworks
9
+ # Contains basic control logic for Grape
10
+ class Grape < NewRelic::Control::Frameworks::Ruby
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ require 'new_relic/control/frameworks/sinatra'
6
+ module NewRelic
7
+ class Control
8
+ module Frameworks
9
+ # Contains basic control logic for Padrino
10
+ class Padrino < NewRelic::Control::Frameworks::Sinatra
11
+ end
12
+ end
13
+ end
14
+ end
@@ -9,9 +9,7 @@ module NewRelic
9
9
  module Frameworks
10
10
  class Rails4 < NewRelic::Control::Frameworks::Rails3
11
11
  def rails_gem_list
12
- Bundler.rubygems.all_specs.map do |gem|
13
- "#{gem.name} (#{gem.version})"
14
- end
12
+ NewRelic::Helper.rubygems_specs.map { |gem| "#{gem.name} (#{gem.version})" }
15
13
  end
16
14
 
17
15
  def append_plugin_list
@@ -25,11 +25,9 @@ module DependencyDetection
25
25
 
26
26
  def detect!
27
27
  @items.each do |item|
28
- if item.dependencies_satisfied?
29
- item.execute
30
- else
31
- item.configure_as_unsatisfied unless item.disabled_configured?
32
- end
28
+ next if item.executed || item.disabled_configured?
29
+
30
+ item.dependencies_satisfied? ? item.execute : item.configure_as_unsatisfied
33
31
  end
34
32
  end
35
33
 
@@ -65,6 +63,13 @@ module DependencyDetection
65
63
  end
66
64
 
67
65
  def configure_as_unsatisfied
66
+ # TODO: currently using :unsatisfied for Padrino will clobber the value
67
+ # already set for Sinatra, so skip Padrino and circle back with a
68
+ # new Padrino specific solution in the future.
69
+ #
70
+ # https://github.com/newrelic/newrelic-ruby-agent/issues/2912
71
+ return if name == :padrino
72
+
68
73
  NewRelic::Agent.config.instance_variable_get(:@cache)[config_key] = :unsatisfied
69
74
  end
70
75
 
@@ -139,8 +144,6 @@ module DependencyDetection
139
144
  !(disabled_configured? || deprecated_disabled_configured?)
140
145
  end
141
146
 
142
- # TODO: MAJOR VERSION
143
- # will only return true if a disabled key is found and is truthy
144
147
  def deprecated_disabled_configured?
145
148
  return false if self.name.nil?
146
149
 
@@ -148,12 +151,7 @@ module DependencyDetection
148
151
  return false unless ::NewRelic::Agent.config[key] == true
149
152
 
150
153
  ::NewRelic::Agent.logger.debug("Not installing #{self.name} instrumentation because of configuration #{key}")
151
- ::NewRelic::Agent.logger.debug( \
152
- "[DEPRECATED] configuration #{key} for #{self.name} will be removed in the next major release. " \
153
- "Use `#{config_key}` with one of `#{VALID_CONFIG_VALUES.map(&:to_s).inspect}`"
154
- )
155
-
156
- return true
154
+ true
157
155
  end
158
156
 
159
157
  def config_key
@@ -44,7 +44,7 @@ module NewRelic
44
44
  ####################################
45
45
  report_on('Gems') do
46
46
  begin
47
- Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" }
47
+ NewRelic::Helper.rubygems_specs.map { |gem| "#{gem.name}(#{gem.version})" }
48
48
  rescue
49
49
  # There are certain rubygem, bundler, rails combinations (e.g. gem
50
50
  # 1.6.2, rails 2.3, bundler 1.2.3) where the code above throws an error
@@ -67,7 +67,7 @@ module NewRelic
67
67
  report_on('Physical Cores') { ::NewRelic::Agent::SystemInfo.num_physical_cores }
68
68
  report_on('Arch') { ::NewRelic::Agent::SystemInfo.processor_arch }
69
69
  report_on('OS version') { ::NewRelic::Agent::SystemInfo.os_version }
70
- report_on('OS') { ::NewRelic::Agent::SystemInfo.ruby_os_identifier }
70
+ report_on('OS') { ::NewRelic::Agent::SystemInfo.os_distribution }
71
71
  report_on('Database adapter') { ::NewRelic::Agent::DatabaseAdapter.value }
72
72
  report_on('Framework') { Agent.config[:framework].to_s }
73
73
  report_on('Dispatcher') { Agent.config[:dispatcher].to_s }
@@ -82,5 +82,20 @@ module NewRelic
82
82
  File.exist?(executable_path) && File.file?(executable_path) && File.executable?(executable_path)
83
83
  end
84
84
  end
85
+
86
+ # Bundler version 2.5.12 deprecated all_specs and added installed_specs.
87
+ # To support newer Bundler versions, try to use installed_specs first,
88
+ # then fall back to all_specs.
89
+ # All callers expect this to be an array, so return an array if Bundler isn't defined
90
+ # @api private
91
+ def rubygems_specs
92
+ return [] unless defined?(Bundler)
93
+
94
+ if Bundler.rubygems.respond_to?(:installed_specs)
95
+ Bundler.rubygems.installed_specs
96
+ else
97
+ Bundler.rubygems.all_specs
98
+ end
99
+ end
85
100
  end
86
101
  end
@@ -88,7 +88,9 @@ module NewRelic
88
88
  end
89
89
 
90
90
  def bundled_gem?(gem_name)
91
- defined?(Bundler) && Bundler.rubygems.all_specs.map(&:name).include?(gem_name)
91
+ return false unless defined?(Bundler)
92
+
93
+ NewRelic::Helper.rubygems_specs.map(&:name).include?(gem_name)
92
94
  rescue => e
93
95
  ::NewRelic::Agent.logger.info("Could not determine if third party #{gem_name} gem is installed", e)
94
96
  false
@@ -142,10 +142,7 @@ module NewRelic
142
142
  end
143
143
 
144
144
  def check_for_falcon
145
- return unless defined?(::Falcon::Server) &&
146
- NewRelic::LanguageSupport.object_space_usable?
147
-
148
- @discovered_dispatcher = :falcon if find_class_in_object_space(::Falcon::Server)
145
+ @discovered_dispatcher = :falcon if defined?(::Falcon::Server) && File.basename($PROGRAM_NAME) == 'falcon'
149
146
  end
150
147
 
151
148
  def check_for_delayed_job
@@ -6,7 +6,7 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 12
9
+ MINOR = 17
10
10
  TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
@@ -79,7 +79,7 @@ module Sequel
79
79
 
80
80
  THREAD_SAFE_CONNECTION_POOL_CLASSES = [
81
81
  (defined?(::Sequel::ThreadedConnectionPool) && ::Sequel::ThreadedConnectionPool),
82
- (defined?(::Sequel::TimedQueueConnectionPool) && RUBY_VERSION >= '3.4' && ::Sequel::TimedQueueConnectionPool)
82
+ (defined?(::Sequel::TimedQueueConnectionPool) && RUBY_VERSION >= '3.2' && ::Sequel::TimedQueueConnectionPool)
83
83
  ].compact.freeze
84
84
 
85
85
  def explainer_for(sql)
@@ -45,6 +45,34 @@ module NewRelicYML
45
45
 
46
46
  HEADER
47
47
 
48
+ SECURITY_BEGIN = <<-SECURITY
49
+ # BEGIN security agent
50
+ #
51
+ # NOTE: At this time, the security agent is intended for use only within
52
+ # a dedicated security testing environment with data that can tolerate
53
+ # modification or deletion. The security agent is available as a
54
+ # separate Ruby gem, newrelic_security. It is recommended that this
55
+ # separate gem only be introduced to a security testing environment
56
+ # by leveraging Bundler grouping like so:
57
+ #
58
+ # # Gemfile
59
+ # gem 'newrelic_rpm' # New Relic APM observability agent
60
+ # gem 'newrelic-infinite_tracing' # New Relic Infinite Tracing
61
+ #
62
+ # group :security do
63
+ # gem 'newrelic_security', require: false # New Relic security agent
64
+ # end
65
+ #
66
+ # NOTE: All "security.*" configuration parameters are related only to the
67
+ # security agent, and all other configuration parameters that may
68
+ # have "security" in the name somewhere are related to the APM agent.
69
+
70
+ SECURITY
71
+
72
+ SECURITY_END = <<-SECURITY
73
+ # END security agent
74
+ SECURITY
75
+
48
76
  FOOTER = <<~FOOTER
49
77
  # Environment-specific settings are in this section.
50
78
  # RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment.
@@ -67,16 +95,35 @@ module NewRelicYML
67
95
  FOOTER
68
96
 
69
97
  def self.get_configs(defaults)
70
- defaults.sort.each_with_object({}) do |(key, value), final_configs|
98
+ agent_configs = {}
99
+ security_configs = {}
100
+
101
+ defaults.sort.each do |key, value|
71
102
  next if CRITICAL.include?(key) || SKIP.include?(key)
72
103
 
73
104
  next unless public_config?(value) && !deprecated?(value)
74
105
 
75
- sanitized_description = sanitize_description(value[:description])
76
- description = format_description(sanitized_description)
77
- default = default_value(key, value)
78
- final_configs[key] = {description: description, default: default}
106
+ # TODO: OLD RUBIES < 2.6
107
+ # Remove `to_s`. `start_with?` doesn't accept symbols in Ruby <2.6
108
+ if key.to_s.start_with?('security.')
109
+ description, default = build_config(key, value)
110
+ security_configs[key] = {description: description, default: default}
111
+ next
112
+ end
113
+
114
+ description, default = build_config(key, value)
115
+ agent_configs[key] = {description: description, default: default}
79
116
  end
117
+
118
+ [agent_configs, security_configs]
119
+ end
120
+
121
+ def self.build_config(key, value)
122
+ sanitized_description = sanitize_description(value[:description])
123
+ description = format_description(sanitized_description)
124
+ default = default_value(key, value)
125
+
126
+ [description, default]
80
127
  end
81
128
 
82
129
  def self.public_config?(value)
@@ -126,15 +173,30 @@ module NewRelicYML
126
173
  end
127
174
  end
128
175
 
129
- def self.build_string(defaults)
130
- configs = get_configs(defaults)
131
- yml_string = ''
176
+ def self.agent_configs_yml(agent_configs)
177
+ agent_yml = ''
178
+ agent_configs.each do |key, value|
179
+ agent_yml += "#{value[:description]}\n # #{key}: #{value[:default]}\n\n"
180
+ end
181
+
182
+ agent_yml
183
+ end
132
184
 
133
- configs.each do |key, value|
134
- yml_string += "#{value[:description]}\n # #{key}: #{value[:default]}\n\n"
185
+ def self.security_configs_yml(security_configs)
186
+ security_yml = ''
187
+ security_configs.each do |key, value|
188
+ security_yml += "#{value[:description]}\n # #{key}: #{value[:default]}\n\n"
135
189
  end
136
190
 
137
- yml_string
191
+ security_yml
192
+ end
193
+
194
+ def self.build_string(defaults)
195
+ agent_configs, security_configs = get_configs(defaults)
196
+ agent_string = agent_configs_yml(agent_configs)
197
+ security_string = security_configs_yml(security_configs)
198
+
199
+ agent_string + SECURITY_BEGIN + security_string + SECURITY_END + "\n"
138
200
  end
139
201
 
140
202
  # :nocov:
@@ -103,7 +103,7 @@ class Instrumentation < Thor
103
103
  <<-CONFIG
104
104
  :'instrumentation.#{snake_name}' => {
105
105
  :default => 'auto',
106
- :documentation_default => 'auto'
106
+ :documentation_default => 'auto',
107
107
  :public => true,
108
108
  :type => String,
109
109
  :dynamic_name => true,
@@ -2,27 +2,30 @@
2
2
  # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
3
  # frozen_string_literal: true
4
4
 
5
- require_relative '<%= @snake_name.downcase %>/instrumentation'
6
- require_relative '<%= @snake_name.downcase %>/chain'
7
- require_relative '<%= @snake_name.downcase %>/prepend'
8
-
9
5
  DependencyDetection.defer do
10
- named :<%= @name.match?(/\-|\_/) ? "'#{@snake_name}'" : @name.downcase %>
6
+ named :<%= @snake_name %>
11
7
 
12
8
  depends_on do
13
9
  # The class that needs to be defined to prepend/chain onto. This can be used
14
10
  # to determine whether the library is installed.
15
- defined?(::<%= @class_name %>)
11
+ defined?(<%= @class_name %>)
16
12
  # Add any additional requirements to verify whether this instrumentation
17
13
  # should be installed
18
14
  end
19
15
 
20
16
  executes do
21
- ::NewRelic::Agent.logger.info('Installing <%= @name.downcase %> instrumentation')
17
+ require_relative '<%= @snake_name.downcase %>/instrumentation'
22
18
 
19
+ # prepend_instrument and chain_instrument call extract_supportability_name
20
+ # to get the library name for supportability metrics and info-level logging.
21
+ # This is done by spliting on the 2nd to last spot of the instrumented
22
+ # module. If this isn't how we want the name to appear, pass in the desired
23
+ # name as a third argument.
23
24
  if use_prepend?
24
- prepend_instrument ::<%= @class_name %>, NewRelic::Agent::Instrumentation::<%= @class_name %>::Prepend
25
+ require_relative '<%= @snake_name.downcase %>/prepend'
26
+ prepend_instrument <%= @class_name %>, NewRelic::Agent::Instrumentation::<%= @class_name %>::Prepend
25
27
  else
28
+ require_relative '<%= @snake_name.downcase %>/chain'
26
29
  chain_instrument NewRelic::Agent::Instrumentation::<%= @class_name %>::Chain
27
30
  end
28
31
  end