newrelic_rpm 9.13.0 → 9.14.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 +87 -5
- data/lib/new_relic/agent/configuration/default_source.rb +82 -1
- data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
- data/lib/new_relic/agent/configuration/manager.rb +16 -1
- data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
- data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +3 -2
- data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
- data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +71 -0
- data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
- data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +66 -0
- data/lib/new_relic/agent/instrumentation/rdkafka.rb +27 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +50 -0
- data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +27 -0
- data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +4 -1
- data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
- data/lib/new_relic/agent/messaging.rb +11 -5
- data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
- data/lib/new_relic/agent.rb +93 -0
- data/lib/new_relic/control/frameworks/rails4.rb +1 -1
- data/lib/new_relic/environment_report.rb +1 -1
- data/lib/new_relic/language_support.rb +1 -1
- data/lib/new_relic/version.rb +1 -1
- data/newrelic.yml +55 -0
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0ade9f1a1a36e6382100a271c5e54a2c426c27b95884a15eac8d6740dcee29b
|
4
|
+
data.tar.gz: 18f728d4289bfda058bd54d7d7798a33c3a43d45de83f543b86dd573ff76283e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a463e150f83291e4ef1c55e23b155621694dc5cf0a709d78bf7ebd10aee7b801afaa1e76a4fd6d12858fba7806368bad7e6b5ca53bd21273f68e1f995b524edd
|
7
|
+
data.tar.gz: 9d0faa058f7810ce4edff5c91a57e584e1efaea99e6d4e533b2128727b0ccc7bb61dfa87ab0a82e134bce05010c7aec5ccef9b7960f72c502e68473885492434
|
data/CHANGELOG.md
CHANGED
@@ -1,16 +1,98 @@
|
|
1
1
|
# New Relic Ruby Agent Release Notes
|
2
2
|
|
3
|
+
## v9.14.0
|
4
|
+
|
5
|
+
Version 9.14.0 adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, correctly captures MIME type for AcionDispatch 7.0+ requests, properly handles Boolean coercion for `newrelic.yml` configuration, fixes a JRuby bug in the configuration manager, fixes a bug related to `Bundler.rubygems.installed_specs`, and fixes a bug to make the agent compatible with ViewComponent v3.15.0+.
|
6
|
+
|
7
|
+
- **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems**
|
8
|
+
|
9
|
+
The agent now has instrumentation for both the rdkafka and ruby-kafka gems. The agent will record transactions and message broker segments for produce and consume calls made using these gems. [PR#2824](https://github.com/newrelic/newrelic-ruby-agent/pull/2824) [PR#2842](https://github.com/newrelic/newrelic-ruby-agent/pull/2842)
|
10
|
+
|
11
|
+
- **Feature: Add a configuration option to permit custom method tracers to be defined automatically**
|
12
|
+
|
13
|
+
A new `:automatic_custom_instrumentation_method_list` configuration parameter has been added to permit the user to define a list of fully qualified (namespaced) Ruby methods for the agent to automatically add custom instrumentation for without requiring any code modifications to be made to the classes that define the methods.
|
14
|
+
|
15
|
+
The list should be an array of `CLASS#METHOD` (for instance methods) and/or `CLASS.METHOD` (for class methods) strings.
|
16
|
+
|
17
|
+
Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing.
|
18
|
+
|
19
|
+
Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and a `notify` class method for a `User` class, both within a `MyCompany` module namespace:
|
20
|
+
|
21
|
+
```
|
22
|
+
module MyCompany
|
23
|
+
class Image
|
24
|
+
def render_png
|
25
|
+
# code to render a PNG
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class User
|
30
|
+
def self.notify
|
31
|
+
# code to notify users
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Given that source code, the `newrelic.yml` config file might request instrumentation for both of these methods like so:
|
38
|
+
|
39
|
+
```
|
40
|
+
automatic_custom_instrumentation_method_list:
|
41
|
+
- MyCompany::Image#render_png
|
42
|
+
- MyCompany::User.notify
|
43
|
+
```
|
44
|
+
|
45
|
+
That configuration example uses YAML array syntax to specify both methods. Alternatively, a comma-delimited string can be used instead:
|
46
|
+
|
47
|
+
```
|
48
|
+
automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify'
|
49
|
+
```
|
50
|
+
|
51
|
+
Whitespace around the comma(s) in the list is optional. When configuring the agent with a list of methods via the `NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST` environment variable, this comma-delimited string format should be used:
|
52
|
+
|
53
|
+
```
|
54
|
+
export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify'
|
55
|
+
```
|
56
|
+
|
57
|
+
[PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851)
|
58
|
+
|
59
|
+
- **Feature: Collect just MIME type for AcionDispatch 7.0+ requests**
|
60
|
+
|
61
|
+
Rails 7.0 [introduced changes](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is) to the behavior of `ActionDispatch::Request#content_type`, adding extra request-related details the agent wasn't expecting to collect. Additionally, the agent's use of `content_type ` was triggering deprecation warnings. The agent now uses `ActionDispatch::Request#media_type` to capture the MIME type. Thanks to [@internethostage](https://github.com/internethostage) for letting us know about this change. [Issue#2500](https://github.com/newrelic/newrelic-ruby-agent/issues/2500) [PR#2855](https://github.com/newrelic/newrelic-ruby-agent/pull/2855)
|
62
|
+
|
63
|
+
- **Bugfix: Corrected Boolean coercion for `newrelic.yml` configuration**
|
64
|
+
|
65
|
+
Previously, any String assigned to New Relic configurations expecting a Boolean value were evaluated as `true`. This could lead to unexpected behavior. For example, setting `application_logging.enabled: 'false'` in `newrelic.yml` would incorrectly evaluate to `application_logging.enabled: true` due to the truthy nature of Strings.
|
66
|
+
|
67
|
+
Now, the agent strictly interprets Boolean configuration values. It recognizes both actual Boolean values and certain Strings/Symbols:
|
68
|
+
- `'true'`, `'yes'`, or `'on'` (evaluates to `true`)
|
69
|
+
- `'false'`, `'no'`, or `'off'` (evaluates to `false`)
|
70
|
+
|
71
|
+
Any other inputs will revert to the setting's default configuration value. [PR#2847](https://github.com/newrelic/newrelic-ruby-agent/pull/2847)
|
72
|
+
|
73
|
+
- **Bugfix: JRuby not saving configuration values correctly in configuration manager**
|
74
|
+
|
75
|
+
Previously, a change made to fix a different JRuby bug caused the agent to not save configuration values correctly in the configuration manager when running on JRuby. This has been fixed. [PR#2848](https://github.com/newrelic/newrelic-ruby-agent/pull/2848)
|
76
|
+
|
77
|
+
- **Bugfix: Update condition to verify Bundler.rubygems.installed_specs is available**
|
78
|
+
|
79
|
+
To address a recent Bundler deprecation warning, we started using `Bundler.rubygems.installed_specs` instead of `Bundler.rubygems.all_specs` in environments that seemed appropriate. We discovered the version constraint we used was too low. Now, rather than check the version, we check for the method using `respond_to?`. [PR#2853](https://github.com/newrelic/newrelic-ruby-agent/pull/2853)
|
80
|
+
|
81
|
+
- **Bugfix: Support view_component v3.15.0+**
|
82
|
+
|
83
|
+
Previously the agent had been making use of a private API to obtain a component identifier value. This private API was dropped in v3.15.0 of view_component, resulting in errors from the New Relic Ruby agent's continued attempts to use it. Many thanks to community member [@navidemad](https://github.com/navidemad) for bringing this issue to our attention and supplying a bugfix with [PR#2870](https://github.com/newrelic/newrelic-ruby-agent/pull/2870).
|
84
|
+
|
3
85
|
## v9.13.0
|
4
86
|
|
5
87
|
Version 9.13.0 enhances support for AWS Lambda functions, adds experimental OpenSearch instrumentation, updates framework detection, silences a Bundler deprecation warning, fixes Falcon dispatcher detection, fixes a bug with Redis instrumentation installation, and addresses a JRuby-specific concurrency issue.
|
6
88
|
|
7
89
|
- **Feature: Enhance AWS Lambda function instrumentation**
|
8
90
|
|
9
|
-
When utilized via the latest [New Relic Ruby layer for AWS Lambda](https://layers.newrelic-external.com/), the agent now offers enhanced support for AWS Lambda function instrumentation.
|
10
|
-
* The agent's instrumentation for AWS Lambda functions now supports distributed tracing.
|
11
|
-
* Web-triggered invocations are now identified as being "web"-based when an API Gateway call is involved, with support for both API Gateway versions 1.0 and 2.0.
|
12
|
-
* Web-based calls have the HTTP method, URI, and status code recorded.
|
13
|
-
* The agent now recognizes and reports on 12 separate AWS resources that are capable of triggering a Lambda function invocation: ALB, API Gateway V1, API Gateway V2, CloudFront, CloudWatch Scheduler, DynamoStreams, Firehose, Kinesis, S3, SES, SNS, and SQS.
|
91
|
+
When utilized via the latest [New Relic Ruby layer for AWS Lambda](https://layers.newrelic-external.com/), the agent now offers enhanced support for AWS Lambda function instrumentation.
|
92
|
+
* The agent's instrumentation for AWS Lambda functions now supports distributed tracing.
|
93
|
+
* Web-triggered invocations are now identified as being "web"-based when an API Gateway call is involved, with support for both API Gateway versions 1.0 and 2.0.
|
94
|
+
* Web-based calls have the HTTP method, URI, and status code recorded.
|
95
|
+
* The agent now recognizes and reports on 12 separate AWS resources that are capable of triggering a Lambda function invocation: ALB, API Gateway V1, API Gateway V2, CloudFront, CloudWatch Scheduler, DynamoStreams, Firehose, Kinesis, S3, SES, SNS, and SQS.
|
14
96
|
* The type of the triggering resource and its ARN will be recorded for each resource, and for many of them, extra resource-specific attributes will be recorded as well. For example, Lambda function invocations triggered by S3 bucket activity will now result in the S3 bucket name being recorded.
|
15
97
|
[PR#2811](https://github.com/newrelic/newrelic-ruby-agent/pull/2811)
|
16
98
|
|
@@ -35,6 +35,15 @@ module NewRelic
|
|
35
35
|
end
|
36
36
|
|
37
37
|
class DefaultSource
|
38
|
+
BOOLEAN_MAP = {
|
39
|
+
'true' => true,
|
40
|
+
'yes' => true,
|
41
|
+
'on' => true,
|
42
|
+
'false' => false,
|
43
|
+
'no' => false,
|
44
|
+
'off' => false
|
45
|
+
}.freeze
|
46
|
+
|
38
47
|
attr_reader :defaults
|
39
48
|
|
40
49
|
extend Forwardable
|
@@ -64,6 +73,12 @@ module NewRelic
|
|
64
73
|
value_from_defaults(key, :allowlist)
|
65
74
|
end
|
66
75
|
|
76
|
+
def self.boolean_for(key, value)
|
77
|
+
string_value = (value.respond_to?(:call) ? value.call : value).to_s
|
78
|
+
|
79
|
+
BOOLEAN_MAP.fetch(string_value, nil)
|
80
|
+
end
|
81
|
+
|
67
82
|
def self.default_for(key)
|
68
83
|
value_from_defaults(key, :default)
|
69
84
|
end
|
@@ -1137,6 +1152,56 @@ module NewRelic
|
|
1137
1152
|
:allowed_from_server => false,
|
1138
1153
|
:description => 'If `false`, custom attributes will not be sent on events.'
|
1139
1154
|
},
|
1155
|
+
:automatic_custom_instrumentation_method_list => {
|
1156
|
+
:default => NewRelic::EMPTY_ARRAY,
|
1157
|
+
:public => true,
|
1158
|
+
:type => Array,
|
1159
|
+
:allowed_from_server => false,
|
1160
|
+
:transform => proc { |arr| NewRelic::Agent.add_automatic_method_tracers(arr) },
|
1161
|
+
:description => <<~DESCRIPTION
|
1162
|
+
An array of `CLASS#METHOD` (for instance methods) and/or `CLASS.METHOD` (for class methods) strings representing Ruby methods for the agent to automatically add custom instrumentation to without the need for altering any of the source code that defines the methods.
|
1163
|
+
|
1164
|
+
Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing.
|
1165
|
+
|
1166
|
+
Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and a `notify` class method for a `User` class, both within a `MyCompany` module namespace:
|
1167
|
+
|
1168
|
+
```
|
1169
|
+
module MyCompany
|
1170
|
+
class Image
|
1171
|
+
def render_png
|
1172
|
+
# code to render a PNG
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
class User
|
1177
|
+
def self.notify
|
1178
|
+
# code to notify users
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
end
|
1182
|
+
```
|
1183
|
+
|
1184
|
+
Given that source code, the `newrelic.yml` config file might request instrumentation for both of these methods like so:
|
1185
|
+
|
1186
|
+
```
|
1187
|
+
automatic_custom_instrumentation_method_list:
|
1188
|
+
- MyCompany::Image#render_png
|
1189
|
+
- MyCompany::User.notify
|
1190
|
+
```
|
1191
|
+
|
1192
|
+
That configuration example uses YAML array syntax to specify both methods. Alternatively, a comma-delimited string can be used instead:
|
1193
|
+
|
1194
|
+
```
|
1195
|
+
automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify'
|
1196
|
+
```
|
1197
|
+
|
1198
|
+
Whitespace around the comma(s) in the list is optional. When configuring the agent with a list of methods via the `NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST` environment variable, this comma-delimited string format should be used:
|
1199
|
+
|
1200
|
+
```
|
1201
|
+
export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify'
|
1202
|
+
```
|
1203
|
+
DESCRIPTION
|
1204
|
+
},
|
1140
1205
|
# Custom events
|
1141
1206
|
:'custom_insights_events.enabled' => {
|
1142
1207
|
:default => true,
|
@@ -1463,6 +1528,14 @@ module NewRelic
|
|
1463
1528
|
:allowed_from_server => false,
|
1464
1529
|
:description => 'Controls auto-instrumentation of bunny at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
|
1465
1530
|
},
|
1531
|
+
:'instrumentation.ruby_kafka' => {
|
1532
|
+
:default => 'auto',
|
1533
|
+
:public => true,
|
1534
|
+
:type => String,
|
1535
|
+
:dynamic_name => true,
|
1536
|
+
:allowed_from_server => false,
|
1537
|
+
:description => 'Controls auto-instrumentation of the ruby-kafka library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.'
|
1538
|
+
},
|
1466
1539
|
:'instrumentation.opensearch' => {
|
1467
1540
|
:default => 'auto',
|
1468
1541
|
:documentation_default => 'auto',
|
@@ -1472,6 +1545,14 @@ module NewRelic
|
|
1472
1545
|
:allowed_from_server => false,
|
1473
1546
|
:description => 'Controls auto-instrumentation of the opensearch-ruby library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.'
|
1474
1547
|
},
|
1548
|
+
:'instrumentation.rdkafka' => {
|
1549
|
+
:default => 'auto',
|
1550
|
+
:public => true,
|
1551
|
+
:type => String,
|
1552
|
+
:dynamic_name => true,
|
1553
|
+
:allowed_from_server => false,
|
1554
|
+
:description => 'Controls auto-instrumentation of the rdkafka library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.'
|
1555
|
+
},
|
1475
1556
|
:'instrumentation.aws_sqs' => {
|
1476
1557
|
:default => 'auto',
|
1477
1558
|
:public => true,
|
@@ -2202,7 +2283,7 @@ module NewRelic
|
|
2202
2283
|
:description => 'Enable or disable debugging version of JavaScript agent loader for browser monitoring instrumentation.'
|
2203
2284
|
},
|
2204
2285
|
:'browser_monitoring.ssl_for_http' => {
|
2205
|
-
:default =>
|
2286
|
+
:default => false,
|
2206
2287
|
:allow_nil => true,
|
2207
2288
|
:public => false,
|
2208
2289
|
:type => Boolean,
|
@@ -92,7 +92,11 @@ module NewRelic
|
|
92
92
|
elsif type == Symbol
|
93
93
|
self[config_key] = value.to_sym
|
94
94
|
elsif type == Array
|
95
|
-
self[config_key] =
|
95
|
+
self[config_key] = if DEFAULTS[config_key].key?(:transform)
|
96
|
+
DEFAULTS[config_key][:transform].call(value)
|
97
|
+
else
|
98
|
+
value.split(/\s*,\s*/)
|
99
|
+
end
|
96
100
|
elsif type == NewRelic::Agent::Configuration::Boolean
|
97
101
|
if /false|off|no/i.match?(value)
|
98
102
|
self[config_key] = false
|
@@ -142,6 +142,9 @@ module NewRelic
|
|
142
142
|
default = enforce_allowlist(key, evaluated)
|
143
143
|
return default if default
|
144
144
|
|
145
|
+
boolean = enforce_boolean(key, value)
|
146
|
+
return boolean if [true, false].include?(boolean)
|
147
|
+
|
145
148
|
apply_transformations(key, evaluated)
|
146
149
|
end
|
147
150
|
|
@@ -167,6 +170,18 @@ module NewRelic
|
|
167
170
|
default
|
168
171
|
end
|
169
172
|
|
173
|
+
def enforce_boolean(key, value)
|
174
|
+
type = default_source.value_from_defaults(key, :type)
|
175
|
+
return unless type == Boolean
|
176
|
+
|
177
|
+
bool_value = default_source.boolean_for(key, value)
|
178
|
+
return bool_value unless bool_value.nil?
|
179
|
+
|
180
|
+
default = default_source.default_for(key)
|
181
|
+
NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
|
182
|
+
default
|
183
|
+
end
|
184
|
+
|
170
185
|
def transform_from_default(key)
|
171
186
|
default_source.transform_for(key)
|
172
187
|
end
|
@@ -388,7 +403,7 @@ module NewRelic
|
|
388
403
|
# modified. The hash really only needs to be modified for the benefit
|
389
404
|
# of the security agent, so if JRuby is in play and the security agent
|
390
405
|
# is not, don't attempt to modify the hash at all and return early.
|
391
|
-
return
|
406
|
+
return new_cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled']
|
392
407
|
|
393
408
|
@lock.synchronize do
|
394
409
|
preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
|
@@ -7,17 +7,17 @@ module NewRelic
|
|
7
7
|
module Database
|
8
8
|
module ObfuscationHelpers
|
9
9
|
COMPONENTS_REGEX_MAP = {
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
}
|
10
|
+
single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
|
11
|
+
double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
|
12
|
+
dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
|
13
|
+
uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/,
|
14
|
+
numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
|
15
|
+
boolean_literals: /\b(?:true|false|null)\b/i,
|
16
|
+
hexadecimal_literals: /0x[0-9a-fA-F]+/,
|
17
|
+
comments: /(?:#|--).*?(?=\r|\n|$)/i,
|
18
|
+
multi_line_comments: %r{/\*.*?\*/}m,
|
19
|
+
oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/
|
20
|
+
}.freeze
|
21
21
|
|
22
22
|
DIALECT_COMPONENTS = {
|
23
23
|
:fallback => COMPONENTS_REGEX_MAP.keys,
|
@@ -31,8 +31,9 @@ module NewRelic::Agent::Instrumentation
|
|
31
31
|
collection: args[0][:table_name]
|
32
32
|
)
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
# TODO: Update this when it has been decided how to handle account id for ARN
|
35
|
+
# arn = get_arn(args[0])
|
36
|
+
# segment&.add_agent_attribute('cloud.resource_id', arn) if arn
|
36
37
|
|
37
38
|
@nr_captured_request = nil # clear request just in case
|
38
39
|
begin
|
@@ -20,7 +20,7 @@ DependencyDetection.defer do
|
|
20
20
|
depends_on do
|
21
21
|
begin
|
22
22
|
if defined?(Bundler) &&
|
23
|
-
((
|
23
|
+
((Bundler.rubygems.respond_to?(:installed_specs) && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) ||
|
24
24
|
Bundler.rubygems.all_specs.map(&:name).include?('newrelic-grape'))
|
25
25
|
NewRelic::Agent.logger.info('Not installing New Relic supported Grape instrumentation because the third party newrelic-grape gem is present')
|
26
26
|
false
|
@@ -0,0 +1,71 @@
|
|
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 'instrumentation'
|
6
|
+
|
7
|
+
module NewRelic::Agent::Instrumentation
|
8
|
+
module Rdkafka::Chain
|
9
|
+
def self.instrument!
|
10
|
+
::Rdkafka::Producer.class_eval do
|
11
|
+
include NewRelic::Agent::Instrumentation::Rdkafka
|
12
|
+
|
13
|
+
alias_method(:produce_without_new_relic, :produce)
|
14
|
+
|
15
|
+
def produce(**kwargs)
|
16
|
+
produce_with_new_relic(kwargs) do |headers|
|
17
|
+
kwargs[:headers] = headers
|
18
|
+
produce_without_new_relic(**kwargs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
::Rdkafka::Consumer.class_eval do
|
24
|
+
include NewRelic::Agent::Instrumentation::Rdkafka
|
25
|
+
|
26
|
+
alias_method(:each_without_new_relic, :each)
|
27
|
+
|
28
|
+
def each(**kwargs)
|
29
|
+
each_without_new_relic(**kwargs) do |message|
|
30
|
+
each_with_new_relic(message) do
|
31
|
+
yield(message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
::Rdkafka::Config.class_eval do
|
38
|
+
include NewRelic::Agent::Instrumentation::RdkafkaConfig
|
39
|
+
|
40
|
+
alias_method(:producer_without_new_relic, :producer)
|
41
|
+
alias_method(:consumer_without_new_relic, :consumer)
|
42
|
+
|
43
|
+
if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0')
|
44
|
+
def producer(**kwargs)
|
45
|
+
producer_without_new_relic(**kwargs).tap do |producer|
|
46
|
+
set_nr_config(producer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def consumer(**kwargs)
|
51
|
+
consumer_without_new_relic(**kwargs).tap do |consumer|
|
52
|
+
set_nr_config(consumer)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
else
|
56
|
+
def producer
|
57
|
+
producer_without_new_relic.tap do |producer|
|
58
|
+
set_nr_config(producer)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def consumer
|
63
|
+
consumer_without_new_relic.tap do |consumer|
|
64
|
+
set_nr_config(consumer)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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 'new_relic/agent/messaging'
|
6
|
+
|
7
|
+
module NewRelic::Agent::Instrumentation
|
8
|
+
module Rdkafka
|
9
|
+
MESSAGING_LIBRARY = 'Kafka'
|
10
|
+
PRODUCE = 'Produce'
|
11
|
+
CONSUME = 'Consume'
|
12
|
+
|
13
|
+
INSTRUMENTATION_NAME = 'Rdkafka'
|
14
|
+
|
15
|
+
def produce_with_new_relic(*args)
|
16
|
+
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
17
|
+
|
18
|
+
topic_name = args[0][:topic]
|
19
|
+
segment = NewRelic::Agent::Tracer.start_message_broker_segment(
|
20
|
+
action: :produce,
|
21
|
+
library: MESSAGING_LIBRARY,
|
22
|
+
destination_type: :topic,
|
23
|
+
destination_name: topic_name
|
24
|
+
)
|
25
|
+
create_kafka_metrics(action: PRODUCE, topic: topic_name)
|
26
|
+
|
27
|
+
headers = args[0][:headers] || {}
|
28
|
+
::NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(headers)
|
29
|
+
|
30
|
+
NewRelic::Agent::Tracer.capture_segment_error(segment) { yield(headers) }
|
31
|
+
ensure
|
32
|
+
segment&.finish
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_with_new_relic(message)
|
36
|
+
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
37
|
+
|
38
|
+
headers = message&.headers || {}
|
39
|
+
topic_name = message&.topic
|
40
|
+
|
41
|
+
NewRelic::Agent::Messaging.wrap_message_broker_consume_transaction(
|
42
|
+
library: MESSAGING_LIBRARY,
|
43
|
+
destination_type: :topic,
|
44
|
+
destination_name: topic_name,
|
45
|
+
headers: headers,
|
46
|
+
action: :consume
|
47
|
+
) do
|
48
|
+
create_kafka_metrics(action: CONSUME, topic: topic_name)
|
49
|
+
yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_kafka_metrics(action:, topic:)
|
54
|
+
hosts = []
|
55
|
+
# both 'bootstrap.servers' and 'metadata.broker.list' are valid ways to specify the Kafka server
|
56
|
+
hosts << @nr_config[:'bootstrap.servers'] if @nr_config[:'bootstrap.servers']
|
57
|
+
hosts << @nr_config[:'metadata.broker.list'] if @nr_config[:'metadata.broker.list']
|
58
|
+
hosts.each do |host|
|
59
|
+
NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}/#{action}/#{topic}", 1)
|
60
|
+
NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}", 1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module RdkafkaConfig
|
66
|
+
def set_nr_config(producer_or_consumer)
|
67
|
+
producer_or_consumer.instance_variable_set(:@nr_config, self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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 'instrumentation'
|
6
|
+
|
7
|
+
module NewRelic::Agent::Instrumentation
|
8
|
+
module RdkafkaProducer
|
9
|
+
module Prepend
|
10
|
+
include NewRelic::Agent::Instrumentation::Rdkafka
|
11
|
+
|
12
|
+
def produce(**kwargs)
|
13
|
+
produce_with_new_relic(kwargs) do |headers|
|
14
|
+
kwargs[:headers] = headers
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module RdkafkaConsumer
|
22
|
+
module Prepend
|
23
|
+
include NewRelic::Agent::Instrumentation::Rdkafka
|
24
|
+
|
25
|
+
def each
|
26
|
+
super do |message|
|
27
|
+
each_with_new_relic(message) do
|
28
|
+
yield(message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module RdkafkaConfig
|
36
|
+
module Prepend
|
37
|
+
include NewRelic::Agent::Instrumentation::RdkafkaConfig
|
38
|
+
|
39
|
+
if defined?(::Rdkafka) && Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0')
|
40
|
+
def producer(**kwargs)
|
41
|
+
super.tap do |producer|
|
42
|
+
set_nr_config(producer)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def consumer(**kwargs)
|
47
|
+
super.tap do |consumer|
|
48
|
+
set_nr_config(consumer)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
else # older versions
|
52
|
+
def producer
|
53
|
+
super.tap do |producer|
|
54
|
+
set_nr_config(producer)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def consumer
|
59
|
+
super.tap do |consumer|
|
60
|
+
set_nr_config(consumer)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
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
|
+
DependencyDetection.defer do
|
6
|
+
named :rdkafka
|
7
|
+
|
8
|
+
depends_on do
|
9
|
+
defined?(Rdkafka)
|
10
|
+
end
|
11
|
+
|
12
|
+
executes do
|
13
|
+
NewRelic::Agent.logger.info('Installing rdkafka instrumentation')
|
14
|
+
|
15
|
+
require_relative 'rdkafka/instrumentation'
|
16
|
+
require_relative 'rdkafka/chain'
|
17
|
+
require_relative 'rdkafka/prepend'
|
18
|
+
|
19
|
+
if use_prepend?
|
20
|
+
prepend_instrument Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend
|
21
|
+
prepend_instrument Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend
|
22
|
+
prepend_instrument Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend
|
23
|
+
else
|
24
|
+
chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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 RubyKafka::Chain
|
7
|
+
def self.instrument!
|
8
|
+
::Kafka::Producer.class_eval do
|
9
|
+
include NewRelic::Agent::Instrumentation::RubyKafka
|
10
|
+
|
11
|
+
alias_method(:produce_without_new_relic, :produce)
|
12
|
+
|
13
|
+
def produce(value, **kwargs)
|
14
|
+
produce_with_new_relic(value, **kwargs) do |headers|
|
15
|
+
kwargs[:headers] = headers
|
16
|
+
produce_without_new_relic(value, **kwargs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
::Kafka::Consumer.class_eval do
|
22
|
+
include NewRelic::Agent::Instrumentation::RubyKafka
|
23
|
+
|
24
|
+
alias_method(:each_message_without_new_relic, :each_message)
|
25
|
+
|
26
|
+
def each_message(*args)
|
27
|
+
each_message_without_new_relic(*args) do |message|
|
28
|
+
each_message_with_new_relic(message) do
|
29
|
+
yield(message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
::Kafka::Client.class_eval do
|
36
|
+
include NewRelic::Agent::Instrumentation::RubyKafkaConfig
|
37
|
+
|
38
|
+
alias_method(:producer_without_new_relic, :producer)
|
39
|
+
alias_method(:consumer_without_new_relic, :consumer)
|
40
|
+
|
41
|
+
def producer(**kwargs)
|
42
|
+
producer_without_new_relic(**kwargs).tap do |producer|
|
43
|
+
set_nr_config(producer)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def consumer(**kwargs)
|
48
|
+
consumer_without_new_relic(**kwargs).tap do |consumer|
|
49
|
+
set_nr_config(consumer)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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 'new_relic/agent/messaging'
|
6
|
+
|
7
|
+
module NewRelic::Agent::Instrumentation
|
8
|
+
module RubyKafka
|
9
|
+
MESSAGING_LIBRARY = 'Kafka'
|
10
|
+
PRODUCE = 'Produce'
|
11
|
+
CONSUME = 'Consume'
|
12
|
+
|
13
|
+
INSTRUMENTATION_NAME = 'ruby-kafka'
|
14
|
+
|
15
|
+
def produce_with_new_relic(value, **kwargs)
|
16
|
+
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
17
|
+
|
18
|
+
topic_name = kwargs[:topic]
|
19
|
+
segment = NewRelic::Agent::Tracer.start_message_broker_segment(
|
20
|
+
action: :produce,
|
21
|
+
library: MESSAGING_LIBRARY,
|
22
|
+
destination_type: :topic,
|
23
|
+
destination_name: topic_name
|
24
|
+
)
|
25
|
+
create_kafka_metrics(action: PRODUCE, topic: topic_name)
|
26
|
+
|
27
|
+
headers = kwargs[:headers] || {}
|
28
|
+
::NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(headers)
|
29
|
+
|
30
|
+
NewRelic::Agent::Tracer.capture_segment_error(segment) { yield(headers) }
|
31
|
+
ensure
|
32
|
+
segment&.finish
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_message_with_new_relic(message)
|
36
|
+
NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
|
37
|
+
|
38
|
+
headers = message&.headers || {}
|
39
|
+
topic_name = message&.topic
|
40
|
+
|
41
|
+
NewRelic::Agent::Messaging.wrap_message_broker_consume_transaction(
|
42
|
+
library: MESSAGING_LIBRARY,
|
43
|
+
destination_type: :topic,
|
44
|
+
destination_name: topic_name,
|
45
|
+
headers: headers,
|
46
|
+
action: :consume
|
47
|
+
) do
|
48
|
+
create_kafka_metrics(action: CONSUME, topic: topic_name)
|
49
|
+
yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_kafka_metrics(action:, topic:)
|
54
|
+
@nr_config.each do |seed_broker|
|
55
|
+
host = "#{seed_broker&.host}:#{seed_broker&.port}"
|
56
|
+
NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}/#{action}/#{topic}", 1)
|
57
|
+
NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}", 1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module RubyKafkaConfig
|
63
|
+
def set_nr_config(producer_or_consumer)
|
64
|
+
producer_or_consumer.instance_variable_set(:@nr_config, @seed_brokers)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,50 @@
|
|
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 RubyKafkaProducer
|
7
|
+
module Prepend
|
8
|
+
include NewRelic::Agent::Instrumentation::RubyKafka
|
9
|
+
|
10
|
+
def produce(value, **kwargs)
|
11
|
+
produce_with_new_relic(value, **kwargs) do |headers|
|
12
|
+
kwargs[:headers] = headers
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module RubyKafkaConsumer
|
20
|
+
module Prepend
|
21
|
+
include NewRelic::Agent::Instrumentation::RubyKafka
|
22
|
+
|
23
|
+
def each_message(*args)
|
24
|
+
super do |message|
|
25
|
+
each_message_with_new_relic(message) do
|
26
|
+
yield(message)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module RubyKafkaClient
|
34
|
+
module Prepend
|
35
|
+
include NewRelic::Agent::Instrumentation::RubyKafkaConfig
|
36
|
+
|
37
|
+
def producer(**kwargs)
|
38
|
+
super.tap do |producer|
|
39
|
+
set_nr_config(producer)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def consumer(**kwargs)
|
44
|
+
super.tap do |consumer|
|
45
|
+
set_nr_config(consumer)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
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 'ruby_kafka/instrumentation'
|
6
|
+
require_relative 'ruby_kafka/chain'
|
7
|
+
require_relative 'ruby_kafka/prepend'
|
8
|
+
|
9
|
+
DependencyDetection.defer do
|
10
|
+
named :'ruby_kafka'
|
11
|
+
|
12
|
+
depends_on do
|
13
|
+
defined?(Kafka)
|
14
|
+
end
|
15
|
+
|
16
|
+
executes do
|
17
|
+
NewRelic::Agent.logger.info('Installing ruby-kafka instrumentation')
|
18
|
+
|
19
|
+
if use_prepend?
|
20
|
+
prepend_instrument Kafka::Producer, NewRelic::Agent::Instrumentation::RubyKafkaProducer::Prepend
|
21
|
+
prepend_instrument Kafka::Consumer, NewRelic::Agent::Instrumentation::RubyKafkaConsumer::Prepend
|
22
|
+
prepend_instrument Kafka::Client, NewRelic::Agent::Instrumentation::RubyKafkaClient::Prepend
|
23
|
+
else
|
24
|
+
chain_instrument NewRelic::Agent::Instrumentation::RubyKafka::Chain
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -11,7 +11,10 @@ module NewRelic::Agent::Instrumentation
|
|
11
11
|
|
12
12
|
begin
|
13
13
|
segment = NewRelic::Agent::Tracer.start_segment(
|
14
|
-
name: metric_name(
|
14
|
+
name: metric_name(
|
15
|
+
self.class.respond_to?(:identifier) ? self.class.identifier : nil,
|
16
|
+
self.class.name
|
17
|
+
)
|
15
18
|
)
|
16
19
|
yield
|
17
20
|
rescue => e
|
@@ -164,9 +164,8 @@ module NewRelic
|
|
164
164
|
|
165
165
|
def add_ssl_for_http(data)
|
166
166
|
ssl_for_http = NewRelic::Agent.config[:'browser_monitoring.ssl_for_http']
|
167
|
-
|
168
|
-
|
169
|
-
end
|
167
|
+
|
168
|
+
data[SSL_FOR_HTTP_KEY] = ssl_for_http if ssl_for_http
|
170
169
|
end
|
171
170
|
|
172
171
|
def add_attributes(data, txn)
|
@@ -117,7 +117,8 @@ module NewRelic
|
|
117
117
|
queue_name: nil,
|
118
118
|
exchange_type: nil,
|
119
119
|
reply_to: nil,
|
120
|
-
correlation_id: nil
|
120
|
+
correlation_id: nil,
|
121
|
+
action: nil)
|
121
122
|
|
122
123
|
state = Tracer.state
|
123
124
|
return yield if state.current_transaction
|
@@ -125,12 +126,12 @@ module NewRelic
|
|
125
126
|
txn = nil
|
126
127
|
|
127
128
|
begin
|
128
|
-
txn_name = transaction_name(library, destination_type, destination_name)
|
129
|
+
txn_name = transaction_name(library, destination_type, destination_name, action)
|
129
130
|
|
130
131
|
txn = Tracer.start_transaction(name: txn_name, category: :message)
|
131
|
-
|
132
132
|
if headers
|
133
|
-
|
133
|
+
NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) # to handle the new w3c headers
|
134
|
+
txn.distributed_tracer.consume_message_headers(headers, state, library) # to do the expected old things
|
134
135
|
CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v|
|
135
136
|
txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil?
|
136
137
|
end
|
@@ -327,12 +328,17 @@ module NewRelic
|
|
327
328
|
NewRelic::Agent.config[:'message_tracer.segment_parameters.enabled']
|
328
329
|
end
|
329
330
|
|
330
|
-
def transaction_name(library, destination_type, destination_name)
|
331
|
+
def transaction_name(library, destination_type, destination_name, action = nil)
|
331
332
|
transaction_name = Transaction::MESSAGE_PREFIX + library
|
332
333
|
transaction_name << NewRelic::SLASH
|
333
334
|
transaction_name << Transaction::MessageBrokerSegment::TYPES[destination_type]
|
334
335
|
transaction_name << NewRelic::SLASH
|
335
336
|
|
337
|
+
if action == :consume
|
338
|
+
transaction_name << 'Consume'
|
339
|
+
transaction_name << NewRelic::SLASH
|
340
|
+
end
|
341
|
+
|
336
342
|
case destination_type
|
337
343
|
when :queue
|
338
344
|
transaction_name << Transaction::MessageBrokerSegment::NAMED
|
@@ -24,7 +24,7 @@ module NewRelic
|
|
24
24
|
@referer = referer_from_request(request)
|
25
25
|
@accept = attribute_from_env(request, HTTP_ACCEPT_HEADER_KEY)
|
26
26
|
@content_length = content_length_from_request(request)
|
27
|
-
@content_type =
|
27
|
+
@content_type = content_type_attribute_from_request(request)
|
28
28
|
@host = attribute_from_request(request, :host)
|
29
29
|
@port = port_from_request(request)
|
30
30
|
@user_agent = attribute_from_request(request, :user_agent)
|
@@ -127,6 +127,18 @@ module NewRelic
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
+
def content_type_attribute_from_request(request)
|
131
|
+
# Rails 7.0 changed the behavior of `content_type`. We want just the MIME type, so use `media_type` if available.
|
132
|
+
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is
|
133
|
+
content_type = if request.respond_to?(:media_type)
|
134
|
+
:media_type
|
135
|
+
elsif request.respond_to?(:content_type)
|
136
|
+
:content_type
|
137
|
+
end
|
138
|
+
|
139
|
+
request.send(content_type) if content_type
|
140
|
+
end
|
141
|
+
|
130
142
|
def attribute_from_env(request, key)
|
131
143
|
if env = attribute_from_request(request, :env)
|
132
144
|
env[key]
|
data/lib/new_relic/agent.rb
CHANGED
@@ -85,6 +85,10 @@ module NewRelic
|
|
85
85
|
# An exception that forces an agent to stop reporting until its mongrel is restarted.
|
86
86
|
class ForceDisconnectException < StandardError; end
|
87
87
|
|
88
|
+
# Error handling for the automated custom instrumentation tracer logic
|
89
|
+
class AutomaticTracerParseException < StandardError; end
|
90
|
+
class AutomaticTracerTraceException < StandardError; end
|
91
|
+
|
88
92
|
# An exception that forces an agent to restart.
|
89
93
|
class ForceRestartException < StandardError
|
90
94
|
def message
|
@@ -109,6 +113,9 @@ module NewRelic
|
|
109
113
|
# placeholder name used when we cannot determine a transaction's name
|
110
114
|
UNKNOWN_METRIC = '(unknown)'.freeze
|
111
115
|
LLM_FEEDBACK_MESSAGE = 'LlmFeedbackMessage'
|
116
|
+
# give the observed app time to load the code that automatic tracers have
|
117
|
+
# been configured for
|
118
|
+
AUTOMATIC_TRACER_MAX_ATTEMPTS = 60 # 60 = try about twice a second for 30 seconds
|
112
119
|
|
113
120
|
attr_reader :error_group_callback
|
114
121
|
attr_reader :llm_token_count_callback
|
@@ -163,6 +170,92 @@ module NewRelic
|
|
163
170
|
end
|
164
171
|
end
|
165
172
|
|
173
|
+
# @api private
|
174
|
+
def self.add_automatic_method_tracers(arr)
|
175
|
+
return unless arr
|
176
|
+
return arr if arr.respond_to?(:empty?) && arr.empty?
|
177
|
+
|
178
|
+
arr = arr.split(/\s*,\s*/) if arr.is_a?(String)
|
179
|
+
|
180
|
+
add_tracers_once_methods_are_defined(arr.dup)
|
181
|
+
|
182
|
+
arr
|
183
|
+
end
|
184
|
+
|
185
|
+
# spawn a thread that will attempt to establish a tracer for each of the
|
186
|
+
# configured methods. the thread will continue to keep trying with each
|
187
|
+
# tracer until one of the following happens:
|
188
|
+
# - the tracer is successfully established
|
189
|
+
# - the configured method string couldn't be parsed
|
190
|
+
# - establishing a tracer for a successfully parsed string failed
|
191
|
+
# - the maximum number of attempts has been reached
|
192
|
+
# the thread will only be spawned once per agent initialization, to account
|
193
|
+
# for configuration reloading scenarios.
|
194
|
+
#
|
195
|
+
# @api private
|
196
|
+
def self.add_tracers_once_methods_are_defined(notations)
|
197
|
+
# this class method can be invoked multiple times at agent startup, so
|
198
|
+
# we return asap here instead of using a traditional memoization of
|
199
|
+
# waiting for the method's body to finish being executed
|
200
|
+
if defined?(@add_tracers_once_methods_are_defined)
|
201
|
+
return
|
202
|
+
else
|
203
|
+
@add_tracers_once_methods_are_defined = true
|
204
|
+
end
|
205
|
+
|
206
|
+
Thread.new do
|
207
|
+
AUTOMATIC_TRACER_MAX_ATTEMPTS.times do
|
208
|
+
notations.delete_if { |notation| prep_tracer_for(notation) }
|
209
|
+
|
210
|
+
break if notations.empty?
|
211
|
+
|
212
|
+
sleep 0.5
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# returns `true` if the notation string has either been successfully
|
218
|
+
# processed or raised an error during processing. returns `false` if the
|
219
|
+
# string seems good but the (customer) code to be traced has not yet been
|
220
|
+
# loaded into the Ruby VM
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
def self.prep_tracer_for(fully_qualified_method_notation)
|
224
|
+
delimiters = fully_qualified_method_notation.scan(/\.|#/)
|
225
|
+
raise AutomaticTracerParseException.new("Expected exactly one '.' or '#' delimiter.") unless delimiters.size == 1
|
226
|
+
|
227
|
+
delimiter = delimiters.first
|
228
|
+
namespace, method_name = fully_qualified_method_notation.split(delimiter)
|
229
|
+
unless namespace && !namespace.empty?
|
230
|
+
raise AutomaticTracerParseException.new("Nothing found to the left of the #{delimiter} delimiter.")
|
231
|
+
end
|
232
|
+
unless method_name && !method_name.empty?
|
233
|
+
raise AutomaticTracerParseException.new("Nothing found to the right of the #{delimiter} delimiter.")
|
234
|
+
end
|
235
|
+
|
236
|
+
begin
|
237
|
+
klass = ::NewRelic::LanguageSupport.constantize(namespace)
|
238
|
+
return false unless klass
|
239
|
+
|
240
|
+
klass_to_trace = delimiter.eql?('.') ? klass.singleton_class : klass
|
241
|
+
add_or_defer_method_tracer(klass_to_trace, method_name, nil, {})
|
242
|
+
rescue StandardError => e
|
243
|
+
raise AutomaticTracerTraceException.new("#{e.class} - #{e.message}")
|
244
|
+
end
|
245
|
+
|
246
|
+
true
|
247
|
+
rescue AutomaticTracerParseException => e
|
248
|
+
NewRelic::Agent.logger.error('Unable to parse out a usable method name to trace. Expected a valid, fully ' \
|
249
|
+
"qualified method notation. Got: '#{fully_qualified_method_notation}'. " \
|
250
|
+
"Error: #{e.message}")
|
251
|
+
true
|
252
|
+
rescue AutomaticTracerTraceException => e
|
253
|
+
NewRelic::Agent.logger.error('Unable to automatically apply a tracer to method ' \
|
254
|
+
"'#{fully_qualified_method_notation}'. Error: #{e.message}")
|
255
|
+
true
|
256
|
+
end
|
257
|
+
|
258
|
+
# @api private
|
166
259
|
def add_deferred_method_tracers_now
|
167
260
|
@tracer_lock.synchronize do
|
168
261
|
@tracer_queue.each do |receiver, method_name, metric_name, options|
|
@@ -9,7 +9,7 @@ module NewRelic
|
|
9
9
|
module Frameworks
|
10
10
|
class Rails4 < NewRelic::Control::Frameworks::Rails3
|
11
11
|
def rails_gem_list
|
12
|
-
if
|
12
|
+
if Bundler.rubygems.respond_to?(:installed_specs)
|
13
13
|
Bundler.rubygems.installed_specs.map { |gem| "#{gem.name} (#{gem.version})" }
|
14
14
|
else
|
15
15
|
Bundler.rubygems.all_specs.map { |gem| "#{gem.name} (#{gem.version})" }
|
@@ -44,7 +44,7 @@ module NewRelic
|
|
44
44
|
####################################
|
45
45
|
report_on('Gems') do
|
46
46
|
begin
|
47
|
-
if
|
47
|
+
if Bundler.rubygems.respond_to?(:installed_specs)
|
48
48
|
Bundler.rubygems.installed_specs.map { |gem| "#{gem.name}(#{gem.version})" }
|
49
49
|
else
|
50
50
|
Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" }
|
@@ -90,7 +90,7 @@ module NewRelic
|
|
90
90
|
def bundled_gem?(gem_name)
|
91
91
|
return false unless defined?(Bundler)
|
92
92
|
|
93
|
-
if
|
93
|
+
if Bundler.rubygems.respond_to?(:installed_specs)
|
94
94
|
Bundler.rubygems.installed_specs.map(&:name).include?(gem_name)
|
95
95
|
else
|
96
96
|
Bundler.rubygems.all_specs.map(&:name).include?(gem_name)
|
data/lib/new_relic/version.rb
CHANGED
data/newrelic.yml
CHANGED
@@ -113,6 +113,53 @@ common: &default_settings
|
|
113
113
|
# Specifies a path to the audit log file (including the filename).
|
114
114
|
# audit_log.path: log/newrelic_audit.log
|
115
115
|
|
116
|
+
# An array of CLASS#METHOD (for instance methods) and/or CLASS.METHOD (for class
|
117
|
+
# methods) strings representing Ruby methods for the agent to automatically add
|
118
|
+
# custom instrumentation to without the need for altering any of the source code
|
119
|
+
# that defines the methods.
|
120
|
+
#
|
121
|
+
# Use fully qualified class names (using the :: delimiter) that include any
|
122
|
+
# module or class namespacing.
|
123
|
+
#
|
124
|
+
# Here is some Ruby source code that defines a render_png instance method for an
|
125
|
+
# Image class and a notify class method for a User class, both within a
|
126
|
+
# MyCompany module namespace:
|
127
|
+
#
|
128
|
+
# module MyCompany
|
129
|
+
# class Image
|
130
|
+
# def render_png
|
131
|
+
# # code to render a PNG
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# class User
|
136
|
+
# def self.notify
|
137
|
+
# # code to notify users
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# Given that source code, the newrelic.yml config file might request
|
143
|
+
# instrumentation for both of these methods like so:
|
144
|
+
#
|
145
|
+
# automatic_custom_instrumentation_method_list:
|
146
|
+
# - MyCompany::Image#render_png
|
147
|
+
# - MyCompany::User.notify
|
148
|
+
#
|
149
|
+
# That configuration example uses YAML array syntax to specify both methods.
|
150
|
+
# Alternatively, a comma-delimited string can be used instead:
|
151
|
+
#
|
152
|
+
# automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify'
|
153
|
+
#
|
154
|
+
# Whitespace around the comma(s) in the list is optional. When configuring the
|
155
|
+
# agent with a list of methods via the
|
156
|
+
# NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST environment variable,
|
157
|
+
# this comma-delimited string format should be used:
|
158
|
+
#
|
159
|
+
# export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify'
|
160
|
+
#
|
161
|
+
# automatic_custom_instrumentation_method_list: []
|
162
|
+
|
116
163
|
# Specify a list of constants that should prevent the agent from starting
|
117
164
|
# automatically. Separate individual constants with a comma ,. For example,
|
118
165
|
# "Rails::Console,UninstrumentedBackgroundJob".
|
@@ -535,6 +582,10 @@ common: &default_settings
|
|
535
582
|
# prepend, chain, disabled.
|
536
583
|
# instrumentation.rake: auto
|
537
584
|
|
585
|
+
# Controls auto-instrumentation of the rdkafka library at start-up. May be one
|
586
|
+
# of auto, prepend, chain, disabled.
|
587
|
+
# instrumentation.rdkafka: auto
|
588
|
+
|
538
589
|
# Controls auto-instrumentation of Redis at start-up. May be one of: auto,
|
539
590
|
# prepend, chain, disabled.
|
540
591
|
# instrumentation.redis: auto
|
@@ -547,6 +598,10 @@ common: &default_settings
|
|
547
598
|
# prepend, chain, disabled.
|
548
599
|
# instrumentation.roda: auto
|
549
600
|
|
601
|
+
# Controls auto-instrumentation of the ruby-kafka library at start-up. May be
|
602
|
+
# one of auto, prepend, chain, disabled.
|
603
|
+
# instrumentation.ruby_kafka: auto
|
604
|
+
|
550
605
|
# Controls auto-instrumentation of the ruby-openai gem at start-up. May be one
|
551
606
|
# of: auto, prepend, chain, disabled. Defaults to disabled in high security
|
552
607
|
# mode.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: newrelic_rpm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.
|
4
|
+
version: 9.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tanna McClure
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2024-
|
14
|
+
date: 2024-09-30 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -509,6 +509,10 @@ files:
|
|
509
509
|
- lib/new_relic/agent/instrumentation/rake/chain.rb
|
510
510
|
- lib/new_relic/agent/instrumentation/rake/instrumentation.rb
|
511
511
|
- lib/new_relic/agent/instrumentation/rake/prepend.rb
|
512
|
+
- lib/new_relic/agent/instrumentation/rdkafka.rb
|
513
|
+
- lib/new_relic/agent/instrumentation/rdkafka/chain.rb
|
514
|
+
- lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb
|
515
|
+
- lib/new_relic/agent/instrumentation/rdkafka/prepend.rb
|
512
516
|
- lib/new_relic/agent/instrumentation/redis.rb
|
513
517
|
- lib/new_relic/agent/instrumentation/redis/chain.rb
|
514
518
|
- lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb
|
@@ -527,6 +531,10 @@ files:
|
|
527
531
|
- lib/new_relic/agent/instrumentation/roda/instrumentation.rb
|
528
532
|
- lib/new_relic/agent/instrumentation/roda/prepend.rb
|
529
533
|
- lib/new_relic/agent/instrumentation/roda/roda_transaction_namer.rb
|
534
|
+
- lib/new_relic/agent/instrumentation/ruby_kafka.rb
|
535
|
+
- lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb
|
536
|
+
- lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb
|
537
|
+
- lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb
|
530
538
|
- lib/new_relic/agent/instrumentation/ruby_openai.rb
|
531
539
|
- lib/new_relic/agent/instrumentation/ruby_openai/chain.rb
|
532
540
|
- lib/new_relic/agent/instrumentation/ruby_openai/instrumentation.rb
|
@@ -770,7 +778,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
770
778
|
- !ruby/object:Gem::Version
|
771
779
|
version: 1.3.1
|
772
780
|
requirements: []
|
773
|
-
rubygems_version: 3.5.
|
781
|
+
rubygems_version: 3.5.16
|
774
782
|
signing_key:
|
775
783
|
specification_version: 4
|
776
784
|
summary: New Relic Ruby Agent
|