newrelic_rpm 9.10.2 → 9.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -1
  3. data/README.md +1 -1
  4. data/lib/new_relic/agent/agent_logger.rb +1 -0
  5. data/lib/new_relic/agent/configuration/default_source.rb +114 -3
  6. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  7. data/lib/new_relic/agent/error_collector.rb +14 -10
  8. data/lib/new_relic/agent/instrumentation/aws_sqs/chain.rb +37 -0
  9. data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
  10. data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
  11. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +25 -0
  12. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  13. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -1
  14. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  15. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  16. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  17. data/lib/new_relic/agent/instrumentation/logstasher.rb +27 -0
  18. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  19. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  20. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  21. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  22. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  23. data/lib/new_relic/agent/instrumentation/redis.rb +5 -0
  24. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +22 -1
  25. data/lib/new_relic/agent/local_log_decorator.rb +8 -1
  26. data/lib/new_relic/agent/log_event_aggregator.rb +91 -26
  27. data/lib/new_relic/control/instance_methods.rb +1 -0
  28. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  29. data/lib/new_relic/control/security_interface.rb +57 -0
  30. data/lib/new_relic/control.rb +1 -1
  31. data/lib/new_relic/rack/browser_monitoring.rb +11 -7
  32. data/lib/new_relic/version.rb +2 -2
  33. data/lib/tasks/config.rake +5 -2
  34. data/lib/tasks/gha.rake +31 -0
  35. data/lib/tasks/helpers/config.html.erb +1 -1
  36. data/lib/tasks/helpers/format.rb +1 -1
  37. data/lib/tasks/helpers/newrelicyml.rb +3 -2
  38. data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -0
  39. data/newrelic.yml +202 -135
  40. data/newrelic_rpm.gemspec +2 -0
  41. data/test/agent_helper.rb +9 -0
  42. metadata +42 -3
@@ -0,0 +1,21 @@
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::Agent::Instrumentation
6
+ module LogStasher::Chain
7
+ def self.instrument!
8
+ ::LogStasher.singleton_class.class_eval do
9
+ include NewRelic::Agent::Instrumentation::LogStasher
10
+
11
+ alias_method(:build_logstash_event_without_new_relic, :build_logstash_event)
12
+
13
+ def build_logstash_event(*args)
14
+ build_logstash_event_with_new_relic(*args) do
15
+ build_logstash_event_without_new_relic(*args)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
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::Agent::Instrumentation
6
+ module LogStasher
7
+ INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
8
+
9
+ def self.enabled?
10
+ NewRelic::Agent.config[:'instrumentation.logstasher'] != 'disabled'
11
+ end
12
+
13
+ def build_logstash_event_with_new_relic(*args)
14
+ logstasher_event = yield
15
+ log = logstasher_event.instance_variable_get(:@data)
16
+
17
+ ::NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
18
+ ::NewRelic::Agent.agent.log_event_aggregator.record_logstasher_event(log)
19
+ ::NewRelic::Agent::LocalLogDecorator.decorate(log)
20
+
21
+ logstasher_event
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
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::Agent::Instrumentation
6
+ module LogStasher::Prepend
7
+ include NewRelic::Agent::Instrumentation::LogStasher
8
+
9
+ def build_logstash_event(*args)
10
+ build_logstash_event_with_new_relic(*args) { super }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
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_relative 'logstasher/instrumentation'
6
+ require_relative 'logstasher/chain'
7
+ require_relative 'logstasher/prepend'
8
+
9
+ DependencyDetection.defer do
10
+ named :logstasher
11
+
12
+ depends_on do
13
+ defined?(LogStasher) &&
14
+ Gem::Version.new(LogStasher::VERSION) >= Gem::Version.new('1.0.0') &&
15
+ NewRelic::Agent.config[:'application_logging.enabled']
16
+ end
17
+
18
+ executes do
19
+ NewRelic::Agent.logger.info('Installing LogStasher instrumentation')
20
+
21
+ if use_prepend?
22
+ prepend_instrument LogStasher.singleton_class, NewRelic::Agent::Instrumentation::LogStasher::Prepend
23
+ else
24
+ chain_instrument NewRelic::Agent::Instrumentation::LogStasher::Chain
25
+ end
26
+ end
27
+ end
@@ -13,6 +13,7 @@ module NewRelic
13
13
  attr_accessor :_nr_deferred_detection_ran
14
14
  end
15
15
  builder_class._nr_deferred_detection_ran = false
16
+ NewRelic::Control::SecurityInterface.instance.wait = true
16
17
  end
17
18
 
18
19
  def deferred_dependency_check
@@ -21,6 +22,8 @@ module NewRelic
21
22
  NewRelic::Agent.logger.info('Doing deferred dependency-detection before Rack startup')
22
23
  DependencyDetection.detect!
23
24
  self.class._nr_deferred_detection_ran = true
25
+ NewRelic::Control::SecurityInterface.instance.wait = false
26
+ NewRelic::Control::SecurityInterface.instance.init_agent
24
27
  end
25
28
 
26
29
  def check_for_late_instrumentation(app)
@@ -32,15 +32,19 @@ DependencyDetection.defer do
32
32
  NewRelic::Agent::Instrumentation::ActionControllerSubscriber \
33
33
  .subscribe(/^process_action.action_controller$/)
34
34
 
35
- subs = %w[send_file
35
+ subs = %w[exist_fragment?
36
+ expire_fragment
37
+ halted_callback
38
+ read_fragment
39
+ redirect_to
36
40
  send_data
41
+ send_file
37
42
  send_stream
38
- redirect_to
39
- halted_callback
40
- unpermitted_parameters]
43
+ write_fragment
44
+ unpermitted_parameters].map { |s| Regexp.escape(s) }
41
45
 
42
46
  # have to double escape period because its going from string -> regex
43
47
  NewRelic::Agent::Instrumentation::ActionControllerOtherSubscriber \
44
- .subscribe(Regexp.new("^(#{subs.join('|')})\\.action_controller$"))
48
+ .subscribe(Regexp.new("^(?:#{subs.join('|')})\\.action_controller$"))
45
49
  end
46
50
  end
@@ -0,0 +1,26 @@
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::Agent::Instrumentation
6
+ module RedisClient
7
+ module ClusterMiddleware
8
+ include NewRelic::Agent::Instrumentation::Redis
9
+
10
+ # Until we decide to move our Redis instrumentation entirely off patches
11
+ # keep the middleware instrumentation for the call and connect methods
12
+ # limited to the redis-clustering instrumentation.
13
+ #
14
+ # Redis's middleware option does not capture errors as high in the stack
15
+ # as our patches. Leaving the patches for call and connect on the main
16
+ # Redis gem limits the feature disparity our customers experience.
17
+ def call(*args, &block)
18
+ call_with_tracing(args[0]) { super }
19
+ end
20
+
21
+ def connect(*args, &block)
22
+ connect_with_tracing { super }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -9,14 +9,14 @@ module NewRelic::Agent::Instrumentation
9
9
  INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
10
10
 
11
11
  def connect_with_tracing
12
- with_tracing(Constants::CONNECT, database: db) { yield }
12
+ with_tracing(Constants::CONNECT, database: _nr_db) { yield }
13
13
  end
14
14
 
15
15
  def call_with_tracing(command, &block)
16
16
  operation = command[0]
17
17
  statement = ::NewRelic::Agent::Datastores::Redis.format_command(command)
18
18
 
19
- with_tracing(operation, statement: statement, database: db) { yield }
19
+ with_tracing(operation, statement: statement, database: _nr_db) { yield }
20
20
  end
21
21
 
22
22
  # Used for Redis 4.x and 3.x
@@ -24,22 +24,15 @@ module NewRelic::Agent::Instrumentation
24
24
  operation = pipeline.is_a?(::Redis::Pipeline::Multi) ? Constants::MULTI_OPERATION : Constants::PIPELINE_OPERATION
25
25
  statement = ::NewRelic::Agent::Datastores::Redis.format_pipeline_commands(pipeline.commands)
26
26
 
27
- with_tracing(operation, statement: statement, database: db) { yield }
27
+ with_tracing(operation, statement: statement, database: _nr_db) { yield }
28
28
  end
29
29
 
30
30
  # Used for Redis 5.x+
31
31
  def call_pipelined_with_tracing(pipeline)
32
- db = begin
33
- _nr_redis_client_config.db
34
- rescue StandardError => e
35
- NewRelic::Agent.logger.error("Failed to determine configured Redis db value: #{e.class} - #{e.message}")
36
- nil
37
- end
38
-
39
32
  operation = pipeline.flatten.include?('MULTI') ? Constants::MULTI_OPERATION : Constants::PIPELINE_OPERATION
40
33
  statement = ::NewRelic::Agent::Datastores::Redis.format_pipeline_commands(pipeline)
41
34
 
42
- with_tracing(operation, statement: statement, database: db) { yield }
35
+ with_tracing(operation, statement: statement, database: _nr_db) { yield }
43
36
  end
44
37
 
45
38
  private
@@ -94,5 +87,15 @@ module NewRelic::Agent::Instrumentation
94
87
  config
95
88
  end
96
89
  end
90
+
91
+ def _nr_db
92
+ # db is a method on the Redis client in versions < 5.x
93
+ return db if respond_to?(:db)
94
+ # db is accessible through the RedisClient::Config object in versions > 5.x
95
+ return _nr_redis_client_config.db if _nr_redis_client_config.respond_to?(:db)
96
+ rescue StandardError => e
97
+ NewRelic::Agent.logger.debug("Failed to determine configured Redis db value: #{e.class} - #{e.message}")
98
+ nil
99
+ end
97
100
  end
98
101
  end
@@ -6,6 +6,9 @@ module NewRelic::Agent::Instrumentation
6
6
  module RedisClient
7
7
  module Middleware
8
8
  # This module is used to instrument Redis 5.x+
9
+ #
10
+ # It only instruments call_pipelined because connect and call are accessed
11
+ # too late in the stack to capture all errors
9
12
  include NewRelic::Agent::Instrumentation::Redis
10
13
 
11
14
  def call_pipelined(*args, &block)
@@ -10,6 +10,7 @@ require_relative 'redis/chain'
10
10
  require_relative 'redis/constants'
11
11
  require_relative 'redis/prepend'
12
12
  require_relative 'redis/middleware'
13
+ require_relative 'redis/cluster_middleware'
13
14
 
14
15
  DependencyDetection.defer do
15
16
  # Why not :redis? newrelic-redis used that name, so avoid conflicting
@@ -33,6 +34,10 @@ DependencyDetection.defer do
33
34
  NewRelic::Agent.logger.info('Installing Redis Instrumentation')
34
35
  if NewRelic::Agent::Instrumentation::Redis::Constants::HAS_REDIS_CLIENT
35
36
  RedisClient.register(NewRelic::Agent::Instrumentation::RedisClient::Middleware)
37
+
38
+ if defined?(Redis::Cluster::Client)
39
+ return RedisClient.register(NewRelic::Agent::Instrumentation::RedisClient::ClusterMiddleware)
40
+ end
36
41
  end
37
42
 
38
43
  if use_prepend?
@@ -10,6 +10,7 @@ module NewRelic
10
10
  EVENT_ATTRIBUTES = %i[http_status method num_retries path request_id].freeze
11
11
  ATTRIBUTE_NAMESPACE = 'stripe.user_data'
12
12
  ATTRIBUTE_FILTER_TYPES = %i[include exclude].freeze
13
+ PATH_PORTION_PATTERN = %r{^/([^/]+/[^/]+)(?:/|\z)}.freeze
13
14
 
14
15
  def start_segment(event)
15
16
  return unless is_execution_traced?
@@ -39,7 +40,27 @@ module NewRelic
39
40
  end
40
41
 
41
42
  def metric_name(event)
42
- "Stripe#{event.path}/#{event.method}"
43
+ # Grab only the first 2 items from the slash (/) delimited event path.
44
+ # These items are the API version string and the category. Grabbing
45
+ # any more of the path will result in unique method names that will
46
+ # easily grow to be too numerous to sort through in the UI and
47
+ # possibly even violate default New Relic metric count thresholds.
48
+ # See newrelic/newrelic-ruby-agent#2654 and
49
+ # newrelic/newrelic-ruby-agent#2709 for more details.
50
+ #
51
+ # In Ruby v3.4 benchmarks, using regex to get at the first two path
52
+ # elements was seen as more performant than using String#split.
53
+ #
54
+ # Regex legend:
55
+ #
56
+ # ^ = starts with
57
+ # / = a literal '/'
58
+ # () = capture
59
+ # (?:) = don't capture
60
+ # [^/]+ = 1 or more characters that are not '/'
61
+ # /|\z = a literal '/' OR the end of the string
62
+ path_portion = event.path =~ PATH_PORTION_PATTERN ? Regexp.last_match(1) : NewRelic::UNKNOWN
63
+ "Stripe/#{path_portion}/#{event.method}"
43
64
  end
44
65
 
45
66
  def add_stripe_attributes(segment, event)
@@ -12,6 +12,12 @@ module NewRelic
12
12
  return message unless decorating_enabled?
13
13
 
14
14
  metadata = NewRelic::Agent.linking_metadata
15
+
16
+ if message.is_a?(Hash)
17
+ message.merge!(metadata) unless message.frozen?
18
+ return
19
+ end
20
+
15
21
  formatted_metadata = " NR-LINKING|#{metadata[ENTITY_GUID_KEY]}|#{metadata[HOSTNAME_KEY]}|" \
16
22
  "#{metadata[TRACE_ID_KEY]}|#{metadata[SPAN_ID_KEY]}|" \
17
23
  "#{escape_entity_name(metadata[ENTITY_NAME_KEY])}|"
@@ -23,7 +29,8 @@ module NewRelic
23
29
 
24
30
  def decorating_enabled?
25
31
  NewRelic::Agent.config[:'application_logging.enabled'] &&
26
- NewRelic::Agent::Instrumentation::Logger.enabled? &&
32
+ (NewRelic::Agent::Instrumentation::Logger.enabled? ||
33
+ NewRelic::Agent::Instrumentation::LogStasher.enabled?) &&
27
34
  NewRelic::Agent.config[:'application_logging.local_decorating.enabled']
28
35
  end
29
36
 
@@ -20,7 +20,8 @@ module NewRelic
20
20
  DROPPED_METRIC = 'Logging/Forwarding/Dropped'.freeze
21
21
  SEEN_METRIC = 'Supportability/Logging/Forwarding/Seen'.freeze
22
22
  SENT_METRIC = 'Supportability/Logging/Forwarding/Sent'.freeze
23
- OVERALL_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
23
+ LOGGER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
24
+ LOGSTASHER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/LogStasher/%s'.freeze
24
25
  METRICS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Metrics/Ruby/%s'.freeze
25
26
  FORWARDING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Forwarding/Ruby/%s'.freeze
26
27
  DECORATING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/LocalDecorating/Ruby/%s'.freeze
@@ -58,38 +59,71 @@ module NewRelic
58
59
  end
59
60
 
60
61
  def record(formatted_message, severity)
61
- return unless enabled?
62
+ return unless logger_enabled?
62
63
 
63
64
  severity = 'UNKNOWN' if severity.nil? || severity.empty?
65
+ increment_event_counters(severity)
66
+
67
+ return if formatted_message.nil? || formatted_message.empty?
68
+ return unless monitoring_conditions_met?(severity)
69
+
70
+ txn = NewRelic::Agent::Transaction.tl_current
71
+ priority = LogPriority.priority_for(txn)
64
72
 
65
- if NewRelic::Agent.config[METRICS_ENABLED_KEY]
66
- @counter_lock.synchronize do
67
- @seen += 1
68
- @seen_by_severity[severity] += 1
73
+ return txn.add_log_event(create_event(priority, formatted_message, severity)) if txn
74
+
75
+ @lock.synchronize do
76
+ @buffer.append(priority: priority) do
77
+ create_event(priority, formatted_message, severity)
69
78
  end
70
79
  end
80
+ rescue
81
+ nil
82
+ end
71
83
 
72
- return if severity_too_low?(severity)
73
- return if formatted_message.nil? || formatted_message.empty?
74
- return unless NewRelic::Agent.config[FORWARDING_ENABLED_KEY]
75
- return if @high_security
84
+ def record_logstasher_event(log)
85
+ return unless logstasher_enabled?
86
+
87
+ # LogStasher logs do not inherently include a message key, so most logs are recorded.
88
+ # But when the key exists, we should not record the log if the message value is nil or empty.
89
+ return if log.key?('message') && (log['message'].nil? || log['message'].empty?)
90
+
91
+ severity = determine_severity(log)
92
+ increment_event_counters(severity)
93
+
94
+ return unless monitoring_conditions_met?(severity)
76
95
 
77
96
  txn = NewRelic::Agent::Transaction.tl_current
78
97
  priority = LogPriority.priority_for(txn)
79
98
 
80
- if txn
81
- return txn.add_log_event(create_event(priority, formatted_message, severity))
82
- else
83
- return @lock.synchronize do
84
- @buffer.append(priority: priority) do
85
- create_event(priority, formatted_message, severity)
86
- end
99
+ return txn.add_log_event(create_logstasher_event(priority, severity, log)) if txn
100
+
101
+ @lock.synchronize do
102
+ @buffer.append(priority: priority) do
103
+ create_logstasher_event(priority, severity, log)
87
104
  end
88
105
  end
89
106
  rescue
90
107
  nil
91
108
  end
92
109
 
110
+ def monitoring_conditions_met?(severity)
111
+ !severity_too_low?(severity) && NewRelic::Agent.config[FORWARDING_ENABLED_KEY] && !@high_security
112
+ end
113
+
114
+ def determine_severity(log)
115
+ log['level'] ? log['level'].to_s.upcase : 'UNKNOWN'
116
+ end
117
+
118
+ def increment_event_counters(severity)
119
+ return unless NewRelic::Agent.config[METRICS_ENABLED_KEY]
120
+
121
+ @counter_lock.synchronize do
122
+ @seen += 1
123
+ @seen_by_severity[severity] += 1
124
+ end
125
+ end
126
+
93
127
  def record_batch(txn, logs)
94
128
  # Ensure we have the same shared priority
95
129
  priority = LogPriority.priority_for(txn)
@@ -104,15 +138,17 @@ module NewRelic
104
138
  end
105
139
  end
106
140
 
107
- def create_event(priority, formatted_message, severity)
108
- formatted_message = truncate_message(formatted_message)
109
-
110
- event = LinkingMetadata.append_trace_linking_metadata({
141
+ def add_event_metadata(formatted_message, severity)
142
+ metadata = {
111
143
  LEVEL_KEY => severity,
112
- MESSAGE_KEY => formatted_message,
113
144
  TIMESTAMP_KEY => Process.clock_gettime(Process::CLOCK_REALTIME) * 1000
114
- })
145
+ }
146
+ metadata[MESSAGE_KEY] = formatted_message unless formatted_message.nil?
147
+
148
+ LinkingMetadata.append_trace_linking_metadata(metadata)
149
+ end
115
150
 
151
+ def create_prioritized_event(priority, event)
116
152
  [
117
153
  {
118
154
  PrioritySampledBuffer::PRIORITY_KEY => priority
@@ -121,6 +157,31 @@ module NewRelic
121
157
  ]
122
158
  end
123
159
 
160
+ def create_event(priority, formatted_message, severity)
161
+ formatted_message = truncate_message(formatted_message)
162
+ event = add_event_metadata(formatted_message, severity)
163
+
164
+ create_prioritized_event(priority, event)
165
+ end
166
+
167
+ def create_logstasher_event(priority, severity, log)
168
+ formatted_message = log['message'] ? truncate_message(log['message']) : nil
169
+ event = add_event_metadata(formatted_message, severity)
170
+ add_logstasher_event_attributes(event, log)
171
+
172
+ create_prioritized_event(priority, event)
173
+ end
174
+
175
+ def add_logstasher_event_attributes(event, log)
176
+ log_copy = log.dup
177
+ # Delete previously reported attributes
178
+ log_copy.delete('message')
179
+ log_copy.delete('level')
180
+ log_copy.delete('@timestamp')
181
+
182
+ event['attributes'] = log_copy
183
+ end
184
+
124
185
  def add_custom_attributes(custom_attributes)
125
186
  attributes.add_custom_attributes(custom_attributes)
126
187
  end
@@ -166,10 +227,14 @@ module NewRelic
166
227
  super
167
228
  end
168
229
 
169
- def enabled?
230
+ def logger_enabled?
170
231
  @enabled && @instrumentation_logger_enabled
171
232
  end
172
233
 
234
+ def logstasher_enabled?
235
+ @enabled && NewRelic::Agent::Instrumentation::LogStasher.enabled?
236
+ end
237
+
173
238
  private
174
239
 
175
240
  # We record once-per-connect metrics for enabled/disabled state at the
@@ -177,8 +242,8 @@ module NewRelic
177
242
  def register_for_done_configuring(events)
178
243
  events.subscribe(:server_source_configuration_added) do
179
244
  @high_security = NewRelic::Agent.config[:high_security]
180
-
181
- record_configuration_metric(OVERALL_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
245
+ record_configuration_metric(LOGGER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
246
+ record_configuration_metric(LOGSTASHER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
182
247
  record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
183
248
  record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
184
249
  record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
@@ -73,6 +73,7 @@ module NewRelic
73
73
  init_config(options)
74
74
  NewRelic::Agent.agent = NewRelic::Agent::Agent.instance
75
75
  init_instrumentation
76
+ init_security_agent
76
77
  end
77
78
 
78
79
  def determine_env(options)
@@ -43,6 +43,10 @@ module NewRelic
43
43
  DependencyDetection.detect!
44
44
  end
45
45
  end
46
+
47
+ def init_security_agent
48
+ SecurityInterface.instance.init_agent
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -0,0 +1,57 @@
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 'singleton'
6
+
7
+ module NewRelic
8
+ class Control
9
+ class SecurityInterface
10
+ include Singleton
11
+
12
+ attr_accessor :wait
13
+
14
+ SUPPORTABILITY_PREFIX_SECURITY = 'Supportability/Ruby/SecurityAgent/Enabled/'
15
+ SUPPORTABILITY_PREFIX_SECURITY_AGENT = 'Supportability/Ruby/SecurityAgent/Agent/Enabled/'
16
+ ENABLED = 'enabled'
17
+ DISABLED = 'disabled'
18
+
19
+ def agent_started?
20
+ (@agent_started ||= false) == true
21
+ end
22
+
23
+ def waiting?
24
+ (@wait ||= false) == true
25
+ end
26
+
27
+ def init_agent
28
+ return if agent_started? || waiting?
29
+
30
+ record_supportability_metrics
31
+
32
+ if Agent.config[:'security.agent.enabled'] && !Agent.config[:high_security]
33
+ Agent.logger.info('Invoking New Relic security module')
34
+ require 'newrelic_security'
35
+
36
+ @agent_started = true
37
+ else
38
+ Agent.logger.info('New Relic Security is completely disabled by one of the user-provided configurations: `security.agent.enabled` or `high_security`. Not loading security capabilities.')
39
+ Agent.logger.info("high_security = #{Agent.config[:high_security]}")
40
+ Agent.logger.info("security.agent.enabled = #{Agent.config[:'security.agent.enabled']}")
41
+ end
42
+ rescue LoadError
43
+ Agent.logger.info('New Relic security agent not found - skipping')
44
+ rescue StandardError => exception
45
+ Agent.logger.error("Exception in New Relic security module loading: #{exception} #{exception.backtrace}")
46
+ end
47
+
48
+ def record_supportability_metrics
49
+ Agent.config[:'security.agent.enabled'] ? security_agent_metric(ENABLED) : security_agent_metric(DISABLED)
50
+ end
51
+
52
+ def security_agent_metric(setting)
53
+ NewRelic::Agent.record_metric_once(SUPPORTABILITY_PREFIX_SECURITY_AGENT + setting)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -8,7 +8,6 @@ require 'new_relic/local_environment'
8
8
  require 'new_relic/language_support'
9
9
  require 'new_relic/helper'
10
10
 
11
- require 'singleton'
12
11
  require 'erb'
13
12
  require 'socket'
14
13
  require 'net/https'
@@ -18,6 +17,7 @@ require 'new_relic/control/server_methods'
18
17
  require 'new_relic/control/instrumentation'
19
18
  require 'new_relic/control/class_methods'
20
19
  require 'new_relic/control/instance_methods'
20
+ require 'new_relic/control/security_interface'
21
21
 
22
22
  require 'new_relic/agent'
23
23
  require 'new_relic/delayed_job_injection'
@@ -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
23
+ CONTENT_TYPE = 'Content-Type'
24
+ CONTENT_DISPOSITION = 'Content-Disposition'
25
+ CONTENT_LENGTH = 'Content-Length'
26
26
  ATTACHMENT = /attachment/.freeze
27
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
@@ -120,7 +120,11 @@ module NewRelic
120
120
  end
121
121
 
122
122
  def streaming?(env, headers)
123
- # 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.
124
128
  return true if headers && headers['Transfer-Encoding'] == 'chunked'
125
129
 
126
130
  defined?(ActionController::Live) &&
@@ -6,8 +6,8 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 10
10
- TINY = 2
9
+ MINOR = 12
10
+ TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
13
13
  end
@@ -23,13 +23,16 @@ namespace :newrelic do
23
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
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>"
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>"
27
29
  }
28
30
 
29
31
  NAME_OVERRIDES = {
30
32
  'slow_sql' => 'Slow SQL [#slow-sql]',
31
33
  'custom_insights_events' => 'Custom Events [#custom-events]',
32
- 'ai_monitoring' => 'AI Monitoring [#ai-monitoring]'
34
+ 'ai_monitoring' => 'AI Monitoring [#ai-monitoring]',
35
+ 'security_agent' => 'Security Agent [#security-agent]'
33
36
  }
34
37
 
35
38
  desc 'Describe available New Relic configuration settings'