newrelic_rpm 9.9.0 → 9.21.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +1 -0
  3. data/CHANGELOG.md +463 -1
  4. data/CONTRIBUTING.md +2 -2
  5. data/README.md +16 -17
  6. data/Rakefile +1 -1
  7. data/lib/boot/strap.rb +102 -0
  8. data/lib/new_relic/agent/agent.rb +6 -0
  9. data/lib/new_relic/agent/agent_helpers/connect.rb +3 -0
  10. data/lib/new_relic/agent/agent_helpers/harvest.rb +3 -0
  11. data/lib/new_relic/agent/agent_helpers/shutdown.rb +3 -0
  12. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -0
  13. data/lib/new_relic/agent/agent_helpers/startup.rb +7 -0
  14. data/lib/new_relic/agent/agent_logger.rb +1 -0
  15. data/lib/new_relic/agent/aws.rb +68 -0
  16. data/lib/new_relic/agent/configuration/default_source.rb +603 -105
  17. data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
  18. data/lib/new_relic/agent/configuration/manager.rb +28 -2
  19. data/lib/new_relic/agent/configuration/yaml_source.rb +7 -2
  20. data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
  21. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  22. data/lib/new_relic/agent/database.rb +41 -1
  23. data/lib/new_relic/agent/database_adapter.rb +1 -1
  24. data/lib/new_relic/agent/datastores/redis.rb +1 -1
  25. data/lib/new_relic/agent/distributed_tracing/cross_app_tracing.rb +1 -1
  26. data/lib/new_relic/agent/distributed_tracing.rb +4 -2
  27. data/lib/new_relic/agent/error_collector.rb +37 -10
  28. data/lib/new_relic/agent/external.rb +2 -0
  29. data/lib/new_relic/agent/health_check.rb +136 -0
  30. data/lib/new_relic/agent/http_clients/uri_util.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/action_dispatch.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/action_dispatch_subscriber.rb +1 -1
  33. data/lib/new_relic/agent/instrumentation/action_mailbox.rb +1 -1
  34. data/lib/new_relic/agent/instrumentation/action_mailer.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/active_job.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/active_job_subscriber.rb +6 -2
  37. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  38. data/lib/new_relic/agent/instrumentation/active_record.rb +7 -12
  39. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +7 -3
  40. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +11 -9
  41. data/lib/new_relic/agent/instrumentation/active_record_prepend.rb +2 -2
  42. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +9 -16
  43. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
  44. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
  45. data/lib/new_relic/agent/instrumentation/async_http.rb +2 -3
  46. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/chain.rb +21 -0
  47. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/instrumentation.rb +66 -0
  48. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/prepend.rb +15 -0
  49. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose.rb +22 -0
  50. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/chain.rb +21 -0
  51. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/instrumentation.rb +91 -0
  52. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/prepend.rb +15 -0
  53. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis.rb +22 -0
  54. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
  55. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
  56. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
  57. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
  58. data/lib/new_relic/agent/instrumentation/aws_sqs/chain.rb +37 -0
  59. data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
  60. data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
  61. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +23 -0
  62. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  63. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
  64. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -3
  65. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +4 -0
  66. data/lib/new_relic/agent/instrumentation/curb.rb +4 -5
  67. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
  68. data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
  69. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +64 -0
  70. data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
  71. data/lib/new_relic/agent/instrumentation/dynamodb.rb +23 -0
  72. data/lib/new_relic/agent/instrumentation/elasticsearch/chain.rb +1 -2
  73. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +53 -7
  74. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +1 -3
  75. data/lib/new_relic/agent/instrumentation/ethon.rb +1 -5
  76. data/lib/new_relic/agent/instrumentation/excon.rb +1 -17
  77. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +1 -1
  78. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +1 -1
  79. data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
  80. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +0 -3
  81. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  82. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
  83. data/lib/new_relic/agent/instrumentation/httpclient.rb +1 -5
  84. data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
  85. data/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb +1 -1
  86. data/lib/new_relic/agent/instrumentation/httpx.rb +1 -5
  87. data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
  88. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  89. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  90. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  91. data/lib/new_relic/agent/instrumentation/logstasher.rb +25 -0
  92. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +1 -1
  93. data/lib/new_relic/agent/instrumentation/memcache/helper.rb +2 -2
  94. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +1 -1
  95. data/lib/new_relic/agent/instrumentation/memcache/prepend.rb +1 -1
  96. data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
  97. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -1
  98. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +3 -3
  99. data/lib/new_relic/agent/instrumentation/net_http.rb +2 -1
  100. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +0 -2
  101. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  102. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  103. data/lib/{tasks/instrumentation_generator/templates/instrumentation.tt → new_relic/agent/instrumentation/opensearch/prepend.rb} +4 -4
  104. data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
  105. data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
  106. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  107. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  108. data/lib/new_relic/agent/instrumentation/rake.rb +1 -2
  109. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
  110. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  111. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
  112. data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
  113. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  114. data/lib/new_relic/agent/instrumentation/redis/constants.rb +2 -2
  115. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  116. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  117. data/lib/new_relic/agent/instrumentation/redis.rb +11 -5
  118. data/lib/new_relic/agent/instrumentation/resque.rb +8 -6
  119. data/lib/new_relic/agent/instrumentation/roda.rb +5 -5
  120. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  121. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  122. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
  123. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
  124. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +2 -2
  125. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delay_extensions.rb +24 -0
  126. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delayed_class.rb +2 -2
  127. data/lib/new_relic/agent/instrumentation/sidekiq.rb +9 -15
  128. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
  129. data/lib/new_relic/agent/instrumentation/stripe.rb +1 -1
  130. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +22 -1
  131. data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
  132. data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
  133. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +2 -2
  134. data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
  135. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +11 -5
  136. data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
  137. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  138. data/lib/new_relic/agent/llm/chat_completion_summary.rb +1 -1
  139. data/lib/new_relic/agent/llm/embedding.rb +1 -1
  140. data/lib/new_relic/agent/local_log_decorator.rb +20 -3
  141. data/lib/new_relic/agent/log_event_aggregator.rb +119 -28
  142. data/lib/new_relic/agent/logging.rb +1 -1
  143. data/lib/new_relic/agent/messaging.rb +16 -5
  144. data/lib/new_relic/agent/method_tracer.rb +3 -0
  145. data/lib/new_relic/agent/monitors/inbound_request_monitor.rb +1 -1
  146. data/lib/new_relic/agent/monitors/synthetics_monitor.rb +1 -1
  147. data/lib/new_relic/agent/new_relic_service/json_marshaller.rb +2 -2
  148. data/lib/new_relic/agent/new_relic_service.rb +8 -2
  149. data/lib/new_relic/agent/opentelemetry/context/propagation/trace_propagator.rb +66 -0
  150. data/lib/new_relic/agent/opentelemetry/context/propagation.rb +15 -0
  151. data/lib/{tasks/instrumentation_generator/templates/Envfile.tt → new_relic/agent/opentelemetry/context.rb} +9 -5
  152. data/lib/new_relic/agent/opentelemetry/trace/span.rb +31 -0
  153. data/lib/new_relic/agent/opentelemetry/trace/tracer.rb +129 -0
  154. data/lib/new_relic/agent/opentelemetry/trace/tracer_provider.rb +18 -0
  155. data/lib/new_relic/agent/opentelemetry/trace.rb +15 -0
  156. data/lib/new_relic/agent/opentelemetry/transaction_patch.rb +69 -0
  157. data/lib/new_relic/agent/opentelemetry_bridge.rb +32 -0
  158. data/lib/new_relic/agent/parameter_filtering.rb +1 -1
  159. data/lib/new_relic/agent/samplers/cpu_sampler.rb +1 -1
  160. data/lib/new_relic/agent/samplers/memory_sampler.rb +1 -1
  161. data/lib/new_relic/agent/serverless_handler.rb +247 -12
  162. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  163. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  164. data/lib/new_relic/agent/span_event_primitive.rb +16 -11
  165. data/lib/new_relic/agent/system_info.rb +14 -0
  166. data/lib/new_relic/agent/threading/backtrace_node.rb +10 -1
  167. data/lib/new_relic/agent/tracer.rb +1 -1
  168. data/lib/new_relic/agent/transaction/abstract_segment.rb +2 -1
  169. data/lib/new_relic/agent/transaction/datastore_segment.rb +1 -1
  170. data/lib/new_relic/agent/transaction/distributed_tracer.rb +3 -3
  171. data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
  172. data/lib/new_relic/agent/transaction/message_broker_segment.rb +4 -1
  173. data/lib/new_relic/agent/transaction/request_attributes.rb +14 -7
  174. data/lib/new_relic/agent/transaction/trace_context.rb +34 -5
  175. data/lib/new_relic/agent/transaction/tracing.rb +3 -3
  176. data/lib/new_relic/agent/transaction.rb +4 -7
  177. data/lib/new_relic/agent/transaction_time_aggregator.rb +1 -1
  178. data/lib/new_relic/agent/utilization/ecs.rb +22 -0
  179. data/lib/new_relic/agent/utilization/ecs_v4.rb +22 -0
  180. data/lib/new_relic/agent/utilization_data.rb +40 -5
  181. data/lib/new_relic/agent/vm/c_ruby_vm.rb +3 -3
  182. data/lib/new_relic/agent.rb +124 -2
  183. data/lib/new_relic/constants.rb +1 -0
  184. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  185. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  186. data/lib/new_relic/control/frameworks/rails4.rb +1 -3
  187. data/lib/new_relic/control/instance_methods.rb +6 -0
  188. data/lib/new_relic/control/instrumentation.rb +1 -1
  189. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  190. data/lib/new_relic/control/security_interface.rb +57 -0
  191. data/lib/new_relic/control.rb +1 -1
  192. data/lib/new_relic/dependency_detection.rb +11 -14
  193. data/lib/new_relic/environment_report.rb +2 -2
  194. data/lib/new_relic/helper.rb +22 -0
  195. data/lib/new_relic/language_support.rb +3 -1
  196. data/lib/new_relic/local_environment.rb +1 -4
  197. data/lib/new_relic/rack/browser_monitoring.rb +20 -8
  198. data/lib/new_relic/version.rb +1 -1
  199. data/lib/sequel/extensions/new_relic_instrumentation.rb +3 -2
  200. data/lib/tasks/config.rake +7 -3
  201. data/lib/tasks/gha.rake +31 -0
  202. data/lib/tasks/helpers/config.html.erb +3 -2
  203. data/lib/tasks/helpers/format.rb +1 -1
  204. data/lib/tasks/helpers/newrelicyml.rb +80 -13
  205. data/newrelic.yml +425 -162
  206. data/newrelic_rpm.gemspec +3 -1
  207. data/test/agent_helper.rb +24 -2
  208. metadata +91 -22
  209. data/lib/tasks/instrumentation_generator/README.md +0 -63
  210. data/lib/tasks/instrumentation_generator/TODO.md +0 -33
  211. data/lib/tasks/instrumentation_generator/instrumentation.thor +0 -121
  212. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -21
  213. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -7
  214. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +0 -29
  215. data/lib/tasks/instrumentation_generator/templates/instrumentation_method.tt +0 -3
  216. data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +0 -19
  217. data/lib/tasks/instrumentation_generator/templates/prepend.tt +0 -13
  218. data/lib/tasks/instrumentation_generator/templates/prepend_method.tt +0 -3
  219. data/lib/tasks/instrumentation_generator/templates/test.tt +0 -15
@@ -92,7 +92,11 @@ module NewRelic
92
92
  elsif type == Symbol
93
93
  self[config_key] = value.to_sym
94
94
  elsif type == Array
95
- self[config_key] = value.split(/\s*,\s*/)
95
+ self[config_key] = if DEFAULTS[config_key].key?(:transform)
96
+ DEFAULTS[config_key][:transform].call(value)
97
+ else
98
+ value.split(/\s*,\s*/)
99
+ end
96
100
  elsif type == NewRelic::Agent::Configuration::Boolean
97
101
  if /false|off|no/i.match?(value)
98
102
  self[config_key] = false
@@ -142,6 +142,9 @@ module NewRelic
142
142
  default = enforce_allowlist(key, evaluated)
143
143
  return default if default
144
144
 
145
+ boolean = enforce_boolean(key, value)
146
+ evaluated = boolean if [true, false].include?(boolean)
147
+
145
148
  apply_transformations(key, evaluated)
146
149
  end
147
150
 
@@ -167,6 +170,18 @@ module NewRelic
167
170
  default
168
171
  end
169
172
 
173
+ def enforce_boolean(key, value)
174
+ type = default_source.value_from_defaults(key, :type)
175
+ return unless type == Boolean
176
+
177
+ bool_value = default_source.boolean_for(key, value)
178
+ return bool_value unless bool_value.nil?
179
+
180
+ default = default_source.default_for(key)
181
+ NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
182
+ default
183
+ end
184
+
170
185
  def transform_from_default(key)
171
186
  default_source.transform_for(key)
172
187
  end
@@ -367,6 +382,7 @@ module NewRelic
367
382
  @security_policy_source = nil
368
383
  @high_security_source = nil
369
384
  @environment_source = EnvironmentSource.new
385
+ log_config(:add, @environment_source) # this is the only place the EnvironmentSource is ever created, so we should log it
370
386
  @server_source = nil
371
387
  @manual_source = nil
372
388
  @yaml_source = nil
@@ -382,6 +398,14 @@ module NewRelic
382
398
  def reset_cache
383
399
  return new_cache unless defined?(@cache) && @cache
384
400
 
401
+ # Modifying the @cache hash under JRuby - even with a `synchronize do`
402
+ # block and a `Hash#dup` operation - has been known to cause issues
403
+ # with JRuby for concurrent access of the hash while it is being
404
+ # modified. The hash really only needs to be modified for the benefit
405
+ # of the security agent, so if JRuby is in play and the security agent
406
+ # is not, don't attempt to modify the hash at all and return early.
407
+ return new_cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled']
408
+
385
409
  @lock.synchronize do
386
410
  preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
387
411
  new_cache
@@ -401,8 +425,10 @@ module NewRelic
401
425
  # actually going to be logging the message based on our current log
402
426
  # level, so use a `do` block.
403
427
  NewRelic::Agent.logger.debug do
404
- hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
405
- "Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}"
428
+ source_hash = source.dup.to_h.delete_if { |k, _v| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
429
+ final_hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
430
+
431
+ "Updating config (#{direction}) from #{source.class} with values: #{source_hash}. \nConfig Stack Results: #{final_hash.inspect}"
406
432
  end
407
433
  end
408
434
 
@@ -36,6 +36,7 @@ module NewRelic
36
36
  erb_file = process_erb(raw_file)
37
37
  config = process_yaml(erb_file, env, config, @file_path)
38
38
  rescue ScriptError, StandardError => e
39
+ NewRelic::Agent.agent&.health_check&.update_status(NewRelic::Agent::HealthCheck::FAILED_TO_PARSE_CONFIG)
39
40
  log_failure("Failed to read or parse configuration file at #{path}", e)
40
41
  end
41
42
 
@@ -66,7 +67,7 @@ module NewRelic
66
67
  end
67
68
 
68
69
  def warn_missing_config_file(path)
69
- based_on = 'unknown'
70
+ based_on = NewRelic::UNKNOWN_LOWER
70
71
  source = ::NewRelic::Agent.config.source(:config_path)
71
72
  candidate_paths = [path]
72
73
 
@@ -99,7 +100,11 @@ module NewRelic
99
100
  file.gsub!(/^\s*#.*$/, '#')
100
101
  ERB.new(file).result(binding)
101
102
  rescue ScriptError, StandardError => e
102
- log_failure('Failed ERB processing configuration file. This is typically caused by a Ruby error in <% %> templating blocks in your newrelic.yml file.', e)
103
+ NewRelic::Agent.agent&.health_check&.update_status(NewRelic::Agent::HealthCheck::FAILED_TO_PARSE_CONFIG)
104
+ message = 'Failed ERB processing configuration file. This is typically caused by a Ruby error in <% %> templating blocks in your newrelic.yml file.'
105
+ failure_array = [message, e]
106
+ failure_array << e.backtrace[0] if NewRelic::Helper.version_satisfied?(RUBY_VERSION, '>=', '3.4.0')
107
+ log_failure(*failure_array)
103
108
  nil
104
109
  end
105
110
  end
@@ -7,17 +7,17 @@ module NewRelic
7
7
  module Database
8
8
  module ObfuscationHelpers
9
9
  COMPONENTS_REGEX_MAP = {
10
- :single_quotes => /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
11
- :double_quotes => /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
12
- :dollar_quotes => /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
13
- :uuids => /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
14
- :numeric_literals => /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
15
- :boolean_literals => /\b(?:true|false|null)\b/i,
16
- :hexadecimal_literals => /0x[0-9a-fA-F]+/,
17
- :comments => /(?:#|--).*?(?=\r|\n|$)/i,
18
- :multi_line_comments => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/,
19
- :oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
20
- }
10
+ single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
11
+ double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
12
+ dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
13
+ uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
14
+ numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
15
+ boolean_literals: /\b(?:true|false|null)\b/i,
16
+ hexadecimal_literals: /0x[0-9a-fA-F]+/,
17
+ comments: /(?:#|--).*?(?=\r|\n|$)/i,
18
+ multi_line_comments: %r{/\*.*?\*/}m,
19
+ oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/
20
+ }.freeze
21
21
 
22
22
  DIALECT_COMPONENTS = {
23
23
  :fallback => COMPONENTS_REGEX_MAP.keys,
@@ -2,6 +2,7 @@
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 'singleton'
5
6
  require 'new_relic/agent/database/obfuscation_helpers'
6
7
 
7
8
  module NewRelic
@@ -90,6 +90,42 @@ module NewRelic
90
90
  ConnectionManager.instance.get_connection(config, &connector)
91
91
  end
92
92
 
93
+ def explain_this(statement, use_execute = false)
94
+ if supports_with_connection?
95
+ explain_this_using_with_connection(statement)
96
+ else
97
+ explain_this_using_adapter_connection(statement, use_execute)
98
+ end
99
+ rescue => e
100
+ NewRelic::Agent.logger.error("Couldn't fetch the explain plan for statement: #{e}")
101
+ end
102
+
103
+ def explain_this_using_with_connection(statement)
104
+ ::ActiveRecord::Base.with_connection do |conn|
105
+ conn.exec_query("EXPLAIN #{statement.sql}", "Explain #{statement.name}", statement.binds)
106
+ end
107
+ end
108
+
109
+ def explain_this_using_adapter_connection(statement, use_execute)
110
+ connection = get_connection(statement.config) do
111
+ ::ActiveRecord::Base.send(:"#{statement.config[:adapter]}_connection", statement.config)
112
+ end
113
+
114
+ if use_execute
115
+ connection.execute("EXPLAIN #{statement.sql}")
116
+ else
117
+ connection.exec_query("EXPLAIN #{statement.sql}", "Explain #{statement.name}", statement.binds)
118
+ end
119
+ end
120
+
121
+ # ActiveRecord v7.2.0 introduced with_connection
122
+ def supports_with_connection?
123
+ return @supports_with_connection if defined?(@supports_with_connection)
124
+
125
+ @supports_with_connection = defined?(::ActiveRecord::VERSION::STRING) &&
126
+ NewRelic::Helper.version_satisfied?(ActiveRecord::VERSION::STRING, '>=', '7.2.0')
127
+ end
128
+
93
129
  def close_connections
94
130
  ConnectionManager.instance.close_connections
95
131
  end
@@ -241,9 +277,11 @@ module NewRelic
241
277
  MYSQL_PREFIX = 'mysql'.freeze
242
278
  MYSQL2_PREFIX = 'mysql2'.freeze
243
279
  SQLITE_PREFIX = 'sqlite'.freeze
280
+ TRILOGY_PREFIX = 'trilogy'.freeze
281
+ REDSHIFT_PREFIX = 'redshift'.freeze
244
282
 
245
283
  def symbolized_adapter(adapter)
246
- if adapter.start_with?(POSTGRES_PREFIX) || adapter == POSTGIS_PREFIX
284
+ if adapter.start_with?(POSTGRES_PREFIX) || adapter == POSTGIS_PREFIX || adapter == REDSHIFT_PREFIX
247
285
  :postgres
248
286
  elsif adapter == MYSQL_PREFIX
249
287
  :mysql
@@ -253,6 +291,8 @@ module NewRelic
253
291
  :mysql2
254
292
  elsif adapter.start_with?(SQLITE_PREFIX)
255
293
  :sqlite
294
+ elsif adapter == TRILOGY_PREFIX
295
+ :trilogy
256
296
  else
257
297
  adapter.to_sym
258
298
  end
@@ -25,7 +25,7 @@ module NewRelic
25
25
  end
26
26
 
27
27
  def value
28
- match = VERSIONS.keys.find { |key| version >= Gem::Version.new(key) }
28
+ match = VERSIONS.keys.find { |key| NewRelic::Helper.version_satisfied?(version, '>=', key) }
29
29
  return unless match
30
30
 
31
31
  VERSIONS[match].call(env)
@@ -83,7 +83,7 @@ module NewRelic
83
83
  end
84
84
 
85
85
  def self.is_supported_version?
86
- Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('3.0.0')
86
+ NewRelic::Helper.version_satisfied?(::Redis::VERSION, '>=', '3.0.0')
87
87
  end
88
88
 
89
89
  def self.ellipsize(result, string)
@@ -207,7 +207,7 @@ module NewRelic
207
207
  decoded_appdata.set_encoding(::Encoding::UTF_8) if
208
208
  decoded_appdata.respond_to?(:set_encoding)
209
209
 
210
- ::JSON.load(decoded_appdata)
210
+ ::JSON.parse(decoded_appdata)
211
211
  end
212
212
 
213
213
  def valid_cross_app_id?(xp_id)
@@ -39,13 +39,14 @@ module NewRelic
39
39
  # @return {Transaction} The transaction the headers were inserted from,
40
40
  # or +nil+ if headers were not inserted.
41
41
  #
42
+ # @!scope class
42
43
  # @api public
43
44
  #
44
45
  def insert_distributed_trace_headers(headers = {})
45
46
  record_api_supportability_metric(:insert_distributed_trace_headers)
46
47
 
47
48
  unless Agent.config[:'distributed_tracing.enabled']
48
- NewRelic::Agent.logger.warn('Not configured to insert distributed trace headers')
49
+ NewRelic::Agent.logger.debug('Not configured to insert distributed trace headers')
49
50
  return nil
50
51
  end
51
52
 
@@ -93,13 +94,14 @@ module NewRelic
93
94
  #
94
95
  # @return {Transaction} if successful, +nil+ otherwise
95
96
  #
97
+ # @!scope class
96
98
  # @api public
97
99
  #
98
100
  def accept_distributed_trace_headers(headers, transport_type = NewRelic::HTTP)
99
101
  record_api_supportability_metric(:accept_distributed_trace_headers)
100
102
 
101
103
  unless Agent.config[:'distributed_tracing.enabled']
102
- NewRelic::Agent.logger.warn('Not configured to accept distributed trace headers')
104
+ NewRelic::Agent.logger.debug('Not configured to accept distributed trace headers')
103
105
  return nil
104
106
  end
105
107
 
@@ -110,6 +110,29 @@ module NewRelic
110
110
  false
111
111
  end
112
112
 
113
+ # Neither ignored nor expected errors impact apdex.
114
+ #
115
+ # Ignored errors are checked via `#error_is_ignored?`
116
+ # Expected errors are checked in 2 separate ways:
117
+ # 1. The presence of an `expected: true` attribute key/value pair in the
118
+ # options hash, which will be set if that key/value pair was used in
119
+ # the `notice_error` public API.
120
+ # 2. By calling `#expected?` which in turn calls `ErrorFilter#expected?`
121
+ # which checks for 3 things:
122
+ # - A match for user-defined HTTP status codes to expect
123
+ # - A match for user-defined error classes to expect
124
+ # - A match for user-defined error messages to expect
125
+ def error_affects_apdex?(error, options)
126
+ return false if error_is_ignored?(error)
127
+ return false if options[:expected]
128
+
129
+ !expected?(error, ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code)
130
+ rescue => e
131
+ NewRelic::Agent.logger.error("Could not determine if error '#{error}' should impact Apdex - " \
132
+ "#{e.class}: #{e.message}. Defaulting to 'true' (it should impact Apdex).")
133
+ true
134
+ end
135
+
113
136
  # Calling instance_variable_set on a wrapped Java object in JRuby will
114
137
  # generate a warning unless that object's class has already been marked
115
138
  # as persistent, so we skip tagging of exception objects that are actually
@@ -214,7 +237,8 @@ module NewRelic
214
237
  end
215
238
 
216
239
  def notice_segment_error(segment, exception, options = {})
217
- return if skip_notice_error?(exception)
240
+ status_code = process_http_status_code(exception, options)
241
+ return if skip_notice_error?(exception, status_code)
218
242
 
219
243
  options.merge!(segment.llm_event.error_attributes(exception)) if segment.llm_event
220
244
 
@@ -227,15 +251,13 @@ module NewRelic
227
251
 
228
252
  # See NewRelic::Agent.notice_error for options and commentary
229
253
  def notice_error(exception, options = {}, span_id = nil)
230
- state = ::NewRelic::Agent::Tracer.state
231
- transaction = state.current_transaction
232
- status_code = transaction&.http_response_code
233
-
254
+ status_code = process_http_status_code(exception, options)
234
255
  return if skip_notice_error?(exception, status_code)
235
256
 
236
257
  tag_exception(exception)
237
258
 
238
- if options[:expected] || @error_filter.expected?(exception, status_code)
259
+ state = ::NewRelic::Agent::Tracer.state
260
+ if options[:expected]
239
261
  increment_expected_error_count!(state, exception)
240
262
  else
241
263
  increment_error_count!(state, exception, options)
@@ -243,10 +265,8 @@ module NewRelic
243
265
 
244
266
  noticed_error = create_noticed_error(exception, options)
245
267
  error_trace_aggregator.add_to_error_queue(noticed_error)
246
- transaction = state.current_transaction
247
- payload = transaction&.payload
248
- span_id ||= transaction&.current_segment ? transaction.current_segment.guid : nil
249
- error_event_aggregator.record(noticed_error, payload, span_id)
268
+ span_id ||= state.current_transaction&.current_segment&.guid
269
+ error_event_aggregator.record(noticed_error, state.current_transaction&.payload, span_id)
250
270
  exception
251
271
  rescue => e
252
272
  ::NewRelic::Agent.logger.warn("Failure when capturing error '#{exception}':", e)
@@ -336,6 +356,13 @@ module NewRelic
336
356
  def error_group_callback
337
357
  NewRelic::Agent.error_group_callback
338
358
  end
359
+
360
+ def process_http_status_code(exception, options)
361
+ status_code = ::NewRelic::Agent::Tracer.state.current_transaction&.http_response_code
362
+ options[:expected] = true if !options[:expected] && @error_filter.expected?(exception, status_code)
363
+
364
+ status_code
365
+ end
339
366
  end
340
367
  end
341
368
  end
@@ -29,6 +29,7 @@ module NewRelic
29
29
  #
30
30
  # @param request_metadata [String] received obfuscated request metadata
31
31
  #
32
+ # @!scope class
32
33
  # @api public
33
34
  #
34
35
  def process_request_metadata(request_metadata)
@@ -74,6 +75,7 @@ module NewRelic
74
75
  #
75
76
  # @return [String] obfuscated response metadata to send
76
77
  #
78
+ # @!scope class
77
79
  # @api public
78
80
  #
79
81
  def get_response_metadata
@@ -0,0 +1,136 @@
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
+ module NewRelic
6
+ module Agent
7
+ class HealthCheck
8
+ def initialize
9
+ @start_time = nano_time
10
+ @continue = true
11
+ @status = HEALTHY
12
+ # the following assignments may set @continue = false if they are invalid
13
+ set_enabled
14
+ set_delivery_location
15
+ set_frequency
16
+ end
17
+
18
+ HEALTHY = {healthy: true, last_error: 'NR-APM-000', message: 'Healthy'}.freeze
19
+ INVALID_LICENSE_KEY = {healthy: false, last_error: 'NR-APM-001', message: 'Invalid license key (HTTP status code 401)'}.freeze
20
+ MISSING_LICENSE_KEY = {healthy: false, last_error: 'NR-APM-002', message: 'License key missing in configuration'}.freeze
21
+ FORCED_DISCONNECT = {healthy: false, last_error: 'NR-APM-003', message: 'Forced disconnect received from New Relic (HTTP status code 410)'}.freeze
22
+ HTTP_ERROR = {healthy: false, last_error: 'NR-APM-004', message: 'HTTP error response code [%s] recevied from New Relic while sending data type [%s]'}.freeze
23
+ MISSING_APP_NAME = {healthy: false, last_error: 'NR-APM-005', message: 'Missing application name in agent configuration'}.freeze
24
+ APP_NAME_EXCEEDED = {healthy: false, last_error: 'NR-APM-006', message: 'The maximum number of configured app names (3) exceeded'}.freeze
25
+ PROXY_CONFIG_ERROR = {healthy: false, last_error: 'NR-APM-007', message: 'HTTP Proxy configuration error; response code [%s]'}.freeze
26
+ AGENT_DISABLED = {healthy: false, last_error: 'NR-APM-008', message: 'Agent is disabled via configuration'}.freeze
27
+ FAILED_TO_CONNECT = {healthy: false, last_error: 'NR-APM-009', message: 'Failed to connect to New Relic data collector'}.freeze
28
+ FAILED_TO_PARSE_CONFIG = {healthy: false, last_error: 'NR-APM-010', message: 'Agent config file is not able to be parsed'}.freeze
29
+ SHUTDOWN = {healthy: true, last_error: 'NR-APM-099', message: 'Agent has shutdown'}.freeze
30
+
31
+ def create_and_run_health_check_loop
32
+ return unless health_checks_enabled? && @continue
33
+
34
+ NewRelic::Agent.logger.debug('Agent Control health check conditions met. Starting health checks.')
35
+ NewRelic::Agent.record_metric('Supportability/AgentControl/Health/enabled', 1)
36
+
37
+ Thread.new do
38
+ while @continue
39
+ begin
40
+ sleep @frequency
41
+ write_file
42
+ @continue = false if @status == SHUTDOWN
43
+ rescue StandardError => e
44
+ NewRelic::Agent.logger.error("Aborting Agent Control health check. Error raised: #{e}")
45
+ @continue = false
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def update_status(status, options = [])
52
+ return unless @continue
53
+
54
+ @status = status.dup
55
+ update_message(options) unless options.empty?
56
+ end
57
+
58
+ def healthy?
59
+ @status == HEALTHY
60
+ end
61
+
62
+ private
63
+
64
+ def set_enabled
65
+ @enabled = if ENV['NEW_RELIC_AGENT_CONTROL_ENABLED'] == 'true'
66
+ true
67
+ else
68
+ NewRelic::Agent.logger.debug('NEW_RELIC_AGENT_CONTROL_ENABLED not true, disabling health checks')
69
+ @continue = false
70
+ false
71
+ end
72
+ end
73
+
74
+ def set_delivery_location
75
+ @delivery_location = if ENV['NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION']
76
+ # The spec states file paths for the delivery location will begin with file://
77
+ # This does not create a valid path in Ruby, so remove the prefix when present
78
+ ENV['NEW_RELIC_AGENT_CONTROL_HEALTH_DELIVERY_LOCATION']&.gsub('file://', '')
79
+ else
80
+ # The spec default is 'file:///newrelic/apm/health', but since we're just going to remove it anyway...
81
+ '/newrelic/apm/health'
82
+ end
83
+ end
84
+
85
+ def set_frequency
86
+ @frequency = ENV['NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY'] ? ENV['NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY'].to_i : 5
87
+
88
+ if @frequency <= 0
89
+ NewRelic::Agent.logger.debug('NEW_RELIC_AGENT_CONTROL_HEALTH_FREQUENCY zero or less, disabling health checks')
90
+ @continue = false
91
+ end
92
+ end
93
+
94
+ def contents
95
+ <<~CONTENTS
96
+ healthy: #{@status[:healthy]}
97
+ status: #{@status[:message]}#{last_error}
98
+ start_time_unix_nano: #{@start_time}
99
+ status_time_unix_nano: #{nano_time}
100
+ CONTENTS
101
+ end
102
+
103
+ def last_error
104
+ @status[:healthy] ? '' : "\nlast_error: #{@status[:last_error]}"
105
+ end
106
+
107
+ def nano_time
108
+ Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
109
+ end
110
+
111
+ def file_name
112
+ "health-#{NewRelic::Agent::GuidGenerator.generate_guid(32)}.yml"
113
+ end
114
+
115
+ def write_file
116
+ @file ||= "#{@delivery_location}/#{file_name}"
117
+
118
+ File.write(@file, contents)
119
+ rescue StandardError => e
120
+ NewRelic::Agent.logger.error("Agent Control health check raised an error while writing a file: #{e}")
121
+ @continue = false
122
+ end
123
+
124
+ def health_checks_enabled?
125
+ @enabled && @delivery_location && @frequency > 0
126
+ end
127
+
128
+ def update_message(options)
129
+ @status[:message] = sprintf(@status[:message], *options)
130
+ rescue StandardError => e
131
+ NewRelic::Agent.logger.debug("Error raised while updating Agent Control health check message: #{e}." \
132
+ "options = #{options}, @status[:message] = #{@status[:message]}")
133
+ end
134
+ end
135
+ end
136
+ end
@@ -36,7 +36,7 @@ module NewRelic
36
36
  uri = ::URI.parse(url)
37
37
  end
38
38
  end
39
- uri.host&.downcase!
39
+ uri.host = uri.host&.downcase
40
40
  uri
41
41
  end
42
42
 
@@ -16,7 +16,7 @@ DependencyDetection.defer do
16
16
  defined?(ActionDispatch) &&
17
17
  defined?(ActionPack) &&
18
18
  ActionPack.respond_to?(:gem_version) &&
19
- ActionPack.gem_version >= Gem::Version.new('6.0.0') && # notifications for dispatch added in Rails 6
19
+ NewRelic::Helper.version_satisfied?(ActionPack.gem_version, '>=', '6.0.0') && # notifications for dispatch added in Rails 6
20
20
  !NewRelic::Agent::Instrumentation::ActionDispatchSubscriber.subscribed?
21
21
  end
22
22
 
@@ -45,7 +45,7 @@ module NewRelic
45
45
  end
46
46
 
47
47
  PATTERN = /\A([^\.]+)\.action_dispatch\z/
48
- UNKNOWN = 'unknown'.freeze
48
+ UNKNOWN = NewRelic::UNKNOWN_LOWER
49
49
 
50
50
  METHOD_NAME_MAPPING = Hash.new do |h, k|
51
51
  if PATTERN =~ k
@@ -15,7 +15,7 @@ DependencyDetection.defer do
15
15
  defined?(ActiveSupport) &&
16
16
  defined?(ActionMailbox) &&
17
17
  ActionMailbox.respond_to?(:gem_version) && # 'require "action_mailbox"' doesn't require version...
18
- ActionMailbox.gem_version >= Gem::Version.new('7.1.0.alpha') && # notifications added in Rails 7.1
18
+ NewRelic::Helper.version_satisfied?(ActionMailbox.gem_version, '>=', '7.1.0.alpha') && # notifications added in Rails 7.1
19
19
  !NewRelic::Agent::Instrumentation::ActionMailboxSubscriber.subscribed?
20
20
  end
21
21
 
@@ -15,7 +15,7 @@ DependencyDetection.defer do
15
15
  defined?(ActiveSupport) &&
16
16
  defined?(ActionMailer) &&
17
17
  ActionMailer.respond_to?(:gem_version) &&
18
- ActionMailer.gem_version >= Gem::Version.new('5.0') &&
18
+ NewRelic::Helper.version_satisfied?(ActionMailer.gem_version, '>=', '5.0') &&
19
19
  !NewRelic::Agent::Instrumentation::ActionMailerSubscriber.subscribed?
20
20
  end
21
21
 
@@ -30,7 +30,7 @@ DependencyDetection.defer do
30
30
  executes do
31
31
  if defined?(ActiveSupport) &&
32
32
  ActiveJob.respond_to?(:gem_version) &&
33
- ActiveJob.gem_version >= Gem::Version.new('6.0.0') &&
33
+ NewRelic::Helper.version_satisfied?(ActiveJob.gem_version, '>=', '6.0.0') &&
34
34
  !NewRelic::Agent.config[:disable_activejob] &&
35
35
  !NewRelic::Agent::Instrumentation::ActiveJobSubscriber.subscribed?
36
36
  NewRelic::Agent.logger.info('Installing notifications based ActiveJob instrumentation')
@@ -8,7 +8,7 @@ module NewRelic
8
8
  module Agent
9
9
  module Instrumentation
10
10
  class ActiveJobSubscriber < NotificationsSubscriber
11
- PAYLOAD_KEYS = %i[adapter db_runtime error job wait]
11
+ PAYLOAD_KEYS = %i[adapter db_runtime error job wait jobs]
12
12
 
13
13
  def add_segment_params(segment, payload)
14
14
  PAYLOAD_KEYS.each do |key|
@@ -16,8 +16,12 @@ module NewRelic
16
16
  end
17
17
  end
18
18
 
19
+ # NOTE: For `enqueue_all.active_job`, only the first job is used to determine the queue.
20
+ # Therefore, this assumes all jobs given as arguments for perform_all_later share the same queue.
19
21
  def metric_name(name, payload)
20
- queue = payload[:job].queue_name
22
+ job = payload[:job] || payload[:jobs].first
23
+
24
+ queue = job.queue_name
21
25
  method = method_from_name(name)
22
26
  "Ruby/ActiveJob/#{queue}/#{method}"
23
27
  end
@@ -35,17 +35,4 @@ DependencyDetection.defer do
35
35
  end
36
36
  end
37
37
  end
38
-
39
- executes do
40
- next unless Gem::Version.new(ActiveMerchant::VERSION) < Gem::Version.new('1.65.0')
41
-
42
- deprecation_msg = 'The Ruby agent is dropping support for ActiveMerchant versions below 1.65.0 ' \
43
- 'in version 9.0.0. Please upgrade your ActiveMerchant version to continue receiving full support. ' \
44
-
45
- NewRelic::Agent.logger.log_once(
46
- :warn,
47
- :deprecated_active_merchant_version,
48
- deprecation_msg
49
- )
50
- end
51
38
  end