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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -5
  3. data/lib/new_relic/agent/configuration/default_source.rb +82 -1
  4. data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
  5. data/lib/new_relic/agent/configuration/manager.rb +16 -1
  6. data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
  7. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +3 -2
  8. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  9. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +71 -0
  10. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  11. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +66 -0
  12. data/lib/new_relic/agent/instrumentation/rdkafka.rb +27 -0
  13. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  14. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  15. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +50 -0
  16. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +27 -0
  17. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +4 -1
  18. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  19. data/lib/new_relic/agent/messaging.rb +11 -5
  20. data/lib/new_relic/agent/transaction/request_attributes.rb +13 -1
  21. data/lib/new_relic/agent.rb +93 -0
  22. data/lib/new_relic/control/frameworks/rails4.rb +1 -1
  23. data/lib/new_relic/environment_report.rb +1 -1
  24. data/lib/new_relic/language_support.rb +1 -1
  25. data/lib/new_relic/version.rb +1 -1
  26. data/newrelic.yml +55 -0
  27. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4322e3b796a1a4656be8f68f98c18f79a4f6f86f3dd36ba1186def73a993c6db
4
- data.tar.gz: e87b88f88a979bc1ff4259643081308670dd860750d12e21cfb6cd45fa3b6004
3
+ metadata.gz: b0ade9f1a1a36e6382100a271c5e54a2c426c27b95884a15eac8d6740dcee29b
4
+ data.tar.gz: 18f728d4289bfda058bd54d7d7798a33c3a43d45de83f543b86dd573ff76283e
5
5
  SHA512:
6
- metadata.gz: e53d1172e55be6cf3f66f12f3926361af7485361e1e8c2e097f725241148bbbc5cb59853ce6d255a7c34871f3530424466b621b5cab467383d9e654c0c8108d4
7
- data.tar.gz: b61902a91a3909760b52797c74f7ef24e5b6f5c282ffd1bfb95451585b052004323fe6c3eb9843e208dcdfdd9382684d8f01a950051e8f7d082269d2ed7134e0
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 => nil,
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] = value.split(/\s*,\s*/)
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 @cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled']
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
- :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 => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/,
19
- :oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
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
- arn = get_arn(args[0])
35
- segment&.add_agent_attribute('cloud.resource_id', arn) if arn
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
- ((Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0') && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) ||
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(self.class.identifier, self.class.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
- unless ssl_for_http.nil?
168
- data[SSL_FOR_HTTP_KEY] = ssl_for_http
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
- txn.distributed_tracer.consume_message_headers(headers, state, RABBITMQ_TRANSPORT_TYPE)
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 = attribute_from_request(request, :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]
@@ -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 Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
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 Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
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 Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
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)
@@ -6,7 +6,7 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 13
9
+ MINOR = 14
10
10
  TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
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.13.0
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-08-22 00:00:00.000000000 Z
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.11
781
+ rubygems_version: 3.5.16
774
782
  signing_key:
775
783
  specification_version: 4
776
784
  summary: New Relic Ruby Agent