newrelic_rpm 9.11.0 → 9.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  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 +106 -3
  6. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  7. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  8. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -1
  9. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  10. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  11. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  12. data/lib/new_relic/agent/instrumentation/logstasher.rb +27 -0
  13. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  14. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  15. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  16. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  17. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  18. data/lib/new_relic/agent/instrumentation/redis.rb +5 -0
  19. data/lib/new_relic/agent/local_log_decorator.rb +8 -1
  20. data/lib/new_relic/agent/log_event_aggregator.rb +91 -26
  21. data/lib/new_relic/control/instance_methods.rb +1 -0
  22. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  23. data/lib/new_relic/control/security_interface.rb +57 -0
  24. data/lib/new_relic/control.rb +1 -1
  25. data/lib/new_relic/rack/browser_monitoring.rb +11 -7
  26. data/lib/new_relic/version.rb +1 -1
  27. data/lib/tasks/config.rake +5 -2
  28. data/lib/tasks/helpers/config.html.erb +1 -1
  29. data/lib/tasks/helpers/format.rb +1 -1
  30. data/lib/tasks/helpers/newrelicyml.rb +3 -2
  31. data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -0
  32. data/newrelic.yml +200 -137
  33. data/test/agent_helper.rb +9 -0
  34. metadata +8 -2
@@ -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,7 +6,7 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 11
9
+ MINOR = 12
10
10
  TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
@@ -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'
@@ -76,7 +76,7 @@ When running the Ruby agent in a Rails app, the agent first looks for the `NEW_R
76
76
  When you edit the config file, be sure to:
77
77
 
78
78
  * Indent only with two spaces.
79
- * Indent only where relevant, in sections such as <DoNotTranslate>**`error_collector`**</DoNotTranslate>.
79
+ * Indent only where relevant, in sections such as **`error_collector`**.
80
80
 
81
81
  If you do not indent correctly, the agent may throw an `Unable to parse configuration file` error on startup.
82
82
 
@@ -69,7 +69,7 @@ module Format
69
69
 
70
70
  def format_description(value)
71
71
  description = ''
72
- description += '<DoNotTranslate>**DEPRECATED**</DoNotTranslate>' if value[:deprecated]
72
+ description += '<DNT>**DEPRECATED**</DNT> ' if value[:deprecated]
73
73
  description += value[:description]
74
74
  description
75
75
  end
@@ -105,8 +105,9 @@ module NewRelicYML
105
105
  def self.format_description(description)
106
106
  # remove leading and trailing whitespace
107
107
  description.strip!
108
- # wrap text after 80 characters
109
- description.gsub!(/(.{1,80})(\s+|\Z)/, "\\1\n")
108
+ # wrap text after 80 characters, assuming we're at one tabstop's (two
109
+ # spaces') level of indentation already
110
+ description.gsub!(/(.{1,78})(\s+|\Z)/, "\\1\n")
110
111
  # add hashtags to lines
111
112
  description = description.split("\n").map { |line| " # #{line}" }.join("\n")
112
113
 
@@ -103,6 +103,7 @@ class Instrumentation < Thor
103
103
  <<-CONFIG
104
104
  :'instrumentation.#{snake_name}' => {
105
105
  :default => 'auto',
106
+ :documentation_default => 'auto'
106
107
  :public => true,
107
108
  :type => String,
108
109
  :dynamic_name => true,