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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -0
- data/README.md +1 -1
- data/lib/new_relic/agent/agent_logger.rb +1 -0
- data/lib/new_relic/agent/configuration/default_source.rb +106 -3
- data/lib/new_relic/agent/database/obfuscator.rb +1 -0
- data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
- data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -1
- data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
- data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
- data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
- data/lib/new_relic/agent/instrumentation/logstasher.rb +27 -0
- data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
- data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
- data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
- data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
- data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
- data/lib/new_relic/agent/instrumentation/redis.rb +5 -0
- data/lib/new_relic/agent/local_log_decorator.rb +8 -1
- data/lib/new_relic/agent/log_event_aggregator.rb +91 -26
- data/lib/new_relic/control/instance_methods.rb +1 -0
- data/lib/new_relic/control/private_instance_methods.rb +4 -0
- data/lib/new_relic/control/security_interface.rb +57 -0
- data/lib/new_relic/control.rb +1 -1
- data/lib/new_relic/rack/browser_monitoring.rb +11 -7
- data/lib/new_relic/version.rb +1 -1
- data/lib/tasks/config.rake +5 -2
- data/lib/tasks/helpers/config.html.erb +1 -1
- data/lib/tasks/helpers/format.rb +1 -1
- data/lib/tasks/helpers/newrelicyml.rb +3 -2
- data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -0
- data/newrelic.yml +200 -137
- data/test/agent_helper.rb +9 -0
- 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
|
-
|
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
|
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
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
73
|
-
return
|
74
|
-
|
75
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
108
|
-
|
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
|
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(
|
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)
|
@@ -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
|
data/lib/new_relic/control.rb
CHANGED
@@ -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'
|
24
|
-
CONTENT_DISPOSITION = 'Content-Disposition'
|
25
|
-
CONTENT_LENGTH = 'Content-Length'
|
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'
|
30
|
-
HEAD_START = '<head'
|
31
|
-
GT = '>'
|
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
|
-
#
|
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) &&
|
data/lib/new_relic/version.rb
CHANGED
data/lib/tasks/config.rake
CHANGED
@@ -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
|
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
|
|
data/lib/tasks/helpers/format.rb
CHANGED
@@ -69,7 +69,7 @@ module Format
|
|
69
69
|
|
70
70
|
def format_description(value)
|
71
71
|
description = ''
|
72
|
-
description += '<
|
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
|
-
|
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
|
|