newrelic_rpm 9.7.0 → 9.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +376 -2
  3. data/README.md +17 -18
  4. data/Rakefile +1 -1
  5. data/lib/boot/strap.rb +101 -0
  6. data/lib/new_relic/agent/agent.rb +4 -1
  7. data/lib/new_relic/agent/agent_helpers/connect.rb +10 -8
  8. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
  9. data/lib/new_relic/agent/agent_helpers/startup.rb +2 -1
  10. data/lib/new_relic/agent/agent_logger.rb +3 -1
  11. data/lib/new_relic/agent/aws.rb +68 -0
  12. data/lib/new_relic/agent/configuration/default_source.rb +519 -23
  13. data/lib/new_relic/agent/configuration/environment_source.rb +14 -2
  14. data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
  15. data/lib/new_relic/agent/configuration/manager.rb +51 -8
  16. data/lib/new_relic/agent/configuration/security_policy_source.rb +11 -0
  17. data/lib/new_relic/agent/configuration/yaml_source.rb +2 -0
  18. data/lib/new_relic/agent/connect/request_builder.rb +1 -1
  19. data/lib/new_relic/agent/custom_event_aggregator.rb +27 -1
  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 +39 -0
  23. data/lib/new_relic/agent/distributed_tracing/distributed_trace_payload.rb +1 -5
  24. data/lib/new_relic/agent/error_collector.rb +39 -10
  25. data/lib/new_relic/agent/harvester.rb +1 -1
  26. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  27. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -8
  28. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +3 -0
  29. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +1 -12
  30. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger/instrumentation.rb +7 -3
  31. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
  32. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
  33. data/lib/new_relic/agent/instrumentation/async_http.rb +4 -3
  34. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
  35. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
  36. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
  37. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
  38. data/lib/new_relic/agent/instrumentation/aws_sqs/chain.rb +37 -0
  39. data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
  40. data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
  41. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +23 -0
  42. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  43. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
  44. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -2
  45. data/lib/new_relic/agent/instrumentation/curb.rb +3 -4
  46. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
  47. data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
  48. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +64 -0
  49. data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
  50. data/lib/new_relic/agent/instrumentation/dynamodb.rb +23 -0
  51. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +58 -8
  52. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +0 -2
  53. data/lib/new_relic/agent/instrumentation/ethon.rb +0 -4
  54. data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
  55. data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
  56. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  57. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
  58. data/lib/new_relic/agent/instrumentation/grpc_server.rb +1 -1
  59. data/lib/new_relic/agent/instrumentation/httpclient.rb +0 -1
  60. data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
  61. data/lib/new_relic/agent/instrumentation/httpx.rb +0 -4
  62. data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
  63. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  64. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  65. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  66. data/lib/new_relic/agent/instrumentation/logstasher.rb +25 -0
  67. data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
  68. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +6 -0
  69. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  70. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  71. data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
  72. data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
  73. data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
  74. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  75. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  76. data/lib/new_relic/agent/instrumentation/rake.rb +0 -1
  77. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
  78. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  79. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
  80. data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
  81. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  82. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  83. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  84. data/lib/new_relic/agent/instrumentation/redis.rb +11 -5
  85. data/lib/new_relic/agent/instrumentation/resque.rb +0 -4
  86. data/lib/new_relic/agent/instrumentation/roda.rb +4 -4
  87. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  88. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  89. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
  90. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
  91. data/lib/new_relic/agent/instrumentation/ruby_openai/chain.rb +36 -0
  92. data/lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb +196 -0
  93. data/lib/new_relic/agent/instrumentation/ruby_openai/prepend.rb +20 -0
  94. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +35 -0
  95. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
  96. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
  97. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +22 -1
  98. data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
  99. data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
  100. data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
  101. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +13 -6
  102. data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
  103. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  104. data/lib/new_relic/agent/llm/chat_completion_message.rb +25 -0
  105. data/lib/new_relic/agent/llm/chat_completion_summary.rb +66 -0
  106. data/lib/new_relic/agent/llm/embedding.rb +60 -0
  107. data/lib/new_relic/agent/llm/llm_event.rb +95 -0
  108. data/lib/new_relic/agent/llm/response_headers.rb +80 -0
  109. data/lib/new_relic/agent/llm.rb +49 -0
  110. data/lib/new_relic/agent/local_log_decorator.rb +8 -1
  111. data/lib/new_relic/agent/log_event_aggregator.rb +120 -44
  112. data/lib/new_relic/agent/messaging.rb +11 -5
  113. data/lib/new_relic/agent/new_relic_service.rb +12 -2
  114. data/lib/new_relic/agent/serverless_handler.rb +400 -0
  115. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  116. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  117. data/lib/new_relic/agent/span_event_primitive.rb +8 -10
  118. data/lib/new_relic/agent/system_info.rb +14 -0
  119. data/lib/new_relic/agent/threading/agent_thread.rb +1 -2
  120. data/lib/new_relic/agent/tracer.rb +5 -5
  121. data/lib/new_relic/agent/transaction/abstract_segment.rb +1 -1
  122. data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
  123. data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
  124. data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
  125. data/lib/new_relic/agent/transaction/tracing.rb +2 -2
  126. data/lib/new_relic/agent/transaction.rb +2 -6
  127. data/lib/new_relic/agent/transaction_error_primitive.rb +23 -19
  128. data/lib/new_relic/agent.rb +198 -10
  129. data/lib/new_relic/constants.rb +2 -0
  130. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  131. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  132. data/lib/new_relic/control/frameworks/rails4.rb +1 -3
  133. data/lib/new_relic/control/instance_methods.rb +8 -0
  134. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  135. data/lib/new_relic/control/security_interface.rb +57 -0
  136. data/lib/new_relic/control.rb +1 -1
  137. data/lib/new_relic/dependency_detection.rb +10 -5
  138. data/lib/new_relic/environment_report.rb +2 -2
  139. data/lib/new_relic/helper.rb +15 -0
  140. data/lib/new_relic/language_support.rb +3 -1
  141. data/lib/new_relic/local_environment.rb +14 -10
  142. data/lib/new_relic/rack/browser_monitoring.rb +28 -12
  143. data/lib/new_relic/supportability_helper.rb +2 -0
  144. data/lib/new_relic/thread_local_storage.rb +31 -0
  145. data/lib/new_relic/version.rb +2 -2
  146. data/lib/sequel/extensions/new_relic_instrumentation.rb +3 -2
  147. data/lib/tasks/config.rake +8 -3
  148. data/lib/tasks/gha.rake +31 -0
  149. data/lib/tasks/helpers/config.html.erb +3 -2
  150. data/lib/tasks/helpers/format.rb +1 -1
  151. data/lib/tasks/helpers/newrelicyml.rb +76 -13
  152. data/lib/tasks/instrumentation_generator/instrumentation.thor +31 -22
  153. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -1
  154. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -1
  155. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +11 -8
  156. data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +1 -1
  157. data/newrelic.yml +387 -143
  158. data/newrelic_rpm.gemspec +2 -0
  159. data/test/agent_helper.rb +17 -2
  160. metadata +80 -3
@@ -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
 
@@ -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
@@ -60,21 +60,22 @@ module NewRelic
60
60
 
61
61
  def discover_dispatcher
62
62
  dispatchers = %w[
63
+ serverless
64
+ puma
65
+ sidekiq
66
+ falcon
67
+ delayed_job
68
+ unicorn
63
69
  passenger
70
+ resque
64
71
  torquebox
65
72
  trinidad
66
73
  glassfish
67
- resque
68
- sidekiq
69
- delayed_job
70
- puma
71
74
  thin
72
75
  mongrel
73
76
  litespeed
74
- unicorn
75
77
  webrick
76
78
  fastcgi
77
- falcon
78
79
  ]
79
80
  while dispatchers.any? && @discovered_dispatcher.nil?
80
81
  send('check_for_' + (dispatchers.shift))
@@ -141,10 +142,7 @@ module NewRelic
141
142
  end
142
143
 
143
144
  def check_for_falcon
144
- return unless defined?(::Falcon::Server) &&
145
- NewRelic::LanguageSupport.object_space_usable?
146
-
147
- @discovered_dispatcher = :falcon if find_class_in_object_space(::Falcon::Server)
145
+ @discovered_dispatcher = :falcon if defined?(::Falcon::Server) && File.basename($PROGRAM_NAME) == 'falcon'
148
146
  end
149
147
 
150
148
  def check_for_delayed_job
@@ -198,6 +196,12 @@ module NewRelic
198
196
  @discovered_dispatcher = :passenger
199
197
  end
200
198
 
199
+ def check_for_serverless
200
+ return unless NewRelic::Agent.config[:'serverless_mode.enabled']
201
+
202
+ @discovered_dispatcher = :serverless
203
+ end
204
+
201
205
  public
202
206
 
203
207
  # outputs a human-readable description
@@ -20,15 +20,15 @@ module NewRelic
20
20
  # examine in order to look for a RUM insertion point.
21
21
  SCAN_LIMIT = 50_000
22
22
 
23
- CONTENT_TYPE = 'Content-Type'.freeze
24
- CONTENT_DISPOSITION = 'Content-Disposition'.freeze
25
- CONTENT_LENGTH = 'Content-Length'.freeze
26
- ATTACHMENT = 'attachment'.freeze
27
- TEXT_HTML = 'text/html'.freeze
23
+ CONTENT_TYPE = 'Content-Type'
24
+ CONTENT_DISPOSITION = 'Content-Disposition'
25
+ CONTENT_LENGTH = 'Content-Length'
26
+ ATTACHMENT = /attachment/.freeze
27
+ TEXT_HTML = %r{text/html}.freeze
28
28
 
29
- BODY_START = '<body'.freeze
30
- HEAD_START = '<head'.freeze
31
- GT = '>'.freeze
29
+ BODY_START = '<body'
30
+ HEAD_START = '<head'
31
+ GT = '>'
32
32
 
33
33
  ALREADY_INSTRUMENTED_KEY = 'newrelic.browser_monitoring_already_instrumented'
34
34
  CHARSET_RE = /<\s*meta[^>]+charset\s*=[^>]*>/im.freeze
@@ -38,7 +38,7 @@ module NewRelic
38
38
  result = @app.call(env)
39
39
  (status, headers, response) = result
40
40
 
41
- js_to_inject = NewRelic::Agent.browser_timing_header
41
+ js_to_inject = NewRelic::Agent.browser_timing_header(nonce(env))
42
42
  if (js_to_inject != NewRelic::EMPTY_STR) && should_instrument?(env, status, headers)
43
43
  response_string = autoinstrument_source(response, js_to_inject)
44
44
  if headers.key?(CONTENT_LENGTH)
@@ -58,6 +58,14 @@ module NewRelic
58
58
  end
59
59
  end
60
60
 
61
+ def nonce(env)
62
+ return unless NewRelic::Agent.config[:'browser_monitoring.content_security_policy_nonce']
63
+ return unless NewRelic::Agent.config[:framework] == :rails_notifications
64
+ return unless defined?(ActionDispatch::ContentSecurityPolicy::Request)
65
+
66
+ env[ActionDispatch::ContentSecurityPolicy::Request::NONCE]
67
+ end
68
+
61
69
  def should_instrument?(env, status, headers)
62
70
  NewRelic::Agent.config[:'browser_monitoring.auto_instrument'] &&
63
71
  status == 200 &&
@@ -65,6 +73,10 @@ module NewRelic
65
73
  html?(headers) &&
66
74
  !attachment?(headers) &&
67
75
  !streaming?(env, headers)
76
+ rescue StandardError => e
77
+ NewRelic::Agent.logger.error('RUM instrumentation applicability check failed on exception:' \
78
+ "#{e.class} - #{e.message}")
79
+ false
68
80
  end
69
81
 
70
82
  private
@@ -100,15 +112,19 @@ module NewRelic
100
112
 
101
113
  def html?(headers)
102
114
  # needs else branch coverage
103
- headers[CONTENT_TYPE] && headers[CONTENT_TYPE].include?(TEXT_HTML) # rubocop:disable Style/SafeNavigation
115
+ headers[CONTENT_TYPE]&.match?(TEXT_HTML)
104
116
  end
105
117
 
106
118
  def attachment?(headers)
107
- headers[CONTENT_DISPOSITION]&.include?(ATTACHMENT)
119
+ headers[CONTENT_DISPOSITION]&.match?(ATTACHMENT)
108
120
  end
109
121
 
110
122
  def streaming?(env, headers)
111
- # Chunked transfer encoding is a streaming data transfer mechanism available only in HTTP/1.1
123
+ # Up until version 8.0, Rails would set 'Transfer-Encoding' to 'chunked'
124
+ # to trigger the desired HTTP/1.1 based streaming functionality in Rack.
125
+ # With version v8.0+, Rails assumes that the web server will be using
126
+ # Rack v3+ or an equally modern alternative and simply leaves the
127
+ # streaming behavior up to them.
112
128
  return true if headers && headers['Transfer-Encoding'] == 'chunked'
113
129
 
114
130
  defined?(ActionController::Live) &&
@@ -43,9 +43,11 @@ module NewRelic
43
43
  :process_response_metadata,
44
44
  :record_custom_event,
45
45
  :record_metric,
46
+ :record_llm_feedback_event,
46
47
  :recording_web_transaction?,
47
48
  :require_test_helper,
48
49
  :set_error_group_callback,
50
+ :set_llm_token_count_callback,
49
51
  :set_segment_callback,
50
52
  :set_sql_obfuscator,
51
53
  :set_transaction_name,
@@ -0,0 +1,31 @@
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 ThreadLocalStorage
7
+ def self.get(thread, key)
8
+ if Agent.config[:thread_local_tracer_state]
9
+ thread.thread_variable_get(key)
10
+ else
11
+ thread[key]
12
+ end
13
+ end
14
+
15
+ def self.set(thread, key, value)
16
+ if Agent.config[:thread_local_tracer_state]
17
+ thread.thread_variable_set(key, value)
18
+ else
19
+ thread[key] = value
20
+ end
21
+ end
22
+
23
+ def self.[](key)
24
+ get(::Thread.current, key)
25
+ end
26
+
27
+ def self.[]=(key, value)
28
+ set(::Thread.current, key, value)
29
+ end
30
+ end
31
+ end
@@ -6,8 +6,8 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 7
10
- TINY = 0
9
+ MINOR = 16
10
+ TINY = 1
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
13
13
  end
@@ -78,8 +78,9 @@ module Sequel
78
78
  end
79
79
 
80
80
  THREAD_SAFE_CONNECTION_POOL_CLASSES = [
81
- (defined?(::Sequel::ThreadedConnectionPool) && ::Sequel::ThreadedConnectionPool)
82
- ].freeze
81
+ (defined?(::Sequel::ThreadedConnectionPool) && ::Sequel::ThreadedConnectionPool),
82
+ (defined?(::Sequel::TimedQueueConnectionPool) && RUBY_VERSION >= '3.2' && ::Sequel::TimedQueueConnectionPool)
83
+ ].compact.freeze
83
84
 
84
85
  def explainer_for(sql)
85
86
  proc do |*|
@@ -20,14 +20,19 @@ namespace :newrelic do
20
20
  ATTRIBUTES => '[Attributes](/docs/features/agent-attributes) are key-value pairs containing information that determines the properties of an event or transaction. These key-value pairs can be viewed within transaction traces in APM, traced errors in APM, transaction events in dashboards, and page views in dashboards. You can customize exactly which attributes will be sent to each of these destinations',
21
21
  'transaction_tracer' => 'The [transaction traces](/docs/apm/traces/transaction-traces/transaction-traces) feature collects detailed information from a selection of transactions, including a summary of the calling sequence, a breakdown of time spent, and a list of SQL queries and their query plans (on mysql and postgresql). Available features depend on your New Relic subscription level.',
22
22
  'error_collector' => "The agent collects and reports all uncaught exceptions by default. These configuration options allow you to customize the error collection.\n\nFor information on ignored and expected errors, [see this page on Error Analytics in APM](/docs/agents/manage-apm-agents/agent-data/manage-errors-apm-collect-ignore-or-mark-expected/). To set expected errors via the `NewRelic::Agent.notice_error` Ruby method, [consult the Ruby agent API](/docs/agents/ruby-agent/api-guides/sending-handled-errors-new-relic/).",
23
- 'browser_monitoring' => "The browser monitoring [page load timing](/docs/browser/new-relic-browser/page-load-timing/page-load-timing-process) feature (sometimes referred to as real user monitoring or RUM) gives you insight into the performance real users are experiencing with your website. This is accomplished by measuring the time it takes for your users' browsers to download and render your web pages by injecting a small amount of JavaScript code into the header and footer of each page.",
23
+ 'browser_monitoring' => "The <InlinePopover type='browser' /> [page load timing](/docs/browser/new-relic-browser/page-load-timing/page-load-timing-process) feature (sometimes referred to as real user monitoring or RUM) gives you insight into the performance real users are experiencing with your website. This is accomplished by measuring the time it takes for your users' browsers to download and render your web pages by injecting a small amount of JavaScript code into the header and footer of each page.",
24
24
  'application_logging' => "The Ruby agent supports [APM logs in context](/docs/apm/new-relic-apm/getting-started/get-started-logs-context). For some tips on configuring logs for the Ruby agent, see [Configure Ruby logs in context](/docs/logs/logs-context/configure-logs-context-ruby).\n\nAvailable logging-related config options include:",
25
- 'analytics_events' => '[New Relic dashboards](/docs/query-your-data/explore-query-data/dashboards/introduction-new-relic-one-dashboards) is a resource to gather and visualize data about your software and what it says about your business. With it you can quickly and easily create real-time dashboards to get immediate answers about end-user experiences, clickstreams, mobile activities, and server transactions.'
25
+ 'analytics_events' => '[New Relic dashboards](/docs/query-your-data/explore-query-data/dashboards/introduction-new-relic-one-dashboards) is a resource to gather and visualize data about your software and what it says about your business. With it you can quickly and easily create real-time dashboards to get immediate answers about end-user experiences, clickstreams, mobile activities, and server transactions.',
26
+ 'ai_monitoring' => "This section includes Ruby agent configurations for setting up AI monitoring.\n\n<Callout variant='important'>You need to enable distributed tracing to capture trace and feedback data. It is turned on by default in Ruby agents 8.0.0 and higher.</Callout>",
27
+ 'security_agent' => "[New Relic Interactive Application Security Testing](https://docs.newrelic.com/docs/iast/introduction/) (IAST) tests your applications for any exploitable vulnerability by replaying the generated HTTP request with vulnerable payloads.\n\n<Callout variant='important'>Run IAST with non-production deployments only to avoid exposing vulnerabilities on your production software. \
28
+ IAST mode requires Ruby agent version 9.12.0 or higher and the [newrelic_security](https://rubygems.org/gems/newrelic_security) gem. Security agent configurations are disabled by default.</Callout>"
26
29
  }
27
30
 
28
31
  NAME_OVERRIDES = {
29
32
  'slow_sql' => 'Slow SQL [#slow-sql]',
30
- 'custom_insights_events' => 'Custom Events [#custom-events]'
33
+ 'custom_insights_events' => 'Custom Events [#custom-events]',
34
+ 'ai_monitoring' => 'AI Monitoring [#ai-monitoring]',
35
+ 'security_agent' => 'Security Agent [#security-agent]'
31
36
  }
32
37
 
33
38
  desc 'Describe available New Relic configuration settings'
@@ -0,0 +1,31 @@
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 'yaml'
6
+ require_relative 'helpers/version_bump'
7
+
8
+ # gha = GitHub Actions
9
+ namespace :gha do
10
+ # See .github/versions.yml
11
+ desc 'Update 3rd party action versions across all workflows'
12
+ task :update_versions do
13
+ gh_dir = File.expand_path('../../../.github', __FILE__)
14
+ info = YAML.load_file(File.join(gh_dir, 'versions.yml'))
15
+ workflows = Dir.glob(File.join(gh_dir, 'workflows', '*.yml'))
16
+ workflows.each do |workflow|
17
+ original = File.read(workflow)
18
+ modified = original.dup
19
+ info.each do |action, settings|
20
+ modified.gsub!(/uses: #{action}.*$/, "uses: #{action}@#{settings[:sha]} # tag #{settings[:tag]}")
21
+ end
22
+
23
+ if original != modified
24
+ File.open(workflow, 'w') { |f| f.puts modified }
25
+ puts "Updated #{workflow} with changes"
26
+ else
27
+ puts "#{workflow} remains unchanged"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -10,6 +10,7 @@ redirects:
10
10
  - /docs/ruby/ruby-agent-configuration
11
11
  - /docs/agents/ruby-agent/installation-and-configuration/ruby-agent-configuration
12
12
  - /docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration
13
+ freshnessValidatedDate: never
13
14
  ---
14
15
 
15
16
  <CONTRIBUTOR_NOTE>
@@ -70,12 +71,12 @@ The Ruby agent looks for the following environment variables, in this order, to
70
71
 
71
72
  If the Ruby agent doesn't detect values for any of those environment variables, it will default the application environment to `development` and read from the `development` section of the `newrelic.yml` config file.
72
73
 
73
- When running the Ruby agent in a Rails app, the agent first looks for the `NEW_RELIC_ENV` environment variable to determine the application environment and which section of the `newrelic.yml` to use. If `NEW_RELIC_ENV` is not present, the agent uses the Rails environment (`RAILS_ENV`).
74
+ When running the Ruby agent in a Rails app, the agent first looks for the `NEW_RELIC_ENV` environment variable to find the application environment and which section of the `newrelic.yml` to use. If `NEW_RELIC_ENV` is not present, the agent uses the Rails environment (`RAILS_ENV`).
74
75
 
75
76
  When you edit the config file, be sure to:
76
77
 
77
78
  * Indent only with two spaces.
78
- * Indent only where relevant, in stanzas such as **`error_collector`**.
79
+ * Indent only where relevant, in sections such as **`error_collector`**.
79
80
 
80
81
  If you do not indent correctly, the agent may throw an `Unable to parse configuration file` error on startup.
81
82
 
@@ -69,7 +69,7 @@ module Format
69
69
 
70
70
  def format_description(value)
71
71
  description = ''
72
- description += '**DEPRECATED** ' if value[:deprecated]
72
+ description += '<DNT>**DEPRECATED**</DNT> ' if value[:deprecated]
73
73
  description += value[:description]
74
74
  description
75
75
  end
@@ -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)
@@ -105,8 +152,9 @@ module NewRelicYML
105
152
  def self.format_description(description)
106
153
  # remove leading and trailing whitespace
107
154
  description.strip!
108
- # wrap text after 80 characters
109
- description.gsub!(/(.{1,80})(\s+|\Z)/, "\\1\n")
155
+ # wrap text after 80 characters, assuming we're at one tabstop's (two
156
+ # spaces') level of indentation already
157
+ description.gsub!(/(.{1,78})(\s+|\Z)/, "\\1\n")
110
158
  # add hashtags to lines
111
159
  description = description.split("\n").map { |line| " # #{line}" }.join("\n")
112
160
 
@@ -125,15 +173,30 @@ module NewRelicYML
125
173
  end
126
174
  end
127
175
 
128
- def self.build_string(defaults)
129
- configs = get_configs(defaults)
130
- 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
131
184
 
132
- configs.each do |key, value|
133
- 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"
134
189
  end
135
190
 
136
- 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"
137
200
  end
138
201
 
139
202
  # :nocov:
@@ -31,15 +31,16 @@ class Instrumentation < Thor
31
31
 
32
32
  def scaffold(name)
33
33
  @name = name
34
+ @snake_name = snake_name(@name)
34
35
  @method = options[:method] if options[:method]
35
36
  @args = options[:args] if options[:args]
36
37
  @class_name = ::NewRelic::LanguageSupport.camelize(name)
37
- base_path = "#{INSTRUMENTATION_ROOT}#{name.downcase}"
38
+ base_path = "#{INSTRUMENTATION_ROOT}#{@snake_name}"
38
39
 
39
40
  empty_directory(base_path)
40
41
  create_instrumentation_files(base_path)
41
- append_to_default_source(name)
42
- append_to_newrelic_yml(name)
42
+ append_to_default_source(@name, @snake_name)
43
+ # append_to_newrelic_yml(@name, @snake_name) # This is now done on release, we don't need it anymore, but leaving it to be sure.
43
44
  create_tests(name)
44
45
  end
45
46
 
@@ -69,53 +70,61 @@ class Instrumentation < Thor
69
70
  def create_tests(name)
70
71
  @name = name
71
72
  @instrumentation_method_global_erb_snippet = '<%= $instrumentation_method %>'
72
- base_path = "#{MULTIVERSE_SUITE_ROOT}#{@name.downcase}"
73
+ @snake_name = snake_name(@name)
74
+ base_path = "#{MULTIVERSE_SUITE_ROOT}#{@snake_name}"
73
75
  empty_directory(base_path)
74
76
  template('templates/Envfile.tt', "#{base_path}/Envfile")
75
- template('templates/test.tt', "#{base_path}/#{@name.downcase}_instrumentation_test.rb")
77
+ template('templates/test.tt', "#{base_path}/#{@snake_name}_instrumentation_test.rb")
76
78
 
77
79
  empty_directory("#{base_path}/config")
78
80
  template('templates/newrelic.yml.tt', "#{base_path}/config/newrelic.yml")
79
81
  end
80
82
 
81
- def append_to_default_source(name)
83
+ def append_to_default_source(name, snake_name)
82
84
  insert_into_file(
83
85
  DEFAULT_SOURCE_LOCATION,
84
- config_block(name.downcase),
85
- after: ":description => 'Controls auto-instrumentation of bunny at start-up. May be one of [auto|prepend|chain|disabled].'
86
+ config_block(name, snake_name),
87
+ after: ":description => 'Controls auto-instrumentation of bunny at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
86
88
  },\n"
87
89
  )
88
90
  end
89
91
 
90
- def append_to_newrelic_yml(name)
92
+ def append_to_newrelic_yml(name, snake_name)
91
93
  insert_into_file(
92
94
  NEWRELIC_YML_LOCATION,
93
- yaml_block(name),
95
+ yaml_block(name, snake_name),
94
96
  after: "# instrumentation.bunny: auto\n"
95
97
  )
96
98
  end
97
99
 
98
- def config_block(name)
99
- <<~CONFIG
100
- :'instrumentation.#{name.downcase}' => {
101
- :default => 'auto',
102
- :public => true,
103
- :type => String,
104
- :dynamic_name => true,
105
- :allowed_from_server => false,
106
- :description => 'Controls auto-instrumentation of the #{name} library at start-up. May be one of [auto|prepend|chain|disabled].'
107
- },
100
+ def config_block(name, snake_name)
101
+ # Don't change to <<~
102
+ # We want to preserve the whitespace so the config is correctly indented
103
+ <<-CONFIG
104
+ :'instrumentation.#{snake_name}' => {
105
+ :default => 'auto',
106
+ :documentation_default => 'auto',
107
+ :public => true,
108
+ :type => String,
109
+ :dynamic_name => true,
110
+ :allowed_from_server => false,
111
+ :description => 'Controls auto-instrumentation of the #{name} library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.'
112
+ },
108
113
  CONFIG
109
114
  end
110
115
 
111
- def yaml_block(name)
116
+ def yaml_block(name, snake_name)
112
117
  <<~HEREDOC
113
118
 
114
119
  # Controls auto-instrumentation of #{name} at start-up.
115
120
  # May be one of [auto|prepend|chain|disabled]
116
- # instrumentation.#{name.downcase}: auto
121
+ # instrumentation.#{snake_name}: auto
117
122
  HEREDOC
118
123
  end
124
+
125
+ def snake_name(name)
126
+ name.downcase.tr('-', '_')
127
+ end
119
128
  end
120
129
 
121
130
  Instrumentation.start(ARGV)
@@ -9,7 +9,6 @@ module NewRelic::Agent::Instrumentation
9
9
  include NewRelic::Agent::Instrumentation::<%= @class_name %>
10
10
 
11
11
  alias_method(:<%= @method.downcase %>_without_new_relic, :<%= @method.downcase %>)
12
- alias_method(:<%= @method.downcase %>, :<%= @method.downcase %>_with_new_relic)
13
12
 
14
13
  def <%= @method.downcase %><%= "(#{@args})" unless @args.empty? %>
15
14
  <%= @method.downcase %>_with_new_relic<%= "(#{@args})" unless @args.empty? %> do
@@ -1,5 +1,4 @@
1
1
  alias_method(:<%= @method.downcase %>_without_new_relic, :<%= @method.downcase %>)
2
- alias_method(:<%= @method.downcase %>, :<%= @method.downcase %>_with_new_relic)
3
2
 
4
3
  def <%= @method.downcase %><%= "(#{@args})" unless @args.empty? %>
5
4
  <%= @method.downcase %>_with_new_relic<%= "(#{@args})" unless @args.empty? %> do