From 78857ad5bc2d54652a7dcae5f8350161fb38c1da Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 7 Aug 2024 15:43:28 -0500 Subject: [PATCH 01/81] add rdkafka instrumentation --- .../agent/configuration/default_source.rb | 8 +++ .../agent/instrumentation/rdkafka.rb | 27 +++++++++ .../agent/instrumentation/rdkafka/chain.rb | 21 +++++++ .../rdkafka/instrumentation.rb | 56 +++++++++++++++++++ .../agent/instrumentation/rdkafka/prepend.rb | 32 +++++++++++ lib/new_relic/agent/messaging.rb | 14 +++-- test/multiverse/suites/rdkafka/Envfile | 9 +++ .../suites/rdkafka/config/newrelic.yml | 19 +++++++ .../rdkafka/rdkafka_instrumentation_test.rb | 15 +++++ 9 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 lib/new_relic/agent/instrumentation/rdkafka.rb create mode 100644 lib/new_relic/agent/instrumentation/rdkafka/chain.rb create mode 100644 lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb create mode 100644 lib/new_relic/agent/instrumentation/rdkafka/prepend.rb create mode 100644 test/multiverse/suites/rdkafka/Envfile create mode 100644 test/multiverse/suites/rdkafka/config/newrelic.yml create mode 100644 test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index c7056415b3..b593bbcb63 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1463,6 +1463,14 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :allowed_from_server => false, :description => 'Controls auto-instrumentation of bunny at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.' }, + :'instrumentation.rdkafka' => { + :default => 'auto', + :public => true, + :type => String, + :dynamic_name => true, + :allowed_from_server => false, + :description => 'Controls auto-instrumentation of the rdkafka library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.' + }, :'instrumentation.aws_sqs' => { :default => 'auto', :public => true, diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb new file mode 100644 index 0000000000..d69ebaa658 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -0,0 +1,27 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +require_relative 'rdkafka/instrumentation' +require_relative 'rdkafka/chain' +require_relative 'rdkafka/prepend' + +DependencyDetection.defer do + named :rdkafka + + depends_on do + defined?(::Rdkafka) + end + + executes do + NewRelic::Agent.logger.info('Installing rdkafka instrumentation') + puts '***NR*** Installing rdkafka instrumentation' + + if use_prepend? + prepend_instrument ::Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend + prepend_instrument ::Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend + else + # chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain + end + end +end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb new file mode 100644 index 0000000000..a9cbf3326c --- /dev/null +++ b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb @@ -0,0 +1,21 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +module NewRelic::Agent::Instrumentation + module Rdkafka::Chain + def self.instrument! + ::Rdkafka.class_eval do + include NewRelic::Agent::Instrumentation::Rdkafka + + alias_method(:method_to_instrument_without_new_relic, :method_to_instrument) + + def method_to_instrument(*args) + method_to_instrument_with_new_relic(*args) do + method_to_instrument_without_new_relic(*args) + end + end + end + end + end +end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb new file mode 100644 index 0000000000..905525eebc --- /dev/null +++ b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb @@ -0,0 +1,56 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +require 'new_relic/agent/messaging' + +module NewRelic::Agent::Instrumentation + module Rdkafka + MESSAGING_LIBRARY = 'Kafka' + + # TODO: serialization + def produce_with_new_relic(*args) + topic_name = args[0][:topic] + segment = NewRelic::Agent::Tracer.start_message_broker_segment( + action: :produce, + library: MESSAGING_LIBRARY, + destination_type: :topic, + destination_name: topic_name + ) + + headers = args[0][:headers] || {} + ::NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(headers) + yield(headers) # wrap in error catching + ensure + segment&.finish + end + + # Adds two new metrics that are used to synthesize the entity relationship between AWS MSK and the APM entity: + # MessageBroker/Kafka/Nodes/: + # MessageBroker/Kafka/Nodes/:// + # where : is the Kafka server port, is either Produce or Consume and is the Kafka topic name + # Updates Kafka container tests to validate the new metrics are present + + # Message/Kafka/Topic/Consume/Named/{topic_name} + # OtherTransaction/Message/Kafka/Topic/Consume/Named/ruby-test-topic + def each_with_new_relic(message) + puts '***NR*** each_with_new_relic' + headers = message&.headers || {} + topic_name = message&.topic + NewRelic::Agent::Messaging.wrap_message_broker_consume_transaction( + library: MESSAGING_LIBRARY, + destination_type: :topic, + destination_name: topic_name, + headers: headers, + action: :consume + ) do + yield + end + end + + def create_kafka_metrics(action:, topic:) + # NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}:#{port}/#{action}/#{topic}", 1) + # NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}:#{port}", 1) + end + end +end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb new file mode 100644 index 0000000000..d4fc8d7723 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb @@ -0,0 +1,32 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +module NewRelic::Agent::Instrumentation + module RdkafkaProducer + module Prepend + include NewRelic::Agent::Instrumentation::Rdkafka + + def produce(**kwargs) + produce_with_new_relic(kwargs) do |headers| + kwargs[:headers] = headers + super + end + end + end + end + + module RdkafkaConsumer + module Prepend + include NewRelic::Agent::Instrumentation::Rdkafka + + def each + super do |message| + each_with_new_relic(message) do + yield(message) + end + end + end + end + end +end diff --git a/lib/new_relic/agent/messaging.rb b/lib/new_relic/agent/messaging.rb index 01bc9b38c3..4e76016582 100644 --- a/lib/new_relic/agent/messaging.rb +++ b/lib/new_relic/agent/messaging.rb @@ -117,7 +117,8 @@ def wrap_message_broker_consume_transaction(library:, queue_name: nil, exchange_type: nil, reply_to: nil, - correlation_id: nil) + correlation_id: nil, + action: nil) state = Tracer.state return yield if state.current_transaction @@ -125,12 +126,12 @@ def wrap_message_broker_consume_transaction(library:, txn = nil begin - txn_name = transaction_name(library, destination_type, destination_name) + txn_name = transaction_name(library, destination_type, destination_name, action) txn = Tracer.start_transaction(name: txn_name, category: :message) if headers - txn.distributed_tracer.consume_message_headers(headers, state, RABBITMQ_TRANSPORT_TYPE) + txn.distributed_tracer.consume_message_headers(headers, state, library) CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? end @@ -327,12 +328,17 @@ def segment_parameters_enabled? NewRelic::Agent.config[:'message_tracer.segment_parameters.enabled'] end - def transaction_name(library, destination_type, destination_name) + def transaction_name(library, destination_type, destination_name, action = nil) transaction_name = Transaction::MESSAGE_PREFIX + library transaction_name << NewRelic::SLASH transaction_name << Transaction::MessageBrokerSegment::TYPES[destination_type] transaction_name << NewRelic::SLASH + if action == :consume + transaction_name << 'Consume' + transaction_name << NewRelic::SLASH + end + case destination_type when :queue transaction_name << Transaction::MessageBrokerSegment::NAMED diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile new file mode 100644 index 0000000000..45e130823c --- /dev/null +++ b/test/multiverse/suites/rdkafka/Envfile @@ -0,0 +1,9 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +instrumentation_methods :chain, :prepend + +gemfile <<~RB + gem 'rdkafka' +RB diff --git a/test/multiverse/suites/rdkafka/config/newrelic.yml b/test/multiverse/suites/rdkafka/config/newrelic.yml new file mode 100644 index 0000000000..bdc09e5915 --- /dev/null +++ b/test/multiverse/suites/rdkafka/config/newrelic.yml @@ -0,0 +1,19 @@ +--- +development: + error_collector: + enabled: true + apdex_t: 0.5 + monitor_mode: true + license_key: bootstrap_newrelic_admin_license_key_000 + instrumentation: + rdkafka: <%= $instrumentation_method %> + app_name: test + log_level: debug + host: 127.0.0.1 + api_host: 127.0.0.1 + transaction_trace: + record_sql: obfuscated + enabled: true + stack_trace_threshold: 0.5 + transaction_threshold: 1.0 + capture_params: false diff --git a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb new file mode 100644 index 0000000000..8273e16541 --- /dev/null +++ b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb @@ -0,0 +1,15 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +class RdkafkaInstrumentationTest < Minitest::Test + def setup + @stats_engine = NewRelic::Agent.instance.stats_engine + end + + def teardown + NewRelic::Agent.instance.stats_engine.clear_stats + end + + # Add tests here +end From 886cdc3c6746f7c0383d7eabed6f3bed54fbfb23 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 8 Aug 2024 10:41:58 -0500 Subject: [PATCH 02/81] add extra metrics --- .../agent/instrumentation/rdkafka.rb | 1 + .../rdkafka/instrumentation.rb | 35 ++++++++++++------- .../agent/instrumentation/rdkafka/prepend.rb | 18 ++++++++++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index d69ebaa658..0da044ba69 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -18,6 +18,7 @@ puts '***NR*** Installing rdkafka instrumentation' if use_prepend? + prepend_instrument ::Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend prepend_instrument ::Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend prepend_instrument ::Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend else diff --git a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb index 905525eebc..b3c4a95f16 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb @@ -7,8 +7,10 @@ module NewRelic::Agent::Instrumentation module Rdkafka MESSAGING_LIBRARY = 'Kafka' + PRODUCE = 'Produce' + CONSUME = 'Consume' - # TODO: serialization + # TODO: serialization ? def produce_with_new_relic(*args) topic_name = args[0][:topic] segment = NewRelic::Agent::Tracer.start_message_broker_segment( @@ -17,26 +19,20 @@ def produce_with_new_relic(*args) destination_type: :topic, destination_name: topic_name ) + create_kafka_metrics(action: PRODUCE, topic: topic_name) headers = args[0][:headers] || {} ::NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(headers) - yield(headers) # wrap in error catching + + NewRelic::Agent::Tracer.capture_segment_error(segment) { yield(headers) } ensure segment&.finish end - # Adds two new metrics that are used to synthesize the entity relationship between AWS MSK and the APM entity: - # MessageBroker/Kafka/Nodes/: - # MessageBroker/Kafka/Nodes/:// - # where : is the Kafka server port, is either Produce or Consume and is the Kafka topic name - # Updates Kafka container tests to validate the new metrics are present - - # Message/Kafka/Topic/Consume/Named/{topic_name} - # OtherTransaction/Message/Kafka/Topic/Consume/Named/ruby-test-topic def each_with_new_relic(message) - puts '***NR*** each_with_new_relic' headers = message&.headers || {} topic_name = message&.topic + NewRelic::Agent::Messaging.wrap_message_broker_consume_transaction( library: MESSAGING_LIBRARY, destination_type: :topic, @@ -44,13 +40,26 @@ def each_with_new_relic(message) headers: headers, action: :consume ) do + create_kafka_metrics(action: CONSUME, topic: topic_name) yield end end def create_kafka_metrics(action:, topic:) - # NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}:#{port}/#{action}/#{topic}", 1) - # NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}:#{port}", 1) + hosts = [] + # both 'bootstrap.servers' and 'metadata.broker.list' are valid ways to specify the Kafka server + hosts << @nr_config[:'bootstrap.servers'] if @nr_config[:'bootstrap.servers'] + hosts << @nr_config[:'metadata.broker.list'] if @nr_config[:'metadata.broker.list'] + hosts.each do |host| + NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}/#{action}/#{topic}", 1) + NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}", 1) + end + end + end + + module RdkafkaConfig + def set_nr_config(producer_or_consumer) + producer_or_consumer.instance_variable_set(:@nr_config, self) end end end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb index d4fc8d7723..6dc8693c1f 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb @@ -29,4 +29,22 @@ def each end end end + + module RdkafkaConfig + module Prepend + include NewRelic::Agent::Instrumentation::RdkafkaConfig + + def producer(**kwargs) + super.tap do |producer| + set_nr_config(producer) + end + end + + def consumer(**kwargs) + super.tap do |consumer| + set_nr_config(consumer) + end + end + end + end end From 26a2f818e25f734fa9653b676c1d8d2674b045bd Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 11:46:15 -0500 Subject: [PATCH 03/81] add tests --- .../agent/instrumentation/rdkafka.rb | 8 +- lib/new_relic/agent/messaging.rb | 4 +- .../rdkafka/rdkafka_instrumentation_test.rb | 144 +++++++++++++++++- 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index 0da044ba69..6a5b068839 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -10,7 +10,7 @@ named :rdkafka depends_on do - defined?(::Rdkafka) + defined?(Rdkafka) end executes do @@ -18,9 +18,9 @@ puts '***NR*** Installing rdkafka instrumentation' if use_prepend? - prepend_instrument ::Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend - prepend_instrument ::Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend - prepend_instrument ::Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend + prepend_instrument Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend + prepend_instrument Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend + prepend_instrument Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend else # chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain end diff --git a/lib/new_relic/agent/messaging.rb b/lib/new_relic/agent/messaging.rb index 4e76016582..189a50f19f 100644 --- a/lib/new_relic/agent/messaging.rb +++ b/lib/new_relic/agent/messaging.rb @@ -129,9 +129,9 @@ def wrap_message_broker_consume_transaction(library:, txn_name = transaction_name(library, destination_type, destination_name, action) txn = Tracer.start_transaction(name: txn_name, category: :message) - if headers - txn.distributed_tracer.consume_message_headers(headers, state, library) + NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) + # txn.distributed_tracer.consume_message_headers(headers, state, library) CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? end diff --git a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb index 8273e16541..9ddc1ff6b9 100644 --- a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb +++ b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb @@ -8,8 +8,150 @@ def setup end def teardown + harvest_span_events! + harvest_transaction_events! NewRelic::Agent.instance.stats_engine.clear_stats + mocha_teardown end - # Add tests here + def host + 'localhost:9092' + end + + def config(host_key = 'bootstrap.servers') + config_vals ||= { + "#{host_key}": host, + "group.id": 'ruby-test', + 'auto.offset.reset': 'smallest' + } + Rdkafka::Config.new(config_vals) + end + + def test_produce_creates_span_metrics + producer = config.producer + topic = 'ruby-test-produce-topic' + in_transaction do |txn| + delivery_handles = [] + delivery_handles << producer.produce( + topic: topic, + payload: 'Payload 1', + key: 'Key 1' + ) + delivery_handles.each(&:wait) + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{topic}" + end + + def test_consume_creates_span_metrics + topic = 'ruby-test-topic' + consumer = config.consumer + consumer.stubs(:poll).returns(mock_message) + consumer.subscribe(topic) + consumer.each do |message| + # get 1 message and leave + break + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{topic}" + end + + def test_produce_with_different_host_key + producer = config('metadata.broker.list').producer + topic = 'ruby-test-produce-topic' + in_transaction do |txn| + delivery_handles = [] + delivery_handles << producer.produce( + topic: topic, + payload: 'Payload 1', + key: 'Key 1' + ) + delivery_handles.each(&:wait) + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{topic}" + end + + def test_consume_with_different_host_key + topic = 'ruby-test-topic' + consumer = config('metadata.broker.list').consumer + consumer.stubs(:poll).returns(mock_message) + consumer.subscribe(topic) + consumer.each do |message| + # get 1 message and leave + break + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{topic}" + end + + # This test takes a long time because we aren't stubbing out the consumer from talking to kafka + def test_rdkafka_distributed_tracing + NewRelic::Agent.agent.stub :connected?, true do + producer = config.producer + topic = 'ruby-test-dt-topic' + Time.now.to_i.to_s + + with_config(account_id: '190', primary_application_id: '46954', trusted_account_key: 'trust_this!') do + in_transaction('first_txn_for_dt') do |txn| + delivery_handles = [] + delivery_handles << producer.produce( + topic: topic, + payload: 'Payload 1', + key: 'Key 1' + ) + delivery_handles.each(&:wait) + end + end + producer.close + # producer.flush + first_txn = harvest_transaction_events![1] + + consumer = config.consumer + consumer.subscribe(topic) + # consumer.stubs(:poll).returns(mock_message(headers: headers)) + + puts 'Starting consumer' + consumer.each do |message| + # get 1 message and leave + break + end + txn = harvest_transaction_events![1] + + assert_metrics_recorded 'Supportability/DistributedTrace/CreatePayload/Success' + assert_equal txn[0][0]['traceId'], first_txn[0][0]['traceId'] + assert_equal txn[0][0]['parentId'], first_txn[0][0]['guid'] + end + end + + def mock_message(headers: {}) + message = mock + message.stubs(:headers).returns(headers) + message.stubs(:key).returns('Key 0') + message.stubs(:offset).returns(7106) + message.stubs(:partition).returns(0) + message.stubs(:payload).returns('Payload 0') + message.stubs(:timestamp).returns(Time.new(2024, 8, 21, 9, 52, 44, '-05:00')) + message.stubs(:topic).returns('ruby-test-topic') + message + end end From 1d9d9e7f76dca4e648da79f77c02225a8969b2a3 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:29:20 -0500 Subject: [PATCH 04/81] update tests --- .../rdkafka/rdkafka_instrumentation_test.rb | 107 ++++++++---------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb index 9ddc1ff6b9..8baa5051ba 100644 --- a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb +++ b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb @@ -4,6 +4,8 @@ class RdkafkaInstrumentationTest < Minitest::Test def setup + @topic = 'ruby-test-topic' + Time.now.to_i.to_s + Rdkafka::Config.logger = Logger.new(STDOUT, level: :error) @stats_engine = NewRelic::Agent.instance.stats_engine end @@ -14,45 +16,25 @@ def teardown mocha_teardown end - def host - 'localhost:9092' - end - - def config(host_key = 'bootstrap.servers') - config_vals ||= { - "#{host_key}": host, - "group.id": 'ruby-test', - 'auto.offset.reset': 'smallest' - } - Rdkafka::Config.new(config_vals) - end - def test_produce_creates_span_metrics - producer = config.producer - topic = 'ruby-test-produce-topic' in_transaction do |txn| - delivery_handles = [] - delivery_handles << producer.produce( - topic: topic, - payload: 'Payload 1', - key: 'Key 1' - ) - delivery_handles.each(&:wait) + produce_message end spans = harvest_span_events! span = spans[1][0] - assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{topic}", span[0]['name'] + assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{@topic}", span[0]['name'] assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" - assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{topic}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{@topic}" end def test_consume_creates_span_metrics - topic = 'ruby-test-topic' + produce_message + harvest_span_events! + consumer = config.consumer - consumer.stubs(:poll).returns(mock_message) - consumer.subscribe(topic) + consumer.subscribe(@topic) consumer.each do |message| # get 1 message and leave break @@ -61,37 +43,31 @@ def test_consume_creates_span_metrics spans = harvest_span_events! span = spans[1][0] - assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{topic}", span[0]['name'] + assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{@topic}", span[0]['name'] assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" - assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{topic}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{@topic}" end def test_produce_with_different_host_key producer = config('metadata.broker.list').producer - topic = 'ruby-test-produce-topic' in_transaction do |txn| - delivery_handles = [] - delivery_handles << producer.produce( - topic: topic, - payload: 'Payload 1', - key: 'Key 1' - ) - delivery_handles.each(&:wait) + produce_message(producer) end spans = harvest_span_events! span = spans[1][0] - assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{topic}", span[0]['name'] + assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{@topic}", span[0]['name'] assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" - assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{topic}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{@topic}" end def test_consume_with_different_host_key - topic = 'ruby-test-topic' + produce_message + harvest_span_events! + consumer = config('metadata.broker.list').consumer - consumer.stubs(:poll).returns(mock_message) - consumer.subscribe(topic) + consumer.subscribe(@topic) consumer.each do |message| # get 1 message and leave break @@ -100,37 +76,22 @@ def test_consume_with_different_host_key spans = harvest_span_events! span = spans[1][0] - assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{topic}", span[0]['name'] + assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{@topic}", span[0]['name'] assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" - assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{topic}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{@topic}" end - # This test takes a long time because we aren't stubbing out the consumer from talking to kafka def test_rdkafka_distributed_tracing NewRelic::Agent.agent.stub :connected?, true do - producer = config.producer - topic = 'ruby-test-dt-topic' + Time.now.to_i.to_s - with_config(account_id: '190', primary_application_id: '46954', trusted_account_key: 'trust_this!') do in_transaction('first_txn_for_dt') do |txn| - delivery_handles = [] - delivery_handles << producer.produce( - topic: topic, - payload: 'Payload 1', - key: 'Key 1' - ) - delivery_handles.each(&:wait) + produce_message end end - producer.close - # producer.flush first_txn = harvest_transaction_events![1] consumer = config.consumer - consumer.subscribe(topic) - # consumer.stubs(:poll).returns(mock_message(headers: headers)) - - puts 'Starting consumer' + consumer.subscribe(@topic) consumer.each do |message| # get 1 message and leave break @@ -143,6 +104,30 @@ def test_rdkafka_distributed_tracing end end + def host + 'localhost:9092' + end + + def config(host_key = 'bootstrap.servers') + config_vals ||= { + "#{host_key}": host, + "group.id": 'ruby-test', + 'auto.offset.reset': 'smallest' + } + Rdkafka::Config.new(config_vals) + end + + def produce_message(producer = config.producer) + delivery_handles = [] + delivery_handles << producer.produce( + topic: @topic, + payload: 'Payload 1', + key: 'Key 1' + ) + delivery_handles.each(&:wait) + producer.close + end + def mock_message(headers: {}) message = mock message.stubs(:headers).returns(headers) From f9be41b2bde4dad2f4efea31cbb192f50be7e023 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:35:41 -0500 Subject: [PATCH 05/81] add GHA service for kafka --- .github/workflows/ci.yml | 27 +++++++++++++++++++ .../rdkafka/rdkafka_instrumentation_test.rb | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c61b1d601..0d6c8711df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,6 +136,33 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 10 + zookeeper: + image: bitnami/zookeeper + ports: + - 2181:2181 + env: + ALLOW_ANONYMOUS_LOGIN: yes + options: >- + --health-cmd "echo mntr | nc -w 2 -q 2 localhost 2181" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + kafka: + image: bitnami/kafka + ports: + - 9092:9092 + options: >- + --health-cmd "kafka-broker-api-versions.sh --version" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 + ALLOW_PLAINTEXT_LISTENER: yes + KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 + KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092 + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CLIENT:PLAINTEXT,INTERNAL:PLAINTEXT" + KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL memcached: image: memcached:latest ports: diff --git a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb index 8baa5051ba..9f9486016f 100644 --- a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb +++ b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb @@ -105,7 +105,7 @@ def test_rdkafka_distributed_tracing end def host - 'localhost:9092' + '127.0.0.1:9092' end def config(host_key = 'bootstrap.servers') From 0cf1d24fd44ca9bff278d747bbf0db15380060be Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:42:51 -0500 Subject: [PATCH 06/81] fix spacing in ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02d5bcdc0a..20c2639814 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,7 +136,7 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 10 - zookeeper: + zookeeper: image: bitnami/zookeeper ports: - 2181:2181 From d3ee9d92d6530b32ea106c6487d21fbb5de952b6 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:44:32 -0500 Subject: [PATCH 07/81] rubocop angry --- lib/new_relic/agent/instrumentation/rdkafka.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index 6a5b068839..76cb158e88 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -21,7 +21,7 @@ prepend_instrument Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend prepend_instrument Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend prepend_instrument Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend - else + # else # chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain end end From 0996791a6e8a79a07148726ba6113499b23cf0b7 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:47:21 -0500 Subject: [PATCH 08/81] remove commented out code and only run prepend for now --- lib/new_relic/agent/messaging.rb | 1 - test/multiverse/suites/rdkafka/Envfile | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/new_relic/agent/messaging.rb b/lib/new_relic/agent/messaging.rb index 189a50f19f..c2c983827a 100644 --- a/lib/new_relic/agent/messaging.rb +++ b/lib/new_relic/agent/messaging.rb @@ -131,7 +131,6 @@ def wrap_message_broker_consume_transaction(library:, txn = Tracer.start_transaction(name: txn_name, category: :message) if headers NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) - # txn.distributed_tracer.consume_message_headers(headers, state, library) CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? end diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index 45e130823c..f6dab5ed48 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -2,7 +2,8 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true -instrumentation_methods :chain, :prepend +# instrumentation_methods :chain, :prepend +instrumentation_methods :prepend gemfile <<~RB gem 'rdkafka' From b26db501bcf595b165a1678373b997c661f31af5 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 12:53:41 -0500 Subject: [PATCH 09/81] change listener name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20c2639814..f121530806 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,7 @@ jobs: KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092 KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CLIENT:PLAINTEXT,INTERNAL:PLAINTEXT" - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT memcached: image: memcached:latest ports: From c008d955fd841eef18431d6846e47a92c660151e Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 13:07:04 -0500 Subject: [PATCH 10/81] try new env variables --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f121530806..e3d8f24804 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,10 +159,14 @@ jobs: env: KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 ALLOW_PLAINTEXT_LISTENER: yes - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092 - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CLIENT:PLAINTEXT,INTERNAL:PLAINTEXT" - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 + # KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 + # KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092 + # KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CLIENT:PLAINTEXT,INTERNAL:PLAINTEXT" + # KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL memcached: image: memcached:latest ports: From cefdb72454e0f82af3d5962db0e26cdcd75fa391 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 13:13:09 -0500 Subject: [PATCH 11/81] remove commented out env vars --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3d8f24804..6ba42e225c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,10 +163,6 @@ jobs: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 - # KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 - # KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092 - # KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CLIENT:PLAINTEXT,INTERNAL:PLAINTEXT" - # KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL memcached: image: memcached:latest ports: From 0db8f8366223bffe3ce2a4b19a42708260988970 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 13:19:13 -0500 Subject: [PATCH 12/81] still call old version when using old CAT --- lib/new_relic/agent/messaging.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/new_relic/agent/messaging.rb b/lib/new_relic/agent/messaging.rb index c2c983827a..51257f2c93 100644 --- a/lib/new_relic/agent/messaging.rb +++ b/lib/new_relic/agent/messaging.rb @@ -131,8 +131,11 @@ def wrap_message_broker_consume_transaction(library:, txn = Tracer.start_transaction(name: txn_name, category: :message) if headers NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) - CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| - txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? + if NewRelic::Agent.config[:'cross_application_tracer.enabled'] + txn.distributed_tracer.consume_message_headers(headers, state, library) + CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| + txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? + end end end From bcaa0b92f975045647c5b4623802827a7c483917 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 13:22:59 -0500 Subject: [PATCH 13/81] add kafka service to cron ci --- .github/workflows/ci_cron.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index 4dc51bcbc5..7cab9de1c1 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -149,6 +149,33 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 10 + zookeeper: + image: bitnami/zookeeper + ports: + - 2181:2181 + env: + ALLOW_ANONYMOUS_LOGIN: yes + options: >- + --health-cmd "echo mntr | nc -w 2 -q 2 localhost 2181" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + kafka: + image: bitnami/kafka + ports: + - 9092:9092 + options: >- + --health-cmd "kafka-broker-api-versions.sh --version" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 + ALLOW_PLAINTEXT_LISTENER: yes + KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 memcached: image: memcached:latest ports: From d44fc068fa781a8dc4feea710f4c9f7f3e7780eb Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 13:30:38 -0500 Subject: [PATCH 14/81] do both old one and new one --- lib/new_relic/agent/messaging.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/new_relic/agent/messaging.rb b/lib/new_relic/agent/messaging.rb index 51257f2c93..67bd3b1d9b 100644 --- a/lib/new_relic/agent/messaging.rb +++ b/lib/new_relic/agent/messaging.rb @@ -130,12 +130,10 @@ def wrap_message_broker_consume_transaction(library:, txn = Tracer.start_transaction(name: txn_name, category: :message) if headers - NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) - if NewRelic::Agent.config[:'cross_application_tracer.enabled'] - txn.distributed_tracer.consume_message_headers(headers, state, library) - CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| - txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? - end + NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(headers, library) # to handle the new w3c headers + txn.distributed_tracer.consume_message_headers(headers, state, library) # to do the expected old things + CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v| + txn.add_agent_attribute(:"message.headers.#{k}", v, AttributeFilter::DST_NONE) unless v.nil? end end From 1ea27bf33b9a2f5c092adb579aa74f21b461fa83 Mon Sep 17 00:00:00 2001 From: fallwith Date: Wed, 21 Aug 2024 12:07:37 -0700 Subject: [PATCH 15/81] update SQL obfuscation regexes - for uuids and Oracle quoted strings, don't regex-escape characters unnecessarily - for multi-line comment, borrow from OTel Ruby contrib, but discard the non-capturing parens from the pattern, as nothing is ORed. - update the hash to Ruby v1.9+ syntax --- .../agent/database/obfuscation_helpers.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/new_relic/agent/database/obfuscation_helpers.rb b/lib/new_relic/agent/database/obfuscation_helpers.rb index 12ae7fd043..ed2a68a74f 100644 --- a/lib/new_relic/agent/database/obfuscation_helpers.rb +++ b/lib/new_relic/agent/database/obfuscation_helpers.rb @@ -7,17 +7,17 @@ module Agent module Database module ObfuscationHelpers COMPONENTS_REGEX_MAP = { - :single_quotes => /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, - :double_quotes => /"(?:[^"]|"")*?(?:\\".*|"(?!"))/, - :dollar_quotes => /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/, - :uuids => /\{?(?:[0-9a-fA-F]\-*){32}\}?/, - :numeric_literals => /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, - :boolean_literals => /\b(?:true|false|null)\b/i, - :hexadecimal_literals => /0x[0-9a-fA-F]+/, - :comments => /(?:#|--).*?(?=\r|\n|$)/i, - :multi_line_comments => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/, - :oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/ - } + single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, + double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/, + dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/, + uuids: /\{?(?:[0-9a-fA-F]-*){32}\}?/, + numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, + boolean_literals: /\b(?:true|false|null)\b/i, + hexadecimal_literals: /0x[0-9a-fA-F]+/, + comments: /(?:#|--).*?(?=\r|\n|$)/i, + multi_line_comments: %r{/\*.*?\*/}m, + oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'<.*?(?:>'|$)|q'\(.*?(?:\)'|$)/ + }.freeze DIALECT_COMPONENTS = { :fallback => COMPONENTS_REGEX_MAP.keys, From d5b7cd7ef00e75fb833614a42c4a0c99583a344b Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 15:54:28 -0500 Subject: [PATCH 16/81] limit ruby versions for test --- test/multiverse/suites/rdkafka/Envfile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index f6dab5ed48..b8d47dfafd 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -5,6 +5,18 @@ # instrumentation_methods :chain, :prepend instrumentation_methods :prepend -gemfile <<~RB - gem 'rdkafka' -RB +suite_condition("Does not run on JRuby") do + RUBY_PLATFORM != 'java' +end + +VERSIONS = [ + [nil, 2.7] +] + +def gem_list(sidekiq_version = nil) + <<-RB + gem 'rdkafka'#{sidekiq_version} + RB +end + +create_gemfiles(VERSIONS) From ec1c79a7a3c5d67664afa28f558892594bc95ca9 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 21 Aug 2024 16:14:55 -0500 Subject: [PATCH 17/81] add chain instrumentation --- .../agent/instrumentation/rdkafka.rb | 7 ++- .../agent/instrumentation/rdkafka/chain.rb | 45 ++++++++++++++++--- test/multiverse/suites/rdkafka/Envfile | 3 +- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index 76cb158e88..ec8ecaa674 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -10,19 +10,18 @@ named :rdkafka depends_on do - defined?(Rdkafka) + defined?(Rdkafka) # TODO: check version bc some older oens break end executes do NewRelic::Agent.logger.info('Installing rdkafka instrumentation') - puts '***NR*** Installing rdkafka instrumentation' if use_prepend? prepend_instrument Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend prepend_instrument Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend prepend_instrument Rdkafka::Consumer, NewRelic::Agent::Instrumentation::RdkafkaConsumer::Prepend - # else - # chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain + else + chain_instrument NewRelic::Agent::Instrumentation::Rdkafka::Chain end end end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb index a9cbf3326c..f311aeb442 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb @@ -5,14 +5,49 @@ module NewRelic::Agent::Instrumentation module Rdkafka::Chain def self.instrument! - ::Rdkafka.class_eval do + ::Rdkafka::Producer.class_eval do include NewRelic::Agent::Instrumentation::Rdkafka - alias_method(:method_to_instrument_without_new_relic, :method_to_instrument) + alias_method(:produce_without_new_relic, :produce) - def method_to_instrument(*args) - method_to_instrument_with_new_relic(*args) do - method_to_instrument_without_new_relic(*args) + def produce(**kwargs) + produce_with_new_relic(kwargs) do |headers| + kwargs[:headers] = headers + produce_without_new_relic(**kwargs) + end + end + end + + ::Rdkafka::Consumer.class_eval do + include NewRelic::Agent::Instrumentation::Rdkafka + + alias_method(:each_without_new_relic, :each) + + def each(**kwargs) + each_without_new_relic(**kwargs) do |message| + each_with_new_relic(message) do + yield(message) + end + end + end + end + + ::Rdkafka::Config.class_eval do + include NewRelic::Agent::Instrumentation::RdkafkaConfig + + alias_method(:producer_without_new_relic, :producer) + + def producer(**kwargs) + producer_without_new_relic(**kwargs).tap do |producer| + set_nr_config(producer) + end + end + + alias_method(:consumer_without_new_relic, :consumer) + + def consumer(**kwargs) + consumer_without_new_relic(**kwargs).tap do |consumer| + set_nr_config(consumer) end end end diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index b8d47dfafd..02290aa50a 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -2,8 +2,7 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true -# instrumentation_methods :chain, :prepend -instrumentation_methods :prepend +instrumentation_methods :chain, :prepend suite_condition("Does not run on JRuby") do RUBY_PLATFORM != 'java' From dde87b144820469d00889ad968b08b4328789525 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 12:00:21 -0500 Subject: [PATCH 18/81] make it work with older kafka versions --- .../agent/instrumentation/rdkafka/chain.rb | 34 +++++++++++++------ .../agent/instrumentation/rdkafka/prepend.rb | 30 +++++++++++----- test/multiverse/suites/rdkafka/Envfile | 2 +- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb index f311aeb442..8b23271b0a 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb @@ -36,18 +36,32 @@ def each(**kwargs) include NewRelic::Agent::Instrumentation::RdkafkaConfig alias_method(:producer_without_new_relic, :producer) - - def producer(**kwargs) - producer_without_new_relic(**kwargs).tap do |producer| - set_nr_config(producer) - end - end - alias_method(:consumer_without_new_relic, :consumer) - def consumer(**kwargs) - consumer_without_new_relic(**kwargs).tap do |consumer| - set_nr_config(consumer) + if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0') + def producer(**kwargs) + producer_without_new_relic(**kwargs).tap do |producer| + set_nr_config(producer) + end + end + + + def consumer(**kwargs) + consumer_without_new_relic(**kwargs).tap do |consumer| + set_nr_config(consumer) + end + end + else + def producer + producer_without_new_relic.tap do |producer| + set_nr_config(producer) + end + end + + def consumer + consumer_without_new_relic.tap do |consumer| + set_nr_config(consumer) + end end end end diff --git a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb index 6dc8693c1f..4d237b7c3e 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb @@ -34,15 +34,29 @@ module RdkafkaConfig module Prepend include NewRelic::Agent::Instrumentation::RdkafkaConfig - def producer(**kwargs) - super.tap do |producer| - set_nr_config(producer) + if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0') + def producer(**kwargs) + super.tap do |producer| + set_nr_config(producer) + end end - end - - def consumer(**kwargs) - super.tap do |consumer| - set_nr_config(consumer) + + def consumer(**kwargs) + super.tap do |consumer| + set_nr_config(consumer) + end + end + else # older versions + def producer + super.tap do |producer| + set_nr_config(producer) + end + end + + def consumer + super.tap do |consumer| + set_nr_config(consumer) + end end end end diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index 02290aa50a..cdeaae66d4 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -9,7 +9,7 @@ suite_condition("Does not run on JRuby") do end VERSIONS = [ - [nil, 2.7] + [nil] ] def gem_list(sidekiq_version = nil) From ee940adaa9b09eb27ccf2dcd9af448431569964b Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 12:03:34 -0500 Subject: [PATCH 19/81] rubocop --- lib/new_relic/agent/instrumentation/rdkafka/chain.rb | 5 ++--- lib/new_relic/agent/instrumentation/rdkafka/prepend.rb | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb index 8b23271b0a..5b0617fb86 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb @@ -44,8 +44,7 @@ def producer(**kwargs) set_nr_config(producer) end end - - + def consumer(**kwargs) consumer_without_new_relic(**kwargs).tap do |consumer| set_nr_config(consumer) @@ -57,7 +56,7 @@ def producer set_nr_config(producer) end end - + def consumer consumer_without_new_relic.tap do |consumer| set_nr_config(consumer) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb index 4d237b7c3e..b9fe845e1d 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb @@ -40,7 +40,7 @@ def producer(**kwargs) set_nr_config(producer) end end - + def consumer(**kwargs) super.tap do |consumer| set_nr_config(consumer) @@ -52,7 +52,7 @@ def producer set_nr_config(producer) end end - + def consumer super.tap do |consumer| set_nr_config(consumer) From f52216b05952d945e8b00cba5320a6213e8253cf Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 12:59:24 -0500 Subject: [PATCH 20/81] try changing rubygems version --- .github/workflows/scripts/setup_bundler | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/setup_bundler b/.github/workflows/scripts/setup_bundler index 7ef619c79a..e4618b2524 100755 --- a/.github/workflows/scripts/setup_bundler +++ b/.github/workflows/scripts/setup_bundler @@ -29,8 +29,9 @@ function update_to_desired_rubygems_version { # Rubies < 2.3 need to use update_rubygems, # newer Rubies can use 'gem update --system' if [[ $RUBY_VERSION =~ ^2\.[^7] ]]; then - echo "DEBUG: running 'gem update --system 3.0.6 --force'" - gem update --system 3.0.6 --force >/dev/null + echo "DEBUG: running 'gem update --system 3.3.22 --force'" + # gem update --system 3.0.6 --force >/dev/null + gem update --system 3.3.22 --force >/dev/null else echo "DEBUG: running 'gem update --system 3.4.1 --force'" gem update --system 3.4.1 --force >/dev/null From b09604010dc763381bce294bd408b0e5745692a3 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 13:15:13 -0500 Subject: [PATCH 21/81] limit to 2.5+ --- .github/workflows/scripts/setup_bundler | 5 ++--- test/multiverse/suites/rdkafka/Envfile | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts/setup_bundler b/.github/workflows/scripts/setup_bundler index e4618b2524..7ef619c79a 100755 --- a/.github/workflows/scripts/setup_bundler +++ b/.github/workflows/scripts/setup_bundler @@ -29,9 +29,8 @@ function update_to_desired_rubygems_version { # Rubies < 2.3 need to use update_rubygems, # newer Rubies can use 'gem update --system' if [[ $RUBY_VERSION =~ ^2\.[^7] ]]; then - echo "DEBUG: running 'gem update --system 3.3.22 --force'" - # gem update --system 3.0.6 --force >/dev/null - gem update --system 3.3.22 --force >/dev/null + echo "DEBUG: running 'gem update --system 3.0.6 --force'" + gem update --system 3.0.6 --force >/dev/null else echo "DEBUG: running 'gem update --system 3.4.1 --force'" gem update --system 3.4.1 --force >/dev/null diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index cdeaae66d4..610ee35ba0 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -9,7 +9,7 @@ suite_condition("Does not run on JRuby") do end VERSIONS = [ - [nil] + [nil, 2.5] ] def gem_list(sidekiq_version = nil) From 9dab09e2decbd4968a715d73ca0ff7bdfcf94ca6 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 15:12:01 -0500 Subject: [PATCH 22/81] limit ffi version --- test/multiverse/suites/rdkafka/Envfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index 610ee35ba0..396f561374 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -15,7 +15,12 @@ VERSIONS = [ def gem_list(sidekiq_version = nil) <<-RB gem 'rdkafka'#{sidekiq_version} + #{ffi} RB end +def ffi + "gem 'ffi', '< 1.17.0'" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') +end + create_gemfiles(VERSIONS) From 81436567674a4c9c95319114a6616aa750e09ee6 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 15:43:02 -0500 Subject: [PATCH 23/81] remove unused helper method --- .../suites/rdkafka/rdkafka_instrumentation_test.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb index 9f9486016f..78d3e60313 100644 --- a/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb +++ b/test/multiverse/suites/rdkafka/rdkafka_instrumentation_test.rb @@ -127,16 +127,4 @@ def produce_message(producer = config.producer) delivery_handles.each(&:wait) producer.close end - - def mock_message(headers: {}) - message = mock - message.stubs(:headers).returns(headers) - message.stubs(:key).returns('Key 0') - message.stubs(:offset).returns(7106) - message.stubs(:partition).returns(0) - message.stubs(:payload).returns('Payload 0') - message.stubs(:timestamp).returns(Time.new(2024, 8, 21, 9, 52, 44, '-05:00')) - message.stubs(:topic).returns('ruby-test-topic') - message - end end From e80579c9a2a63b0fcdeed1e484d3d980d667ab44 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 22 Aug 2024 15:44:49 -0500 Subject: [PATCH 24/81] remove comment --- lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb index b3c4a95f16..8798839c0f 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb @@ -10,7 +10,6 @@ module Rdkafka PRODUCE = 'Produce' CONSUME = 'Consume' - # TODO: serialization ? def produce_with_new_relic(*args) topic_name = args[0][:topic] segment = NewRelic::Agent::Tracer.start_message_broker_segment( From 935e559191c0297dfa90f9bafb180d222a8e5b81 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Fri, 23 Aug 2024 11:42:24 -0500 Subject: [PATCH 25/81] add changelog entry --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5946f5fc..04f7790875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # New Relic Ruby Agent Release Notes +## dev + +Version adds instrumentation for the rdkafka gem. + +- **Feature: Add instrumentation for the rdkafka gem** + + The agent now has instrumentation for the rdkafka gem and will record message broker segments for produce and consume calls made using this gem. [PR#2824](https://github.com/newrelic/newrelic-ruby-agent/pull/2824) + + ## v9.13.0 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. From c1d8d6419eae14310e6bddde6498471849581535 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Fri, 23 Aug 2024 12:05:08 -0500 Subject: [PATCH 26/81] remove todo --- lib/new_relic/agent/instrumentation/rdkafka.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index ec8ecaa674..0ab5c32fdf 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -10,7 +10,7 @@ named :rdkafka depends_on do - defined?(Rdkafka) # TODO: check version bc some older oens break + defined?(Rdkafka) end executes do From 92030d3e5eea45144f8c13c42fe2f8d0a4e4fb08 Mon Sep 17 00:00:00 2001 From: fallwith Date: Fri, 23 Aug 2024 18:16:15 -0700 Subject: [PATCH 27/81] CI: Healthy URL checker tweaks - Update the `skip_unless_special_ci` helper to only perform the skip when in a CI context; allowing things to run in a local dev context - Don't bother performing the "ignore" regex check against a URL that has already previously been tested - In addition to ignoring 'http://#', ignore 'https://#' as well --- test/helpers/misc.rb | 5 ++++- test/new_relic/healthy_urls_test.rb | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/helpers/misc.rb b/test/helpers/misc.rb index 2f0c0b3094..16a1d14945 100644 --- a/test/helpers/misc.rb +++ b/test/helpers/misc.rb @@ -131,8 +131,11 @@ def skip_unless_ci_cron skip 'This test only runs as part of the CI cron workflow' end +# If in a CI (non local dev) context, skip unless operating within the +# special CI context. When in a local dev context or in a special CI context, +# permit the test(s) to run. def skip_unless_special_ci - return if ENV['SPECIAL_CI'] + return if ENV.fetch('CI', nil).nil? || ENV.fetch('SPECIAL_CI', nil) skip 'This test only runs as part of the special CI workflow' end diff --git a/test/new_relic/healthy_urls_test.rb b/test/new_relic/healthy_urls_test.rb index ecedc22e16..925971e0d3 100644 --- a/test/new_relic/healthy_urls_test.rb +++ b/test/new_relic/healthy_urls_test.rb @@ -65,7 +65,7 @@ class HealthyUrlsTest < Minitest::Test FILE_PATTERN = /(?:^(?:#{FILENAMES.join('|')})$)|\.(?:#{EXTENSIONS.join('|')})$/.freeze IGNORED_FILE_PATTERN = %r{/(?:coverage|test)/}.freeze URL_PATTERN = %r{(https?://.*?)[^a-zA-Z0-9/\.\-_#]}.freeze - IGNORED_URL_PATTERN = %r{(?:\{|\(|\$|169\.254|\.\.\.|learn\.|metadata\.google|honeyryderchuck\.gitlab\.io/httpx|http://#)} + IGNORED_URL_PATTERN = %r{(?:\{|\(|\$|169\.254|\.\.\.|learn\.|metadata\.google|honeyryderchuck\.gitlab\.io/httpx|https?://#)} TIMEOUT = 5 DEBUG = false @@ -103,7 +103,7 @@ def gather_urls next unless line =~ URL_PATTERN url = Regexp.last_match(1).sub(%r{(?:/|\.)$}, '') - urls[url] << file if real_url?(url) + urls[url] << file if urls.key?(url) || real_url?(url) end end end From 5ca88b7a2f46d1b94e9540543c7b72e8aaa2ccf0 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 27 Aug 2024 12:40:23 -0500 Subject: [PATCH 28/81] add instrumentation metric --- .../agent/instrumentation/rdkafka/instrumentation.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb index 8798839c0f..a91c3f9e1d 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb @@ -10,7 +10,11 @@ module Rdkafka PRODUCE = 'Produce' CONSUME = 'Consume' + INSTRUMENTATION_NAME = 'Rdkafka' + def produce_with_new_relic(*args) + NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME) + topic_name = args[0][:topic] segment = NewRelic::Agent::Tracer.start_message_broker_segment( action: :produce, @@ -29,6 +33,8 @@ def produce_with_new_relic(*args) end def each_with_new_relic(message) + NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME) + headers = message&.headers || {} topic_name = message&.topic From f91e1de57462d6d3df61066b39bc608e68034021 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 29 Aug 2024 12:04:25 -0500 Subject: [PATCH 29/81] add karafka rdkafka to envfile --- test/multiverse/suites/rdkafka/Envfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/multiverse/suites/rdkafka/Envfile b/test/multiverse/suites/rdkafka/Envfile index 396f561374..6c3d00b9eb 100644 --- a/test/multiverse/suites/rdkafka/Envfile +++ b/test/multiverse/suites/rdkafka/Envfile @@ -24,3 +24,11 @@ def ffi end create_gemfiles(VERSIONS) + +# check karafka-rdkafka compatibility as well +# but we don't need to test it on every ruby version bc it should just be the same as rdkafka +if Gem::Version.new(RUBY_VERSION) > Gem::Version.new('3.3.0') + gemfile <<~RB + gem 'karafka-rdkafka', require: 'rdkafka' + RB +end From bcc6de6e08a4de7b3099e1f0bc8a02643faadcea Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 29 Aug 2024 12:53:44 -0500 Subject: [PATCH 30/81] comment out ARN attr until it is decided how we want to handle getting the account ID from aws --- .../agent/instrumentation/dynamodb/instrumentation.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb b/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb index b2584aa22c..cf869660f4 100644 --- a/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb @@ -31,8 +31,9 @@ def instrument_method_with_new_relic(method_name, *args) collection: args[0][:table_name] ) - arn = get_arn(args[0]) - segment&.add_agent_attribute('cloud.resource_id', arn) if arn + # TODO: Update this when it has been decided how to handle account id for ARN + # arn = get_arn(args[0]) + # segment&.add_agent_attribute('cloud.resource_id', arn) if arn @nr_captured_request = nil # clear request just in case begin From 9f734fe29af82ca2e536018942794596dec02929 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Thu, 29 Aug 2024 13:03:03 -0500 Subject: [PATCH 31/81] comment out ARN test assertion --- .../suites/dynamodb/dynamodb_instrumentation_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/multiverse/suites/dynamodb/dynamodb_instrumentation_test.rb b/test/multiverse/suites/dynamodb/dynamodb_instrumentation_test.rb index 72dfbc7ae0..9da2e08885 100644 --- a/test/multiverse/suites/dynamodb/dynamodb_instrumentation_test.rb +++ b/test/multiverse/suites/dynamodb/dynamodb_instrumentation_test.rb @@ -42,7 +42,8 @@ def test_all_attributes_added_to_segment assert_equal 'us-east-2', span[2]['aws.region'] assert_equal 'query', span[2]['aws.operation'] assert_equal '1234321', span[2]['aws.requestId'] - assert_equal 'test-arn', span[2]['cloud.resource_id'] + # TODO: Uncomment this when the ARN is added to the segment + # assert_equal 'test-arn', span[2]['cloud.resource_id'] end def test_create_table_table_name_operation From 2b3949c6f2c8947460fc7ce7fd74aa7bf2f7fa25 Mon Sep 17 00:00:00 2001 From: fallwith Date: Fri, 30 Aug 2024 11:59:00 -0700 Subject: [PATCH 32/81] add test for Sidekiq's perform_inline add a test that confirms that `perform_inline` produces a segment --- .../suites/sidekiq/sidekiq_instrumentation_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb index 60d7179dd6..68e2ebd274 100644 --- a/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb +++ b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb @@ -53,4 +53,16 @@ def test_captures_sidekiq_internal_errors assert_equal 1, noticed.size assert_equal exception, noticed.first end + + # Sidekiq::Job::Setter#perform_inline is expected to light up all registered + # client and server middleware, and the lighting up of NR's server middleware + # will produce a segment + def test_works_with_perform_inline + in_transaction do |txn| + NRDeadEndJob.perform_inline + segments = txn.segments.select { |s| s.name.eql?('Nested/OtherTransaction/SidekiqJob/NRDeadEndJob/perform') } + + assert_equal 1, segments.size, "Expected to find a single Sidekiq job segment, found #{segments.size}" + end + end end From 9eea9df735f158d800f5a126ddf348eb933a9373 Mon Sep 17 00:00:00 2001 From: fallwith Date: Fri, 30 Aug 2024 12:30:55 -0700 Subject: [PATCH 33/81] Sidekiq inline test: use Sidekiq v7+ restrict the perform_inline test to Sidkiq 7+ --- .../suites/sidekiq/sidekiq_instrumentation_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb index 68e2ebd274..9d75ffbf0f 100644 --- a/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb +++ b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb @@ -58,6 +58,11 @@ def test_captures_sidekiq_internal_errors # client and server middleware, and the lighting up of NR's server middleware # will produce a segment def test_works_with_perform_inline + # Sidekiq version 6.4.2 ends up invoking String#constantize, which is only + # delivered by ActiveSupport, which this test suite doesn't currently + # include. + skip 'This test requires Sidekiq v7+' unless Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.0.0') + in_transaction do |txn| NRDeadEndJob.perform_inline segments = txn.segments.select { |s| s.name.eql?('Nested/OtherTransaction/SidekiqJob/NRDeadEndJob/perform') } From 5bdb8b9c26fe7fafd7926e2dcc762c2290049726 Mon Sep 17 00:00:00 2001 From: fallwith Date: Wed, 4 Sep 2024 09:25:52 -0700 Subject: [PATCH 34/81] CI: update to Ruby v3.3.5 Ruby v3.3.4 -> v3.3.5 --- .github/versions.yml | 4 ++-- .github/workflows/ci.yml | 18 +++++++++--------- .github/workflows/ci_cron.yml | 16 ++++++++-------- .github/workflows/ci_jruby.yml | 4 ++-- .github/workflows/ci_special.yml | 2 +- .github/workflows/config_docs.yml | 2 +- .github/workflows/performance_tests.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/release_notes.yml | 2 +- .github/workflows/release_pr.yml | 2 +- .github/workflows/slack_notifications.yml | 4 ++-- 12 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/versions.yml b/.github/versions.yml index 5b002e5028..467f91ad92 100644 --- a/.github/versions.yml +++ b/.github/versions.yml @@ -1,8 +1,8 @@ --- # This file is consumed by lib/tasks/gha.rake ruby/setup-ruby: - :tag: v1.190.0 - :sha: a6e6f86333f0a2523ece813039b8b4be04560854 + :tag: v1.191.0 + :sha: 52753b7da854d5c07df37391a986c76ab4615999 actions/checkout: :tag: v4.1.7 :sha: 692973e3d937129bcbf40652eb9f2f61becf3332 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ba42e225c..0be439be24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - name: Configure git run: 'git config --global init.defaultBranch main' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: '3.3' - run: bundle @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: [2.4.10, 3.3.4] + ruby-version: [2.4.10, 3.3.5] steps: - name: Configure git run: 'git config --global init.defaultBranch main' @@ -49,7 +49,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libsasl2-dev libxslt1-dev - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} @@ -62,7 +62,7 @@ jobs: "2.4.10": { "rails": "norails,rails42,rails52" }, - "3.3.4": { + "3.3.5": { "rails": "norails,rails61,rails72" } } @@ -217,7 +217,7 @@ jobs: fail-fast: false matrix: multiverse: [agent, ai, background, background_2, database, frameworks, httpclients, httpclients_2, rails, rest] - ruby-version: [2.4.10, 3.3.4] + ruby-version: [2.4.10, 3.3.5] steps: - name: Configure git @@ -231,7 +231,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libsasl2-dev libxslt1-dev - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} @@ -317,14 +317,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: [2.7.8, 3.3.4] + ruby-version: [2.7.8, 3.3.5] steps: - name: Configure git run: 'git config --global init.defaultBranch main' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} @@ -364,7 +364,7 @@ jobs: - name: Configure git run: 'git config --global init.defaultBranch main' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: '3.3' - run: bundle diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index 7cab9de1c1..7e783d5401 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -16,7 +16,7 @@ jobs: - name: Configure git run: 'git config --global init.defaultBranch main' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: '3.3' - run: bundle @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: [2.4.10, 2.5.9, 2.6.10, 2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.4, 3.4.0-preview1] + ruby-version: [2.4.10, 2.5.9, 2.6.10, 2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.5, 3.4.0-preview1] steps: - name: Configure git @@ -50,7 +50,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libsasl2-dev libxslt1-dev - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} @@ -81,7 +81,7 @@ jobs: "3.2.5": { "rails": "norails,rails61,rails70,rails71,rails72,railsedge" }, - "3.3.4": { + "3.3.5": { "rails": "norails,rails61,rails70,rails71,rails72,railsedge" }, "3.4.0-preview1": { @@ -230,7 +230,7 @@ jobs: fail-fast: false matrix: multiverse: [agent, ai, background, background_2, database, frameworks, httpclients, httpclients_2, rails, rest] - ruby-version: [2.4.10, 2.5.9, 2.6.10, 2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.4, 3.4.0-preview1] + ruby-version: [2.4.10, 2.5.9, 2.6.10, 2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.5, 3.4.0-preview1] steps: - name: Configure git run: 'git config --global init.defaultBranch main' @@ -243,7 +243,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libsasl2-dev libxslt1-dev - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} @@ -308,14 +308,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: [2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.4, 3.4.0-preview1] + ruby-version: [2.7.8, 3.0.7, 3.1.6, 3.2.5, 3.3.5, 3.4.0-preview1] steps: - name: Configure git run: 'git config --global init.defaultBranch main' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - name: Install Ruby ${{ matrix.ruby-version }} - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: ${{ matrix.ruby-version }} diff --git a/.github/workflows/ci_jruby.yml b/.github/workflows/ci_jruby.yml index 6c759c65a6..4f48127c64 100644 --- a/.github/workflows/ci_jruby.yml +++ b/.github/workflows/ci_jruby.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - name: Install JRuby - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: jruby-9.4.8.0 @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 - name: Install JRuby - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: jruby-9.4.8.0 diff --git a/.github/workflows/ci_special.yml b/.github/workflows/ci_special.yml index 58eb075c2c..46e3e0c9f3 100644 --- a/.github/workflows/ci_special.yml +++ b/.github/workflows/ci_special.yml @@ -21,7 +21,7 @@ jobs: run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libsasl2-dev libxslt1-dev - name: Install Ruby 3.4.0-preview1 - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.4.0-preview1 diff --git a/.github/workflows/config_docs.yml b/.github/workflows/config_docs.yml index aa50dd699f..22aee84aec 100644 --- a/.github/workflows/config_docs.yml +++ b/.github/workflows/config_docs.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: Install Ruby 3.3 - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 diff --git a/.github/workflows/performance_tests.yml b/.github/workflows/performance_tests.yml index c1872b5c45..1aceb9e60d 100644 --- a/.github/workflows/performance_tests.yml +++ b/.github/workflows/performance_tests.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 with: ref: 'main' - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: '3.3' - run: bundle diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 1d30de8b93..8ceb0aced4 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write steps: - name: Install Ruby 3.3 - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9232fcb6f1..a96f4a10c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 0 - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 diff --git a/.github/workflows/release_notes.yml b/.github/workflows/release_notes.yml index febddf5cde..ed77a6748e 100644 --- a/.github/workflows/release_notes.yml +++ b/.github/workflows/release_notes.yml @@ -13,7 +13,7 @@ jobs: contents: write pull-requests: write steps: - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 - name: Checkout code diff --git a/.github/workflows/release_pr.yml b/.github/workflows/release_pr.yml index 7aa891d6c3..ec1015ecbf 100644 --- a/.github/workflows/release_pr.yml +++ b/.github/workflows/release_pr.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Install Ruby 3.3 - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 diff --git a/.github/workflows/slack_notifications.yml b/.github/workflows/slack_notifications.yml index 2fa184bf7d..c11b1bbbab 100644 --- a/.github/workflows/slack_notifications.yml +++ b/.github/workflows/slack_notifications.yml @@ -8,7 +8,7 @@ jobs: gem_notifications: runs-on: ubuntu-22.04 steps: - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 @@ -46,7 +46,7 @@ jobs: cve_notifications: runs-on: ubuntu-22.04 steps: - - uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # tag v1.190.0 + - uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: ruby-version: 3.3 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag v4.1.7 From d19d7293f3441ed6085467e689a05199c64360a7 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Thu, 5 Sep 2024 09:22:18 -0700 Subject: [PATCH 35/81] Add rdkafka gem to Slack Notifications Now, we'll receive alerts when new versions are released. --- .github/workflows/slack_notifications.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/slack_notifications.yml b/.github/workflows/slack_notifications.yml index 2fa184bf7d..f1b963891f 100644 --- a/.github/workflows/slack_notifications.yml +++ b/.github/workflows/slack_notifications.yml @@ -32,6 +32,7 @@ jobs: rack rails rake + rdkafka redis resque roda From b9c118e1c51b6940a0199416d17c6a9c5b1501bf Mon Sep 17 00:00:00 2001 From: fallwith Date: Fri, 6 Sep 2024 13:12:14 -0700 Subject: [PATCH 36/81] Kafka instrumentation: code loading logic fix Make sure that the `Rdkafka` class based instrumentation is not attempted on systems without it present by relocating the `require_relative` statements into the conditionally executed block. --- lib/new_relic/agent/instrumentation/rdkafka.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka.rb b/lib/new_relic/agent/instrumentation/rdkafka.rb index 0ab5c32fdf..51b8dae73d 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka.rb @@ -2,10 +2,6 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true -require_relative 'rdkafka/instrumentation' -require_relative 'rdkafka/chain' -require_relative 'rdkafka/prepend' - DependencyDetection.defer do named :rdkafka @@ -16,6 +12,10 @@ executes do NewRelic::Agent.logger.info('Installing rdkafka instrumentation') + require_relative 'rdkafka/instrumentation' + require_relative 'rdkafka/chain' + require_relative 'rdkafka/prepend' + if use_prepend? prepend_instrument Rdkafka::Config, NewRelic::Agent::Instrumentation::RdkafkaConfig::Prepend prepend_instrument Rdkafka::Producer, NewRelic::Agent::Instrumentation::RdkafkaProducer::Prepend From dcc965eb6eebb6076484bd7af2d5b8a056f69164 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Mon, 9 Sep 2024 11:03:40 -0500 Subject: [PATCH 37/81] adding instrumentation --- .../agent/configuration/default_source.rb | 8 ++++++++ test/multiverse/suites/ruby_kafka/Envfile | 9 +++++++++ .../suites/ruby_kafka/config/newrelic.yml | 19 +++++++++++++++++++ .../ruby_kafka_instrumentation_test.rb | 15 +++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 test/multiverse/suites/ruby_kafka/Envfile create mode 100644 test/multiverse/suites/ruby_kafka/config/newrelic.yml create mode 100644 test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 08e028e939..ca417f8785 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1463,6 +1463,14 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :allowed_from_server => false, :description => 'Controls auto-instrumentation of bunny at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.' }, + :'instrumentation.ruby_kafka' => { + :default => 'auto', + :public => true, + :type => String, + :dynamic_name => true, + :allowed_from_server => false, + :description => 'Controls auto-instrumentation of the ruby-kafka library at start-up. May be one of `auto`, `prepend`, `chain`, `disabled`.' + }, :'instrumentation.opensearch' => { :default => 'auto', :documentation_default => 'auto', diff --git a/test/multiverse/suites/ruby_kafka/Envfile b/test/multiverse/suites/ruby_kafka/Envfile new file mode 100644 index 0000000000..c17597303e --- /dev/null +++ b/test/multiverse/suites/ruby_kafka/Envfile @@ -0,0 +1,9 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +instrumentation_methods :chain, :prepend + +gemfile <<~RB + gem 'ruby-kafka' +RB diff --git a/test/multiverse/suites/ruby_kafka/config/newrelic.yml b/test/multiverse/suites/ruby_kafka/config/newrelic.yml new file mode 100644 index 0000000000..09a10a4f2d --- /dev/null +++ b/test/multiverse/suites/ruby_kafka/config/newrelic.yml @@ -0,0 +1,19 @@ +--- +development: + error_collector: + enabled: true + apdex_t: 0.5 + monitor_mode: true + license_key: bootstrap_newrelic_admin_license_key_000 + instrumentation: + ruby_kafka: <%= $instrumentation_method %> + app_name: test + log_level: debug + host: 127.0.0.1 + api_host: 127.0.0.1 + transaction_trace: + record_sql: obfuscated + enabled: true + stack_trace_threshold: 0.5 + transaction_threshold: 1.0 + capture_params: false diff --git a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb new file mode 100644 index 0000000000..368a37f392 --- /dev/null +++ b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb @@ -0,0 +1,15 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +class RubyKafkaInstrumentationTest < Minitest::Test + def setup + @stats_engine = NewRelic::Agent.instance.stats_engine + end + + def teardown + NewRelic::Agent.instance.stats_engine.clear_stats + end + + # Add tests here +end From 70a0d1a0a972468ae86aace51c53f36baf6954d4 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Mon, 9 Sep 2024 12:12:55 -0500 Subject: [PATCH 38/81] add prepend instrumentation --- .../agent/instrumentation/ruby_kafka.rb | 31 +++++++++ .../agent/instrumentation/ruby_kafka/chain.rb | 21 ++++++ .../ruby_kafka/instrumentation.rb | 65 +++++++++++++++++++ .../instrumentation/ruby_kafka/prepend.rb | 50 ++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 lib/new_relic/agent/instrumentation/ruby_kafka.rb create mode 100644 lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb create mode 100644 lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb create mode 100644 lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka.rb b/lib/new_relic/agent/instrumentation/ruby_kafka.rb new file mode 100644 index 0000000000..9bd3cff45e --- /dev/null +++ b/lib/new_relic/agent/instrumentation/ruby_kafka.rb @@ -0,0 +1,31 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +require_relative 'ruby_kafka/instrumentation' +require_relative 'ruby_kafka/chain' +require_relative 'ruby_kafka/prepend' + +DependencyDetection.defer do + named :'ruby_kafka' + + depends_on do + # The class that needs to be defined to prepend/chain onto. This can be used + # to determine whether the library is installed. + defined?(Kafka) + # Add any additional requirements to verify whether this instrumentation + # should be installed + end + + executes do + NewRelic::Agent.logger.info('Installing ruby-kafka instrumentation') + + if use_prepend? + prepend_instrument Kafka::Producer, NewRelic::Agent::Instrumentation::RubyKafkaProducer::Prepend + prepend_instrument Kafka::Consumer, NewRelic::Agent::Instrumentation::RubyKafkaConsumer::Prepend + prepend_instrument Kafka::Client, NewRelic::Agent::Instrumentation::RubyKafkaClient::Prepend + else + # chain_instrument NewRelic::Agent::Instrumentation::RubyKafka::Chain + end + end +end diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb b/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb new file mode 100644 index 0000000000..5e437b9f77 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb @@ -0,0 +1,21 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +module NewRelic::Agent::Instrumentation + module RubyKafka::Chain + def self.instrument! + ::RubyKafka.class_eval do + include NewRelic::Agent::Instrumentation::RubyKafka + + alias_method(:method_to_instrument_without_new_relic, :method_to_instrument) + + def method_to_instrument(*args) + method_to_instrument_with_new_relic(*args) do + method_to_instrument_without_new_relic(*args) + end + end + end + end + end +end diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb new file mode 100644 index 0000000000..f48c7b2d0c --- /dev/null +++ b/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb @@ -0,0 +1,65 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +module NewRelic::Agent::Instrumentation + module RubyKafka + MESSAGING_LIBRARY = 'Kafka' + PRODUCE = 'Produce' + CONSUME = 'Consume' + + INSTRUMENTATION_NAME = 'ruby-kafka' + + def produce_with_new_relic(value, **kwargs) + NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME) + + topic_name = kwargs[:topic] + segment = NewRelic::Agent::Tracer.start_message_broker_segment( + action: :produce, + library: MESSAGING_LIBRARY, + destination_type: :topic, + destination_name: topic_name + ) + create_kafka_metrics(action: PRODUCE, topic: topic_name) + + headers = kwargs[:headers] || {} + ::NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(headers) + + NewRelic::Agent::Tracer.capture_segment_error(segment) { yield(headers) } + ensure + segment&.finish + end + + def each_message_with_new_relic(message) + NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME) + + headers = message&.headers || {} + topic_name = message&.topic + + NewRelic::Agent::Messaging.wrap_message_broker_consume_transaction( + library: MESSAGING_LIBRARY, + destination_type: :topic, + destination_name: topic_name, + headers: headers, + action: :consume + ) do + create_kafka_metrics(action: CONSUME, topic: topic_name) + yield + end + end + + def create_kafka_metrics(action:, topic:) + @nr_config.each do |seed_broker| + host = "#{seed_broker&.host}:#{seed_broker&.port}" + NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}/#{action}/#{topic}", 1) + NewRelic::Agent.record_metric("MessageBroker/Kafka/Nodes/#{host}", 1) + end + end + end + + module RubyKafkaConfig + def set_nr_config(producer_or_consumer) + producer_or_consumer.instance_variable_set(:@nr_config, @seed_brokers) + end + end +end diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb b/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb new file mode 100644 index 0000000000..66cec78a33 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb @@ -0,0 +1,50 @@ +# This file is distributed under New Relic's license terms. +# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. +# frozen_string_literal: true + +module NewRelic::Agent::Instrumentation + module RubyKafkaProducer + module Prepend + include NewRelic::Agent::Instrumentation::RubyKafka + + def produce(value, **kwargs) + produce_with_new_relic(value, **kwargs) do |headers| + kwargs[:headers] = headers + super + end + end + end + end + + module RubyKafkaConsumer + module Prepend + include NewRelic::Agent::Instrumentation::RubyKafka + + def each_message(*args) + super do |message| + each_message_with_new_relic(message) do + yield(message) + end + end + end + end + end + + module RubyKafkaClient + module Prepend + include NewRelic::Agent::Instrumentation::RubyKafkaConfig + + def producer(**kwargs) + super.tap do |producer| + set_nr_config(producer) + end + end + + def consumer(**kwargs) + super.tap do |consumer| + set_nr_config(consumer) + end + end + end + end +end From bb7f67ee904f19590831f904c25044d5cf2945fe Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Mon, 9 Sep 2024 12:24:28 -0500 Subject: [PATCH 39/81] add tests --- .../agent/instrumentation/ruby_kafka.rb | 2 +- .../ruby_kafka_instrumentation_test.rb | 77 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka.rb b/lib/new_relic/agent/instrumentation/ruby_kafka.rb index 9bd3cff45e..58025bf318 100644 --- a/lib/new_relic/agent/instrumentation/ruby_kafka.rb +++ b/lib/new_relic/agent/instrumentation/ruby_kafka.rb @@ -25,7 +25,7 @@ prepend_instrument Kafka::Consumer, NewRelic::Agent::Instrumentation::RubyKafkaConsumer::Prepend prepend_instrument Kafka::Client, NewRelic::Agent::Instrumentation::RubyKafkaClient::Prepend else - # chain_instrument NewRelic::Agent::Instrumentation::RubyKafka::Chain + chain_instrument NewRelic::Agent::Instrumentation::RubyKafka::Chain end end end diff --git a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb index 368a37f392..1e49a13370 100644 --- a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb +++ b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb @@ -4,12 +4,87 @@ class RubyKafkaInstrumentationTest < Minitest::Test def setup + @topic = 'ruby-test-topic' + Time.now.to_i.to_s @stats_engine = NewRelic::Agent.instance.stats_engine end def teardown + harvest_span_events! + harvest_transaction_events! NewRelic::Agent.instance.stats_engine.clear_stats + mocha_teardown end - # Add tests here + def test_produce_creates_span_metrics + in_transaction do |txn| + produce_message + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "MessageBroker/Kafka/Topic/Produce/Named/#{@topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Produce/#{@topic}" + end + + def test_consume_creates_span_metrics + produce_message + harvest_span_events! + + consumer = config.consumer(group_id: 'ruby-test') + consumer.subscribe(@topic) + consumer.each_message do |message| + # get 1 message and leave + break + end + + spans = harvest_span_events! + span = spans[1][0] + + assert_equal "OtherTransaction/Message/Kafka/Topic/Consume/Named/#{@topic}", span[0]['name'] + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}" + assert_metrics_recorded "MessageBroker/Kafka/Nodes/#{host}/Consume/#{@topic}" + end + + def test_rdkafka_distributed_tracing + NewRelic::Agent.agent.stub :connected?, true do + with_config(account_id: '190', primary_application_id: '46954', trusted_account_key: 'trust_this!') do + in_transaction('first_txn_for_dt') do |txn| + produce_message + end + end + first_txn = harvest_transaction_events![1] + + consumer = config.consumer(group_id: 'ruby-test') + consumer.subscribe(@topic) + consumer.each_message do |message| + # get 1 message and leave + break + end + txn = harvest_transaction_events![1] + + assert_metrics_recorded 'Supportability/DistributedTrace/CreatePayload/Success' + assert_equal txn[0][0]['traceId'], first_txn[0][0]['traceId'] + assert_equal txn[0][0]['parentId'], first_txn[0][0]['guid'] + end + end + + def host + '127.0.0.1:9092' + end + + def config + Kafka.new([host], client_id: 'ruby-test') + end + + def produce_message(producer = config.producer) + producer.produce( + 'Payload 1', + topic: @topic, + key: 'Key 1' + ) + producer.deliver_messages + producer.shutdown + end end From 61ff012336cc443b42c3625a1301688219c668d8 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Mon, 9 Sep 2024 12:34:27 -0500 Subject: [PATCH 40/81] do chain instrumentation --- .../agent/instrumentation/ruby_kafka/chain.rb | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb b/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb index 5e437b9f77..896245a709 100644 --- a/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +++ b/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb @@ -5,14 +5,48 @@ module NewRelic::Agent::Instrumentation module RubyKafka::Chain def self.instrument! - ::RubyKafka.class_eval do + ::Kafka::Producer.class_eval do include NewRelic::Agent::Instrumentation::RubyKafka - alias_method(:method_to_instrument_without_new_relic, :method_to_instrument) + alias_method(:produce_without_new_relic, :produce) - def method_to_instrument(*args) - method_to_instrument_with_new_relic(*args) do - method_to_instrument_without_new_relic(*args) + def produce(value, **kwargs) + produce_with_new_relic(value, **kwargs) do |headers| + kwargs[:headers] = headers + produce_without_new_relic(value, **kwargs) + end + end + end + + ::Kafka::Consumer.class_eval do + include NewRelic::Agent::Instrumentation::RubyKafka + + alias_method(:each_message_without_new_relic, :each_message) + + def each_message(*args) + each_message_without_new_relic(*args) do |message| + each_message_with_new_relic(message) do + yield(message) + end + end + end + end + + ::Kafka::Client.class_eval do + include NewRelic::Agent::Instrumentation::RubyKafkaConfig + + alias_method(:producer_without_new_relic, :producer) + alias_method(:consumer_without_new_relic, :consumer) + + def producer(**kwargs) + producer_without_new_relic(**kwargs).tap do |producer| + set_nr_config(producer) + end + end + + def consumer(**kwargs) + consumer_without_new_relic(**kwargs).tap do |consumer| + set_nr_config(consumer) end end end From 3097935997d56f765b40c4a03ae6379fa8d0f2f5 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 09:53:27 -0500 Subject: [PATCH 41/81] add debug logging for kafka tests --- .../suites/ruby_kafka/ruby_kafka_instrumentation_test.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb index 1e49a13370..8126be034b 100644 --- a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb +++ b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb @@ -4,8 +4,11 @@ class RubyKafkaInstrumentationTest < Minitest::Test def setup + @logger = Logger.new($stdout) @topic = 'ruby-test-topic' + Time.now.to_i.to_s @stats_engine = NewRelic::Agent.instance.stats_engine + puts ' ' + puts '-' * 100 end def teardown @@ -75,7 +78,7 @@ def host end def config - Kafka.new([host], client_id: 'ruby-test') + Kafka.new([host], client_id: 'ruby-test', logger: @logger) end def produce_message(producer = config.producer) From 0e379d193279062a9c0d8b10c56336b46fd4bc7c Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 10:11:03 -0500 Subject: [PATCH 42/81] add require --- .../agent/instrumentation/ruby_kafka/instrumentation.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb b/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb index f48c7b2d0c..389fa3cb86 100644 --- a/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb @@ -2,6 +2,8 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true +require 'new_relic/agent/messaging' + module NewRelic::Agent::Instrumentation module RubyKafka MESSAGING_LIBRARY = 'Kafka' From 6ee6722895d790449fab3da81dc102b77816c23f Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 10:30:09 -0500 Subject: [PATCH 43/81] remove debugging logger --- .../suites/ruby_kafka/ruby_kafka_instrumentation_test.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb index 8126be034b..1e49a13370 100644 --- a/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb +++ b/test/multiverse/suites/ruby_kafka/ruby_kafka_instrumentation_test.rb @@ -4,11 +4,8 @@ class RubyKafkaInstrumentationTest < Minitest::Test def setup - @logger = Logger.new($stdout) @topic = 'ruby-test-topic' + Time.now.to_i.to_s @stats_engine = NewRelic::Agent.instance.stats_engine - puts ' ' - puts '-' * 100 end def teardown @@ -78,7 +75,7 @@ def host end def config - Kafka.new([host], client_id: 'ruby-test', logger: @logger) + Kafka.new([host], client_id: 'ruby-test') end def produce_message(producer = config.producer) From 53bb5b0e7c94c2e8b5361037990e4d0c0558ebcd Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 12:40:12 -0500 Subject: [PATCH 44/81] remove comments --- lib/new_relic/agent/instrumentation/ruby_kafka.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/ruby_kafka.rb b/lib/new_relic/agent/instrumentation/ruby_kafka.rb index 58025bf318..6ab2c88654 100644 --- a/lib/new_relic/agent/instrumentation/ruby_kafka.rb +++ b/lib/new_relic/agent/instrumentation/ruby_kafka.rb @@ -10,11 +10,7 @@ named :'ruby_kafka' depends_on do - # The class that needs to be defined to prepend/chain onto. This can be used - # to determine whether the library is installed. defined?(Kafka) - # Add any additional requirements to verify whether this instrumentation - # should be installed end executes do From 7102016eebb2813a54451ced27afc3fc4825d96a Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 12:43:28 -0500 Subject: [PATCH 45/81] update changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f7790875..ad3419960f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ## dev -Version adds instrumentation for the rdkafka gem. +Version adds instrumentation for the rdkafka and ruby-kafka gems. - **Feature: Add instrumentation for the rdkafka gem** - The agent now has instrumentation for the rdkafka gem and will record message broker segments for produce and consume calls made using this gem. [PR#2824](https://github.com/newrelic/newrelic-ruby-agent/pull/2824) + The agent now has instrumentation for both the rdkafka and ruby-kafka gems. The agent will now 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) + + ## v9.13.0 From 54893420583c2d8164e8219a27758e480f6b3b56 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Tue, 10 Sep 2024 12:44:01 -0500 Subject: [PATCH 46/81] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3419960f..a7782118a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## dev -Version adds instrumentation for the rdkafka and ruby-kafka gems. +Version adds kafka instrumentation for the rdkafka and ruby-kafka gems. -- **Feature: Add instrumentation for the rdkafka gem** +- **Feature: Add kafka instrumentation for the rdkafka and ruby-kafka gems** The agent now has instrumentation for both the rdkafka and ruby-kafka gems. The agent will now 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) From 6490a22956bf950e5233c9cabacd9a57fa3ba9da Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 14:21:36 -0500 Subject: [PATCH 47/81] Update CHANGELOG.md Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7782118a8..805ac5732d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## dev -Version adds kafka instrumentation for the rdkafka and ruby-kafka gems. +Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems. - **Feature: Add kafka instrumentation for the rdkafka and ruby-kafka gems** From 47540d5a040df644f3cbc3c132c78a405f2896e3 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 14:21:43 -0500 Subject: [PATCH 48/81] Update CHANGELOG.md Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 805ac5732d..ee238ca9a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems. -- **Feature: Add kafka instrumentation for the rdkafka and ruby-kafka gems** +- **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** The agent now has instrumentation for both the rdkafka and ruby-kafka gems. The agent will now 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) From e3a4d93d934939a8776807337501b4116640cf28 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 14:21:51 -0500 Subject: [PATCH 49/81] Update CHANGELOG.md Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee238ca9a5..81070b4df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** - The agent now has instrumentation for both the rdkafka and ruby-kafka gems. The agent will now 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) + 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) From 14b23d272a5d57dc2971698a4a3e144324e5dd6b Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 14:29:06 -0500 Subject: [PATCH 50/81] add suite condition for CI to not run on new ruby versions because gem is no longer maintained --- test/multiverse/suites/ruby_kafka/Envfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/multiverse/suites/ruby_kafka/Envfile b/test/multiverse/suites/ruby_kafka/Envfile index c17597303e..f42d6a9523 100644 --- a/test/multiverse/suites/ruby_kafka/Envfile +++ b/test/multiverse/suites/ruby_kafka/Envfile @@ -2,6 +2,11 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true +suite_condition('Skip in CI on newer ruby versions') do + # will run locally OR on CI for ruby < 3.3.0 + !ENV['CI'] || RUBY_VERSION < '3.3.0' +end + instrumentation_methods :chain, :prepend gemfile <<~RB From a63dfbd2a3014d58f847232f4e518c70c3431ee7 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 16:16:49 -0500 Subject: [PATCH 51/81] fix jruby config bug --- lib/new_relic/agent/configuration/manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_relic/agent/configuration/manager.rb b/lib/new_relic/agent/configuration/manager.rb index 40249f8a8f..9d56c2c006 100644 --- a/lib/new_relic/agent/configuration/manager.rb +++ b/lib/new_relic/agent/configuration/manager.rb @@ -388,7 +388,7 @@ def reset_cache # modified. The hash really only needs to be modified for the benefit # of the security agent, so if JRuby is in play and the security agent # is not, don't attempt to modify the hash at all and return early. - return @cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled'] + return new_cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled'] @lock.synchronize do preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) } From 65dfe180de7daa6ce423668530d5b3e4fe58d029 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 16:20:15 -0500 Subject: [PATCH 52/81] add changelog entry --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f7790875..1bfa3fb77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## dev -Version adds instrumentation for the rdkafka gem. +Version adds instrumentation for the rdkafka gem and fixes a JRuby bug in the configuration manager. - **Feature: Add instrumentation for the rdkafka gem** The agent now has instrumentation for the rdkafka gem and will record message broker segments for produce and consume calls made using this gem. [PR#2824](https://github.com/newrelic/newrelic-ruby-agent/pull/2824) +- **Bugfix: Jruby not saving configuration values correctly in configuration manager** + + 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) + ## v9.13.0 From 978b3c650cd6041fec6b7444b3230c800d87b9a0 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 16:39:43 -0500 Subject: [PATCH 53/81] fix for rdkafka on load tests --- lib/new_relic/agent/instrumentation/rdkafka/chain.rb | 2 ++ lib/new_relic/agent/instrumentation/rdkafka/prepend.rb | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb index 5b0617fb86..fc9b826aa8 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/chain.rb @@ -2,6 +2,8 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true +require_relative 'instrumentation' + module NewRelic::Agent::Instrumentation module Rdkafka::Chain def self.instrument! diff --git a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb index b9fe845e1d..3397c2436c 100644 --- a/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +++ b/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb @@ -2,6 +2,8 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true +require_relative 'instrumentation' + module NewRelic::Agent::Instrumentation module RdkafkaProducer module Prepend @@ -34,7 +36,7 @@ module RdkafkaConfig module Prepend include NewRelic::Agent::Instrumentation::RdkafkaConfig - if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0') + if defined?(::Rdkafka) && Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.16.0') def producer(**kwargs) super.tap do |producer| set_nr_config(producer) From 9028e3f1591cd1d515de51c7a213b47884af9871 Mon Sep 17 00:00:00 2001 From: Tanna McClure Date: Wed, 11 Sep 2024 17:00:26 -0500 Subject: [PATCH 54/81] Update test/multiverse/suites/ruby_kafka/Envfile Co-authored-by: James Bunch --- test/multiverse/suites/ruby_kafka/Envfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/multiverse/suites/ruby_kafka/Envfile b/test/multiverse/suites/ruby_kafka/Envfile index f42d6a9523..c8fa620dfa 100644 --- a/test/multiverse/suites/ruby_kafka/Envfile +++ b/test/multiverse/suites/ruby_kafka/Envfile @@ -3,8 +3,8 @@ # frozen_string_literal: true suite_condition('Skip in CI on newer ruby versions') do - # will run locally OR on CI for ruby < 3.3.0 - !ENV['CI'] || RUBY_VERSION < '3.3.0' + # will run locally OR on CI for ruby < 3.4.0 + !ENV['CI'] || RUBY_VERSION < '3.4.0' end instrumentation_methods :chain, :prepend From a514d91b189a99749f9107a746366bd0c29079f3 Mon Sep 17 00:00:00 2001 From: fallwith Date: Thu, 12 Sep 2024 12:10:31 -0700 Subject: [PATCH 55/81] unit tests: scope to files, favor the shortest I invoked `bert agent`, with a desire to run the tests located at `test/new_relic/agent_test.rb`. Instead the tests located at `test/new_relic/agent/rpm_agent_test.rb` were ran instead. This is because we were previously simply grabbing the very first `find` command. Now, all results obtained by the `find` command will be considered, and the one with the shortest file path will be chosen. In future we may want to detect the presence of `fzf` and prompt the human tester when multiple results are found, but for now this seems to yield pretty desirable behavior while still permitting the human to add more characters to the input string if they wish to disambiguate between multiple matches themselves. Also, the `find` command was updated to only focus on files and not directories. --- test/script/run_tests | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/test/script/run_tests b/test/script/run_tests index 9d9ad6aaaf..45e8e437a9 100755 --- a/test/script/run_tests +++ b/test/script/run_tests @@ -144,15 +144,48 @@ run_unit_tests() { fi } +# from a given space/newline delimited string of file paths, return the +# shortest path +# +# so if "bert agent" is invoked and all of these files match 'agent": +# +# new_relic/agent/rpm_agent_test.rb +# new_relic/agent/agent_test.rb +# new_relic/agent/distributed_tracing/trace_context_cross_agent_test.rb +# new_relic/agent/distributed_tracing/distributed_tracing_cross_agent_test.rb +# new_relic/agent/commands/agent_command_router_test.rb +# new_relic/agent/commands/agent_command_test.rb +# new_relic/agent/threading/agent_thread_test.rb +# new_relic/agent/agent_logger_test.rb +# new_relic/agent_test.rb +# new_relic/rack/agent_hooks_test.rb +# new_relic/rack/agent_middleware_test.rb +# +# then favor new_relic/agent_test.rb +shortest_test_file() { + files="$1" + shortest=1138000 + desired="" + for file in $files; do + len=${#file} + if (( "$len" < "$shortest" )); then + shortest=$len + desired=$file + fi + done + printf $desired +} + # If the first argument doesn't contain a slash or start with test_ # then assume it is a partial filename match. Find the file. find_test_file() { if [[ "$1" != */* && "$1" != test* ]]; then - file=$(find "test/$2" -name "*$1*" -print -quit) - if [[ "$file" == "" ]]; then + files=$(find "test/$2" -type f -name "*$1*" -print) + if [[ "$files" == "" ]]; then echo "Could not find a file match for '$1'" exit else + file=$(shortest_test_file "$files") echo "Testing against file '$file'..." TEST="$file" fi From de9aa5877c3ddd0b693c3d2e16a3a1a02bf9a8e7 Mon Sep 17 00:00:00 2001 From: fallwith Date: Fri, 13 Sep 2024 18:53:38 -0700 Subject: [PATCH 56/81] Apply method tracers automatically via config A new `:automatic_custom_instrumentation_method_list` configuration parameter has been added to permit the user to define a list of namespaced method name strings representing methods that the agent should automatically add custom instrumentation tracers to. resolves #2701 --- lib/new_relic/agent.rb | 87 ++++++++++++++ .../agent/configuration/default_source.rb | 46 +++++++ .../agent/configuration/environment_source.rb | 6 +- .../configuration/default_source_test.rb | 22 ++++ .../configuration/environment_source_test.rb | 12 ++ test/new_relic/agent_test.rb | 113 ++++++++++++++++++ 6 files changed, 285 insertions(+), 1 deletion(-) diff --git a/lib/new_relic/agent.rb b/lib/new_relic/agent.rb index 6865341fe3..910733f97a 100644 --- a/lib/new_relic/agent.rb +++ b/lib/new_relic/agent.rb @@ -85,6 +85,10 @@ class LicenseException < StandardError; end # An exception that forces an agent to stop reporting until its mongrel is restarted. class ForceDisconnectException < StandardError; end + # Error handling for the automated custom instrumentation tracer logic + class AutomaticTracerParseException < StandardError; end + class AutomaticTracerTraceException < StandardError; end + # An exception that forces an agent to restart. class ForceRestartException < StandardError def message @@ -109,6 +113,9 @@ class SerializationError < StandardError; end # placeholder name used when we cannot determine a transaction's name UNKNOWN_METRIC = '(unknown)'.freeze LLM_FEEDBACK_MESSAGE = 'LlmFeedbackMessage' + # give the observed app time to load the code that automatic tracers have + # been configured for + AUTOMATIC_TRACER_MAX_ATTEMPTS = 60 # 60 = try about twice a second for 30 seconds attr_reader :error_group_callback attr_reader :llm_token_count_callback @@ -163,6 +170,86 @@ def add_or_defer_method_tracer(receiver, method_name, metric_name, options) end end + def self.add_automatic_method_tracers(arr) + return unless arr + return arr if arr.respond_to?(:empty?) && arr.empty? + + arr = arr.split(/\s*,\s*/) if arr.is_a?(String) + + add_tracers_once_methods_are_defined(arr.dup) + + arr + end + + # spawn a thread that will attempt to establish a tracer for each of the + # configured methods. the thread will continue to keep trying with each + # tracer until one of the following happens: + # - the tracer is successfully established + # - the configured method string couldn't be parsed + # - establishing a tracer for a successfully parsed string failed + # - the maximum number of attempts has been reached + # the thread will only be spawned once per agent initialization, to account + # for configuration reloading scenarios. + def self.add_tracers_once_methods_are_defined(notations) + # this class method can be invoked multiple times at agent startup, so + # we return asap here instead of using a traditional memoization of + # waiting for the method's body to finish being executed + if defined?(@add_tracers_once_methods_are_defined) + return + else + @add_tracers_once_methods_are_defined = true + end + + Thread.new do + AUTOMATIC_TRACER_MAX_ATTEMPTS.times do + notations.delete_if { |notation| prep_tracer_for(notation) } + + break if notations.empty? + + sleep 0.5 + end + end + end + + # returns `true` if the notation string has either been successfully + # processed or raised an error during processing. returns `false` if the + # string seems good but the (customer) code to be traced has not yet been + # loaded into the Ruby VM + def self.prep_tracer_for(fully_qualified_method_notation) + delimiters = fully_qualified_method_notation.scan(/\.|#/) + raise AutomaticTracerParseException.new("Expected exactly one '.' or '#' delimiter.") unless delimiters.size == 1 + + delimiter = delimiters.first + namespace, method_name = fully_qualified_method_notation.split(delimiter) + unless namespace && !namespace.empty? + raise AutomaticTracerParseException.new("Nothing found to the left of the #{delimiter} delimiter.") + end + unless method_name && !method_name.empty? + raise AutomaticTracerParseException.new("Nothing found to the right of the #{delimiter} delimiter.") + end + + begin + klass = ::NewRelic::LanguageSupport.constantize(namespace) + return false unless klass + + klass_to_trace = delimiter.eql?('.') ? klass.singleton_class : klass + add_or_defer_method_tracer(klass_to_trace, method_name, nil, {}) + rescue StandardError => e + raise AutomaticTracerTraceException.new("#{e.class} - #{e.message}") + end + + true + rescue AutomaticTracerParseException => e + NewRelic::Agent.logger.error('Unable to parse out a usable method name to trace. Expected a valid, fully ' \ + "qualified method notation. Got: '#{fully_qualified_method_notation}'. " \ + "Error: #{e.message}") + true + rescue AutomaticTracerTraceException => e + NewRelic::Agent.logger.error('Unable to automatically apply a tracer to method ' \ + "'#{fully_qualified_method_notation}'. Error: #{e.message}") + true + end + def add_deferred_method_tracers_now @tracer_lock.synchronize do @tracer_queue.each do |receiver, method_name, metric_name, options| diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 08e028e939..050d5107c1 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1137,6 +1137,52 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :allowed_from_server => false, :description => 'If `false`, custom attributes will not be sent on events.' }, + :automatic_custom_instrumentation_method_list => { + :default => NewRelic::EMPTY_ARRAY, + :public => true, + :type => Array, + :allowed_from_server => false, + :transform => proc { |arr| NewRelic::Agent.add_automatic_method_tracers(arr) }, + :description => <<~DESCRIPTION + An array of `CLASS#METHOD` (for instance methods) and/or `CLASS.METHOD` (for class methods) strings representing Ruby methods for the agent to add custom instrumentation to automatically without the need for altering any of the source code that defines the methods. + + Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing. + + Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and and a `notify` class method for a `User` class, both within a `MyCompany` module namespace: + + ``` + module MyCompany + class Image + def render_png + # code to render a PNG + end + end + + class User + def self.notify + # code to notify users + end + end + end + ``` + + Given that source code, the `newrelic.yml` config file might request instrumentation for both of these methods like so: + + ``` + automatic_custom_instrumentation_method_list: + - MyCompany::Image#render_png + - MyCompany::User.notify + ``` + + That configuration example uses YAML array syntax to specify both methods. Alternatively, a comma-delimited string can be used instead: + + ``` + automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify' + ``` + + 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. + DESCRIPTION + }, # Custom events :'custom_insights_events.enabled' => { :default => true, diff --git a/lib/new_relic/agent/configuration/environment_source.rb b/lib/new_relic/agent/configuration/environment_source.rb index e8c24079d3..fe6cb791a1 100644 --- a/lib/new_relic/agent/configuration/environment_source.rb +++ b/lib/new_relic/agent/configuration/environment_source.rb @@ -92,7 +92,11 @@ def set_key_by_type(config_key, environment_key) elsif type == Symbol self[config_key] = value.to_sym elsif type == Array - self[config_key] = value.split(/\s*,\s*/) + self[config_key] = if DEFAULTS[config_key].key?(:transform) + DEFAULTS[config_key][:transform].call(value) + else + value.split(/\s*,\s*/) + end elsif type == NewRelic::Agent::Configuration::Boolean if /false|off|no/i.match?(value) self[config_key] = false diff --git a/test/new_relic/agent/configuration/default_source_test.rb b/test/new_relic/agent/configuration/default_source_test.rb index 43a2b50ebd..5dc8e1a1a8 100644 --- a/test/new_relic/agent/configuration/default_source_test.rb +++ b/test/new_relic/agent/configuration/default_source_test.rb @@ -291,6 +291,28 @@ def test_allowlist_blocks_invalid_values_and_uses_a_default end end + def test_automatic_custom_instrumentation_method_list_supports_an_array + key = :automatic_custom_instrumentation_method_list + list = %w[Beano::Roger#dodge Beano::Gnasher.gnash] + NewRelic::Agent.stub :add_tracers_once_methods_are_defined, nil do + with_config(key => list) do + assert_equal list, NewRelic::Agent.config[key], + "Expected '#{key}' to be configured with the unmodified original list" + end + end + end + + def test_automatic_custom_instrumentation_method_list_supports_a_comma_delmited_string + key = :automatic_custom_instrumentation_method_list + list = %w[Beano::Roger#dodge Beano::Gnasher.gnash] + NewRelic::Agent.stub :add_tracers_once_methods_are_defined, nil do + with_config(key => list.join(' ,')) do + assert_equal list, NewRelic::Agent.config[key], + "Expected '#{key}' to be configured with the given string converted into an array" + end + end + end + def get_config_value_class(value) type = value.class diff --git a/test/new_relic/agent/configuration/environment_source_test.rb b/test/new_relic/agent/configuration/environment_source_test.rb index 004e9250b0..1286a20a50 100644 --- a/test/new_relic/agent/configuration/environment_source_test.rb +++ b/test/new_relic/agent/configuration/environment_source_test.rb @@ -160,6 +160,18 @@ def test_set_key_by_type_converts_comma_lists_with_spaces_to_array assert_equal %w[hi bye], @environment_source[:'attributes.include'] end + def test_array_based_params_use_the_transform_proc_when_present + arr = %w[James Jessie Meowth] + + env_var = 'NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST' + param = :automatic_custom_instrumentation_method_list + ENV.stub(:[], arr.join(','), [env_var]) do + @environment_source.set_key_by_type(param, env_var) + end + + assert_equal arr, @environment_source[param] + end + def test_set_key_with_new_relic_prefix assert_applied_string('NEW_RELIC_LICENSE_KEY', :license_key) end diff --git a/test/new_relic/agent_test.rb b/test/new_relic/agent_test.rb index e83c8df5f4..71c0c1cd5a 100644 --- a/test/new_relic/agent_test.rb +++ b/test/new_relic/agent_test.rb @@ -12,6 +12,8 @@ module NewRelic class MainAgentTest < Minitest::Test include NewRelic::Agent::MethodTracer + class TesterClass; end + def setup NewRelic::Agent.drop_buffered_data NewRelic::Agent.reset_config @@ -698,6 +700,109 @@ def test_base_name_without_module_namespace assert_equal name, NewRelic::Agent.base_name(name) end + def test_add_automatic_method_tracers_short_circuits_with_a_nil_method_list + assert_nil NewRelic::Agent.add_automatic_method_tracers(nil) + end + + def test_add_automatic_method_tracers_returns_early_when_given_an_empty_method_list + arr = [] + + assert_equal arr, NewRelic::Agent.add_automatic_method_tracers(arr) + end + + # as of 2024-09-12, the agent doesn't natively permit a configuration + # parameter to accept either an array (definable only in the YAML file) or + # a comma-delimited string (definable in YAML or via an env var), so this + # method permits it by handling the `String#split` call itself. + def test_add_automatic_method_tracers_handles_a_comma_delimited_string + arr = %w[Astarion Gale Shadowheart] + # don't actually spawn the thread to add custom instrumentation + NewRelic::Agent.stub :add_tracers_once_methods_are_defined, nil do + assert_equal arr, NewRelic::Agent.add_automatic_method_tracers(arr.join(',')) + end + end + + # treat the configured list as immutable and operate on a dupe of the list + # when iterating over it and removing entries that have been processed + def test_add_automatic_method_tracers_processes_a_dupe_of_the_methods_array + arr = %w[Andy Barney] + dupe_object_id = nil + NewRelic::Agent.stub :add_tracers_once_methods_are_defined, proc { |a| dupe_object_id = a.object_id } do + result = NewRelic::Agent.add_automatic_method_tracers(arr) + + assert_same arr, result + assert dupe_object_id + refute_equal arr.object_id, dupe_object_id + end + end + + def test_prep_tracer_for_handles_a_non_delimited_notation + notation = 'StringWithoutADelimiter' + result = with_logger_expectation(:error, /Unable to parse out .*#{notation}.* Expected exactly/) do + NewRelic::Agent.prep_tracer_for(notation) + end + + assert result # `true`, we're not going to try processing this notation again + end + + def test_prep_tracer_for_handles_a_missing_namespace + notation = '.method_name' + result = with_logger_expectation(:error, /Unable to parse out .*#{notation}.* to the left of/) do + NewRelic::Agent.prep_tracer_for(notation) + end + + assert result # `true`, we're not going to try processing this notation again + end + + def test_prep_tracer_for_handles_a_missing_method_name + notation = 'namespace#' + result = with_logger_expectation(:error, /Unable to parse out .*#{notation}.* to the right of/) do + NewRelic::Agent.prep_tracer_for(notation) + end + + assert result # `true`, we're not going to try processing this notation again + end + + def test_prep_tracer_for_returns_false_if_the_notation_is_find_but_the_method_cannot_be_found_yet + notation = 'AGoodModuleNameSpace::AClass#a_method' # good syntax but undefined + result = NewRelic::Agent.prep_tracer_for(notation) + + refute result # `false`, we're going to try processing this notation again later + end + + def test_prep_tracer_handles_a_failed_tracer_add_attempt + notation = "Monk's.Blend" + NewRelic::LanguageSupport.stub :constantize, -> { raise 'kaboom' }, [notation] do + result = with_logger_expectation(:error, /Unable to automatically apply .*#{notation}/) do + NewRelic::Agent.prep_tracer_for(notation) + end + + assert result # `true`, we're not going to try processing this notation again + end + end + + def test_prep_tracer_for_traces_a_class_method + notation = 'A::Short.hike' + NewRelic::LanguageSupport.stub :constantize, TesterClass, [notation] do + NewRelic::Agent.stub :add_or_defer_method_tracer, nil, [TesterClass.singleton_class, 'hike', nil, {}] do + result = NewRelic::Agent.prep_tracer_for(notation) + + assert result # `true`, we're not going to try processing this notation again + end + end + end + + def test_prep_tracer_for_traces_an_instance_method + notation = 'A::Short#hike' + NewRelic::LanguageSupport.stub :constantize, TesterClass, [notation] do + NewRelic::Agent.stub :add_or_defer_method_tracer, nil, [TesterClass, 'hike', nil, {}] do + result = NewRelic::Agent.prep_tracer_for(notation) + + assert result # `true`, we're not going to try processing this notation again + end + end + end + private def with_unstarted_agent @@ -731,5 +836,13 @@ def fetch(k, d) NewRelic::Control.stubs(:instance).returns(control) control end + + def with_logger_expectation(log_level, expected_regex, &block) + logger = MiniTest::Mock.new + logger.expect log_level, nil, [expected_regex] + result = NewRelic::Agent.stub(:logger, logger) { yield } + logger.verify + result + end end end From e829bad5fe8d1b1e1c175de6c418ac6a2885c88c Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 16 Sep 2024 11:07:26 -0700 Subject: [PATCH 57/81] unit test fixes for automatic method tracers unit test updates for #2851 --- .../agent/configuration/environment_source_test.rb | 2 ++ .../configuration/orphan_configuration_test.rb | 13 ++++++++++++- test/new_relic/agent_test.rb | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/new_relic/agent/configuration/environment_source_test.rb b/test/new_relic/agent/configuration/environment_source_test.rb index 1286a20a50..056d889995 100644 --- a/test/new_relic/agent/configuration/environment_source_test.rb +++ b/test/new_relic/agent/configuration/environment_source_test.rb @@ -161,6 +161,8 @@ def test_set_key_by_type_converts_comma_lists_with_spaces_to_array end def test_array_based_params_use_the_transform_proc_when_present + skip_unless_minitest5_or_above + arr = %w[James Jessie Meowth] env_var = 'NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST' diff --git a/test/new_relic/agent/configuration/orphan_configuration_test.rb b/test/new_relic/agent/configuration/orphan_configuration_test.rb index f30041194b..d85827235d 100644 --- a/test/new_relic/agent/configuration/orphan_configuration_test.rb +++ b/test/new_relic/agent/configuration/orphan_configuration_test.rb @@ -8,6 +8,9 @@ class OrphanedConfigTest < Minitest::Test include NewRelic::TestHelpers::FileSearching include NewRelic::TestHelpers::ConfigScanning + # :automatic_custom_instrumentation_method_list - the tranform proc handles all processing, no other reference exists + IGNORED_KEYS = %i[automatic_custom_instrumentation_method_list] + def setup @default_keys = ::NewRelic::Agent::Configuration::DEFAULTS.keys end @@ -38,8 +41,16 @@ def test_all_default_source_config_keys_are_used_in_the_agent # This indicates that these keys are referenced and implemented in # an external gem, so we don't expect any explicit references to them # in the core gem's code. + # + + # Remove any of the following types of keys + # - "external" keys: these are expected to only be leveraged by "external" code bases (Infinite Tracing, CSEC) + # - "deprecated" keys: these are supported for a time and have their values set on new param names used in code + # - "ignored" keys: special cased params defined by a constant above @default_keys.delete_if do |key_name| - NewRelic::Agent::Configuration::DEFAULTS[key_name][:external] || NewRelic::Agent::Configuration::DEFAULTS[key_name][:deprecated] + NewRelic::Agent::Configuration::DEFAULTS[key_name][:external] || + NewRelic::Agent::Configuration::DEFAULTS[key_name][:deprecated] || + IGNORED_KEYS.include?(key_name) end assert_empty @default_keys diff --git a/test/new_relic/agent_test.rb b/test/new_relic/agent_test.rb index 71c0c1cd5a..3bf5281dfa 100644 --- a/test/new_relic/agent_test.rb +++ b/test/new_relic/agent_test.rb @@ -771,6 +771,8 @@ def test_prep_tracer_for_returns_false_if_the_notation_is_find_but_the_method_ca end def test_prep_tracer_handles_a_failed_tracer_add_attempt + skip_unless_minitest5_or_above + notation = "Monk's.Blend" NewRelic::LanguageSupport.stub :constantize, -> { raise 'kaboom' }, [notation] do result = with_logger_expectation(:error, /Unable to automatically apply .*#{notation}/) do @@ -782,6 +784,8 @@ def test_prep_tracer_handles_a_failed_tracer_add_attempt end def test_prep_tracer_for_traces_a_class_method + skip_unless_minitest5_or_above + notation = 'A::Short.hike' NewRelic::LanguageSupport.stub :constantize, TesterClass, [notation] do NewRelic::Agent.stub :add_or_defer_method_tracer, nil, [TesterClass.singleton_class, 'hike', nil, {}] do @@ -793,6 +797,8 @@ def test_prep_tracer_for_traces_a_class_method end def test_prep_tracer_for_traces_an_instance_method + skip_unless_minitest5_or_above + notation = 'A::Short#hike' NewRelic::LanguageSupport.stub :constantize, TesterClass, [notation] do NewRelic::Agent.stub :add_or_defer_method_tracer, nil, [TesterClass, 'hike', nil, {}] do From 29626907bf8bfcea78ebf9b6440d2003ec72c33e Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 16 Sep 2024 11:32:04 -0700 Subject: [PATCH 58/81] CHANGELOG entry for automatic custom instrumentation CHANGELOG entry for #2851 --- CHANGELOG.md | 49 ++++++++++++++++++- .../agent/configuration/default_source.rb | 6 ++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 439c6ff2a1..5cad3d4232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,59 @@ ## dev -Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems and fixes a JRuby bug in the configuration manager. +Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration based automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** 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) +- **Feature: Add a configuration option to permit custom method tracers to be defined automatically** + + 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. + + The list should be an array of `CLASS#METHOD` (for instance methods) and/or `CLASS.METHOD` (for class methods) strings. + + Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing. + + Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and and a `notify` class method for a `User` class, both within a `MyCompany` module namespace: + + ``` + module MyCompany + class Image + def render_png + # code to render a PNG + end + end + class User + def self.notify + # code to notify users + end + end + end + ``` + + Given that source code, the `newrelic.yml` config file might request instrumentation for both of these methods like so: + + ``` + automatic_custom_instrumentation_method_list: + - MyCompany::Image#render_png + - MyCompany::User.notify + ``` + + That configuration example uses YAML array syntax to specify both methods. Alternatively, a comma-delimited string can be used instead: + + ``` + automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify' + ``` + + 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: + + ``` + export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify' + ``` + +[PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851) + - **Bugfix: Jruby not saving configuration values correctly in configuration manager** 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) diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 8dffd5fe98..5deab1ddc5 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1180,7 +1180,11 @@ def self.notify automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify' ``` - 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. + 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: + + ``` + export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify' + ``` DESCRIPTION }, # Custom events From e1ec6379a1ee113f4b5bc35f7a363a0380e06cd6 Mon Sep 17 00:00:00 2001 From: James Bunch Date: Mon, 16 Sep 2024 16:48:09 -0700 Subject: [PATCH 59/81] Update CHANGELOG.md grammar update for PR 2851 CHANGELOG entry Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cad3d4232..f0297cc731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## dev -Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration based automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager. +Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** From 6425875305911ff1a17ed27b282b813cbef0ac8a Mon Sep 17 00:00:00 2001 From: James Bunch Date: Mon, 16 Sep 2024 16:48:26 -0700 Subject: [PATCH 60/81] Update CHANGELOG.md grammar update for PR 2851 CHANGELOG entry Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0297cc731..ce96d00edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing. - Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and and a `notify` class method for a `User` class, both within a `MyCompany` module namespace: + 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: ``` module MyCompany From 3b84ee05d64dd7c0b2897229a2e090137d57f528 Mon Sep 17 00:00:00 2001 From: James Bunch Date: Mon, 16 Sep 2024 16:49:12 -0700 Subject: [PATCH 61/81] Update lib/new_relic/agent/configuration/default_source.rb "and and" -> "and" typo fix Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- lib/new_relic/agent/configuration/default_source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 5deab1ddc5..c61536ef8c 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1148,7 +1148,7 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing. - Here is some Ruby source code that defines a `render_png` instance method for an `Image` class and and a `notify` class method for a `User` class, both within a `MyCompany` module namespace: + 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: ``` module MyCompany From 3925b29772c1e1f2642f8b582a912d532b72033e Mon Sep 17 00:00:00 2001 From: James Bunch Date: Mon, 16 Sep 2024 16:50:15 -0700 Subject: [PATCH 62/81] Update lib/new_relic/agent/configuration/default_source.rb :automatic_custom_instrumentation_method_list description update for PR 2851 Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- lib/new_relic/agent/configuration/default_source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index c61536ef8c..0dfd2a07d1 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1144,7 +1144,7 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :allowed_from_server => false, :transform => proc { |arr| NewRelic::Agent.add_automatic_method_tracers(arr) }, :description => <<~DESCRIPTION - An array of `CLASS#METHOD` (for instance methods) and/or `CLASS.METHOD` (for class methods) strings representing Ruby methods for the agent to add custom instrumentation to automatically without the need for altering any of the source code that defines the methods. + 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. Use fully qualified class names (using the `::` delimiter) that include any module or class namespacing. From 27023470c73c9082c38c574e9963bb5152f8000d Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 16 Sep 2024 16:53:43 -0700 Subject: [PATCH 63/81] indentation fix for code block for PR 2851 fix the Ruby code block indentation --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce96d00edc..4a715ff53f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,16 +21,17 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g ``` module MyCompany - class Image + class Image def render_png - # code to render a PNG + # code to render a PNG end - end - class User + end + + class User def self.notify - # code to notify users - end + # code to notify users end + end end ``` From 87589d7fe08f8c00c2e1a357b7d3e8a843d1caf6 Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 16 Sep 2024 16:54:08 -0700 Subject: [PATCH 64/81] mark new agent class methods as private for the agent class methods delivered by PR 2851, mark them as `@api private` to avoid any potential developer confusion --- lib/new_relic/agent.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/new_relic/agent.rb b/lib/new_relic/agent.rb index 910733f97a..888ce963ba 100644 --- a/lib/new_relic/agent.rb +++ b/lib/new_relic/agent.rb @@ -170,6 +170,7 @@ def add_or_defer_method_tracer(receiver, method_name, metric_name, options) end end + # @api private def self.add_automatic_method_tracers(arr) return unless arr return arr if arr.respond_to?(:empty?) && arr.empty? @@ -190,6 +191,8 @@ def self.add_automatic_method_tracers(arr) # - the maximum number of attempts has been reached # the thread will only be spawned once per agent initialization, to account # for configuration reloading scenarios. + # + # @api private def self.add_tracers_once_methods_are_defined(notations) # this class method can be invoked multiple times at agent startup, so # we return asap here instead of using a traditional memoization of @@ -215,6 +218,8 @@ def self.add_tracers_once_methods_are_defined(notations) # processed or raised an error during processing. returns `false` if the # string seems good but the (customer) code to be traced has not yet been # loaded into the Ruby VM + # + # @api private def self.prep_tracer_for(fully_qualified_method_notation) delimiters = fully_qualified_method_notation.scan(/\.|#/) raise AutomaticTracerParseException.new("Expected exactly one '.' or '#' delimiter.") unless delimiters.size == 1 @@ -250,6 +255,7 @@ def self.prep_tracer_for(fully_qualified_method_notation) true end + # @api private def add_deferred_method_tracers_now @tracer_lock.synchronize do @tracer_queue.each do |receiver, method_name, metric_name, options| From 701c5fcf6ca0492e76daa30f09b1247019eddfe6 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 18 Sep 2024 13:09:32 -0700 Subject: [PATCH 65/81] Update min bundler version for installed_specs? The Bundler::RubygemsIntegration.installed_specs method was added in version 2.5.12. --- lib/new_relic/agent/instrumentation/grape.rb | 2 +- lib/new_relic/control/frameworks/rails4.rb | 2 +- lib/new_relic/environment_report.rb | 2 +- lib/new_relic/language_support.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/new_relic/agent/instrumentation/grape.rb b/lib/new_relic/agent/instrumentation/grape.rb index 0167794b3c..493193516f 100644 --- a/lib/new_relic/agent/instrumentation/grape.rb +++ b/lib/new_relic/agent/instrumentation/grape.rb @@ -20,7 +20,7 @@ depends_on do begin if defined?(Bundler) && - ((Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0') && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) || + ((Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) || Bundler.rubygems.all_specs.map(&:name).include?('newrelic-grape')) NewRelic::Agent.logger.info('Not installing New Relic supported Grape instrumentation because the third party newrelic-grape gem is present') false diff --git a/lib/new_relic/control/frameworks/rails4.rb b/lib/new_relic/control/frameworks/rails4.rb index 3b13d588c1..14f225c4fc 100644 --- a/lib/new_relic/control/frameworks/rails4.rb +++ b/lib/new_relic/control/frameworks/rails4.rb @@ -9,7 +9,7 @@ class Control module Frameworks class Rails4 < NewRelic::Control::Frameworks::Rails3 def rails_gem_list - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0') + if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') Bundler.rubygems.installed_specs.map { |gem| "#{gem.name} (#{gem.version})" } else Bundler.rubygems.all_specs.map { |gem| "#{gem.name} (#{gem.version})" } diff --git a/lib/new_relic/environment_report.rb b/lib/new_relic/environment_report.rb index 7d2b487fc0..e45beeabc0 100644 --- a/lib/new_relic/environment_report.rb +++ b/lib/new_relic/environment_report.rb @@ -44,7 +44,7 @@ def self.registered_reporters=(logic) #################################### report_on('Gems') do begin - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0') + if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') Bundler.rubygems.installed_specs.map { |gem| "#{gem.name}(#{gem.version})" } else Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" } diff --git a/lib/new_relic/language_support.rb b/lib/new_relic/language_support.rb index 438d899aa4..563e0f800b 100644 --- a/lib/new_relic/language_support.rb +++ b/lib/new_relic/language_support.rb @@ -90,7 +90,7 @@ def snakeize(string) def bundled_gem?(gem_name) return false unless defined?(Bundler) - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0') + if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') Bundler.rubygems.installed_specs.map(&:name).include?(gem_name) else Bundler.rubygems.all_specs.map(&:name).include?(gem_name) From 97e3a7918de52ea9ddf458617998e55c16025128 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 18 Sep 2024 13:44:17 -0700 Subject: [PATCH 66/81] Add changelog --- CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a715ff53f..7005ab26c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,7 @@ ## dev - -Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager. +Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** @@ -61,6 +60,9 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g 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) +- **Bugfix: Update condition to verify Bundler.rubygems.installed_specs is available** + + 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. ## v9.13.0 @@ -68,11 +70,11 @@ Version 9.13.0 enhances support for AWS Lambda functions, adds experimental Open - **Feature: Enhance AWS Lambda function instrumentation** -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. -* The agent's instrumentation for AWS Lambda functions now supports distributed tracing. -* 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. -* Web-based calls have the HTTP method, URI, and status code recorded. -* 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. +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. +* The agent's instrumentation for AWS Lambda functions now supports distributed tracing. +* 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. +* Web-based calls have the HTTP method, URI, and status code recorded. +* 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. * 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. [PR#2811](https://github.com/newrelic/newrelic-ruby-agent/pull/2811) From bf466eff3d07efe041a7fcdd1abe844c7f6ada6b Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 18 Sep 2024 13:46:32 -0700 Subject: [PATCH 67/81] Replace version checks with respond_to? --- CHANGELOG.md | 2 +- lib/new_relic/agent/instrumentation/grape.rb | 2 +- lib/new_relic/control/frameworks/rails4.rb | 2 +- lib/new_relic/environment_report.rb | 2 +- lib/new_relic/language_support.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7005ab26c4..554657e785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,7 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g - **Bugfix: Update condition to verify Bundler.rubygems.installed_specs is available** - 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. + 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?`. ## v9.13.0 diff --git a/lib/new_relic/agent/instrumentation/grape.rb b/lib/new_relic/agent/instrumentation/grape.rb index 493193516f..1424ffa06b 100644 --- a/lib/new_relic/agent/instrumentation/grape.rb +++ b/lib/new_relic/agent/instrumentation/grape.rb @@ -20,7 +20,7 @@ depends_on do begin if defined?(Bundler) && - ((Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) || + ((Bundler.rubygems.respond_to?(:installed_specs) && Bundler.rubygems.installed_specs.map(&:name).include?('newrelic-grape')) || Bundler.rubygems.all_specs.map(&:name).include?('newrelic-grape')) NewRelic::Agent.logger.info('Not installing New Relic supported Grape instrumentation because the third party newrelic-grape gem is present') false diff --git a/lib/new_relic/control/frameworks/rails4.rb b/lib/new_relic/control/frameworks/rails4.rb index 14f225c4fc..23d403dd69 100644 --- a/lib/new_relic/control/frameworks/rails4.rb +++ b/lib/new_relic/control/frameworks/rails4.rb @@ -9,7 +9,7 @@ class Control module Frameworks class Rails4 < NewRelic::Control::Frameworks::Rails3 def rails_gem_list - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') + if Bundler.rubygems.respond_to?(:installed_specs) Bundler.rubygems.installed_specs.map { |gem| "#{gem.name} (#{gem.version})" } else Bundler.rubygems.all_specs.map { |gem| "#{gem.name} (#{gem.version})" } diff --git a/lib/new_relic/environment_report.rb b/lib/new_relic/environment_report.rb index e45beeabc0..1661bebeb6 100644 --- a/lib/new_relic/environment_report.rb +++ b/lib/new_relic/environment_report.rb @@ -44,7 +44,7 @@ def self.registered_reporters=(logic) #################################### report_on('Gems') do begin - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') + if Bundler.rubygems.respond_to?(:installed_specs) Bundler.rubygems.installed_specs.map { |gem| "#{gem.name}(#{gem.version})" } else Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" } diff --git a/lib/new_relic/language_support.rb b/lib/new_relic/language_support.rb index 563e0f800b..140d2455d2 100644 --- a/lib/new_relic/language_support.rb +++ b/lib/new_relic/language_support.rb @@ -90,7 +90,7 @@ def snakeize(string) def bundled_gem?(gem_name) return false unless defined?(Bundler) - if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.5.12') + if Bundler.rubygems.respond_to?(:installed_specs) Bundler.rubygems.installed_specs.map(&:name).include?(gem_name) else Bundler.rubygems.all_specs.map(&:name).include?(gem_name) From 29eb94aaedcea84d1e91e50c45ce5fba84253102 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan <76922290+hannahramadan@users.noreply.github.com> Date: Thu, 19 Sep 2024 08:57:05 -0700 Subject: [PATCH 68/81] Lockdown Boolean configs (#2847) * Correct Boolean config coercion Configs of type Boolean must contain either a boolean or a string/symbol of 'true', 'false', 'yes', 'no', 'on', or 'off' --- CHANGELOG.md | 10 +++ .../agent/configuration/default_source.rb | 17 ++++- lib/new_relic/agent/configuration/manager.rb | 15 ++++ .../agent/javascript_instrumentor.rb | 5 +- .../configuration/default_source_test.rb | 73 +++++++++++++++++-- 5 files changed, 110 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a715ff53f..8493b1b885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,16 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g [PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851) +- **Bugfix: Corrected Boolean coercion for `newrelic.yml` configuration** + + 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. + + Now, the agent strictly interprets Boolean configuration values. It recognizes both actual Boolean values and certain Strings/Symbols: + - `'true'`, `'yes'`, or `'on'` (evaluates to `true`) + - `'false'`, `'no'`, or `'off'` (evaluates to `false`) + + Any other inputs will revert to the setting's default configuration value. [PR#2847](https://github.com/newrelic/newrelic-ruby-agent/pull/2847) + - **Bugfix: Jruby not saving configuration values correctly in configuration manager** 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) diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 0dfd2a07d1..246af67829 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -35,6 +35,15 @@ def self.===(o) end class DefaultSource + BOOLEAN_MAP = { + 'true' => true, + 'yes' => true, + 'on' => true, + 'false' => false, + 'no' => false, + 'off' => false + }.freeze + attr_reader :defaults extend Forwardable @@ -64,6 +73,12 @@ def self.allowlist_for(key) value_from_defaults(key, :allowlist) end + def self.boolean_for(key, value) + string_value = (value.respond_to?(:call) ? value.call : value).to_s + + BOOLEAN_MAP.fetch(string_value, nil) + end + def self.default_for(key) value_from_defaults(key, :default) end @@ -2268,7 +2283,7 @@ def self.notify :description => 'Enable or disable debugging version of JavaScript agent loader for browser monitoring instrumentation.' }, :'browser_monitoring.ssl_for_http' => { - :default => nil, + :default => false, :allow_nil => true, :public => false, :type => Boolean, diff --git a/lib/new_relic/agent/configuration/manager.rb b/lib/new_relic/agent/configuration/manager.rb index 9d56c2c006..f1c5b6bb24 100644 --- a/lib/new_relic/agent/configuration/manager.rb +++ b/lib/new_relic/agent/configuration/manager.rb @@ -142,6 +142,9 @@ def evaluate_and_apply_transformations(key, value) default = enforce_allowlist(key, evaluated) return default if default + boolean = enforce_boolean(key, value) + return boolean if [true, false].include?(boolean) + apply_transformations(key, evaluated) end @@ -167,6 +170,18 @@ def enforce_allowlist(key, value) default end + def enforce_boolean(key, value) + type = default_source.value_from_defaults(key, :type) + return unless type == Boolean + + bool_value = default_source.boolean_for(key, value) + return bool_value unless bool_value.nil? + + default = default_source.default_for(key) + NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'" + default + end + def transform_from_default(key) default_source.transform_for(key) end diff --git a/lib/new_relic/agent/javascript_instrumentor.rb b/lib/new_relic/agent/javascript_instrumentor.rb index e753d2f25c..3e1742c5db 100644 --- a/lib/new_relic/agent/javascript_instrumentor.rb +++ b/lib/new_relic/agent/javascript_instrumentor.rb @@ -164,9 +164,8 @@ def data_for_js_agent(transaction) def add_ssl_for_http(data) ssl_for_http = NewRelic::Agent.config[:'browser_monitoring.ssl_for_http'] - unless ssl_for_http.nil? - data[SSL_FOR_HTTP_KEY] = ssl_for_http - end + + data[SSL_FOR_HTTP_KEY] = ssl_for_http if ssl_for_http end def add_attributes(data, txn) diff --git a/test/new_relic/agent/configuration/default_source_test.rb b/test/new_relic/agent/configuration/default_source_test.rb index 5dc8e1a1a8..dd68ce405d 100644 --- a/test/new_relic/agent/configuration/default_source_test.rb +++ b/test/new_relic/agent/configuration/default_source_test.rb @@ -140,12 +140,6 @@ def test_config_search_path_in_warbler end end - def test_application_logging_enabled_default - with_config(:'application_logging.enabled' => :foo) do - assert_equal :foo, NewRelic::Agent.config['application_logging.enabled'] - end - end - def test_agent_attribute_settings_convert_comma_delimited_strings_into_an_arrays types = %w[transaction_tracer. transaction_events. error_collector. browser_monitoring.] types << '' @@ -313,6 +307,73 @@ def test_automatic_custom_instrumentation_method_list_supports_a_comma_delmited_ end end + def test_boolean_configs_accepts_yes_on_and_true_as_strings + key = :'send_data_on_exit' + config_array = %w[yes on true] + + config_array.each do |value| + with_config(key => value) do + assert NewRelic::Agent.config[key], "The '#{value}' value failed to evaluate as truthy!" + end + end + end + + def test_boolean_configs_accepts_yes_on_and_true_as_symbols + key = :'send_data_on_exit' + config_array = %i[yes on true] + + config_array.each do |value| + with_config(key => value) do + assert NewRelic::Agent.config[key], "The '#{value}' value failed to evaluate as truthy!" + end + end + end + + def test_boolean_configs_accepts_no_off_and_false_as_strings + key = :'send_data_on_exit' + + %w[no off false].each do |value| + with_config(key => value) do + refute NewRelic::Agent.config[key], "The '#{value}' value failed to evaluate as falsey!" + end + end + end + + def test_boolean_configs_accepts_no_off_and_false_as_strings_as_symbols + key = :'send_data_on_exit' + + %i[no off false].each do |value| + with_config(key => value) do + refute NewRelic::Agent.config[key], "The '#{value}' value failed to evaluate as falsey!" + end + end + end + + def test_enforce_boolean_uses_defult_on_invalid_value + key = :'send_data_on_exit' # default value is `true` + + with_config(key => 'invalid_value') do + assert NewRelic::Agent.config[key] + end + end + + def test_enforce_boolean_logs_warning_on_invalid_value + key = :'send_data_on_exit' + default = ::NewRelic::Agent::Configuration::DefaultSource.default_for(key) + + with_config(key => 'yikes!') do + expects_logging(:warn, includes("Invalid value 'yikes!' for #{key}, applying default value of '#{default}'")) + end + end + + def test_boolean_config_evaluates_proc_configs + key = :agent_enabled # default value is a proc + + with_config(key => 'off') do + refute NewRelic::Agent.config[key] + end + end + def get_config_value_class(value) type = value.class From ccd055bc5bafd1a37f2e11206403b38158cec3ed Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:11:18 -0700 Subject: [PATCH 69/81] Update CHANGELOG.md Co-authored-by: Tanna McClure --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554657e785..12ca08e32e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## dev -Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, and fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. +Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** From e8d4356f965db7fb6b9b66234ae273452bf6ffc6 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 25 Sep 2024 10:23:44 -0700 Subject: [PATCH 70/81] Rails edge now requires sqlite3 2.1+ --- test/environments/railsedge/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/environments/railsedge/Gemfile b/test/environments/railsedge/Gemfile index 366403f7c1..9446e5896b 100644 --- a/test/environments/railsedge/Gemfile +++ b/test/environments/railsedge/Gemfile @@ -11,7 +11,7 @@ gem 'mocha', '~> 1.16', require: false platforms :ruby, :rbx do gem 'mysql2', '>= 0.5.4' - gem 'sqlite3', '~> 2.0.4' + gem 'sqlite3', '>= 2.1' end gem 'newrelic_rpm', path: '../../..' From 2dfd22356d42a8d57e443077106363b803115395 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan <76922290+hannahramadan@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:32:59 -0700 Subject: [PATCH 71/81] Use `media_type` instead of `content_type` when avaliable (#2855) * Use media_type to collect MIME type for Rails 7.1+ --- CHANGELOG.md | 6 +++- .../agent/transaction/request_attributes.rb | 14 ++++++++- .../transaction/request_attributes_test.rb | 31 ++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d028e81bd5..95151a76ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## dev -Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. +Version 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, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** @@ -56,6 +56,10 @@ Version adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g [PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851) +- **Feature: Collect just MIME type for AcionDispatch 7.0+ requests** + + 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) + - **Bugfix: Corrected Boolean coercion for `newrelic.yml` configuration** 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. diff --git a/lib/new_relic/agent/transaction/request_attributes.rb b/lib/new_relic/agent/transaction/request_attributes.rb index 60486ae94c..def6f07183 100644 --- a/lib/new_relic/agent/transaction/request_attributes.rb +++ b/lib/new_relic/agent/transaction/request_attributes.rb @@ -24,7 +24,7 @@ def initialize(request) @referer = referer_from_request(request) @accept = attribute_from_env(request, HTTP_ACCEPT_HEADER_KEY) @content_length = content_length_from_request(request) - @content_type = attribute_from_request(request, :content_type) + @content_type = content_type_attribute_from_request(request) @host = attribute_from_request(request, :host) @port = port_from_request(request) @user_agent = attribute_from_request(request, :user_agent) @@ -127,6 +127,18 @@ def attribute_from_request(request, attribute_method) end end + def content_type_attribute_from_request(request) + # Rails 7.0 changed the behavior of `content_type`. We want just the MIME type, so use `media_type` if available. + # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is + content_type = if request.respond_to?(:media_type) + :media_type + elsif request.respond_to?(:content_type) + :content_type + end + + request.send(content_type) if content_type + end + def attribute_from_env(request, key) if env = attribute_from_request(request, :env) env[key] diff --git a/test/new_relic/agent/transaction/request_attributes_test.rb b/test/new_relic/agent/transaction/request_attributes_test.rb index 5ed1baa593..20ccb7115e 100644 --- a/test/new_relic/agent/transaction/request_attributes_test.rb +++ b/test/new_relic/agent/transaction/request_attributes_test.rb @@ -127,13 +127,42 @@ def test_sets_content_length_from_request assert_equal 111, attrs.content_length end - def test_sets_content_type_from_request + def test_sets_content_type_from_request_content_type_attribute request = stub('request', :content_type => 'application/json') attrs = RequestAttributes.new(request) assert_equal 'application/json', attrs.content_type end + def test_sets_content_type_from_request_media_type_attribute + media_type = 'pool-party/alligator' + request = stub('request', media_type: media_type) + attrs = RequestAttributes.new(request) + + assert_equal media_type, attrs.content_type + end + + def test_sets_content_type_to_nil_if_media_type_is_available_with_a_nil_value + request = stub('request', media_type: nil) + attrs = RequestAttributes.new(request) + + assert_nil attrs.content_type + end + + def test_sets_content_type_to_nil_if_content_type_is_available_with_a_nil_value + request = stub('request', content_type: nil) + attrs = RequestAttributes.new(request) + + assert_nil attrs.content_type + end + + def test_sets_content_type_to_nil_if_neither_media_type_or_content_type_are_available + request = stub('request') + attrs = RequestAttributes.new(request) + + assert_nil attrs.content_type + end + def test_sets_host_from_request request = stub('request', :host => 'localhost') attrs = RequestAttributes.new(request) From 44c89d9d2ed57cdb1cc12f1c8d25d4b660b855d8 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan <76922290+hannahramadan@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:59:48 -0700 Subject: [PATCH 72/81] Add boolean update to CHANGELOG summary (#2864) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95151a76ea..a6a3040803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## dev -Version 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, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`. +Version 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, and fixes a bug related to `Bundler.rubygems.installed_specs`. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** From 56ab32eb252708118c73aeab60ae1265b7ab6475 Mon Sep 17 00:00:00 2001 From: newrelic-ruby-agent-bot Date: Wed, 25 Sep 2024 19:12:48 +0000 Subject: [PATCH 73/81] bump version --- CHANGELOG.md | 4 +-- lib/new_relic/version.rb | 2 +- newrelic.yml | 55 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a3040803..867e72ff0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # New Relic Ruby Agent Release Notes -## dev +## v9.14.0 -Version 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, and fixes a bug related to `Bundler.rubygems.installed_specs`. +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, and fixes a bug related to `Bundler.rubygems.installed_specs`. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems** diff --git a/lib/new_relic/version.rb b/lib/new_relic/version.rb index e4889456a2..a64d74cf2e 100644 --- a/lib/new_relic/version.rb +++ b/lib/new_relic/version.rb @@ -6,7 +6,7 @@ module NewRelic module VERSION # :nodoc: MAJOR = 9 - MINOR = 13 + MINOR = 14 TINY = 0 STRING = "#{MAJOR}.#{MINOR}.#{TINY}" diff --git a/newrelic.yml b/newrelic.yml index cb2c78a591..3ef6c05b77 100644 --- a/newrelic.yml +++ b/newrelic.yml @@ -113,6 +113,53 @@ common: &default_settings # Specifies a path to the audit log file (including the filename). # audit_log.path: log/newrelic_audit.log + # 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. + # Use fully qualified class names (using the :: delimiter) that include any + # module or class namespacing. + # 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: + # `` + # module MyCompany + # class Image + # def render_png + # # code to render a PNG + # end + # end + # class User + # def self.notify + # # code to notify users + # end + # end + # end + # ` + # Given that source code, the newrelic.yml config file might request + # instrumentation for both of these methods like so: + # ` + # automatic_custom_instrumentation_method_list: + # - MyCompany::Image#render_png + # - MyCompany::User.notify + # ` + # That configuration example uses YAML array syntax to specify both methods. + # Alternatively, a comma-delimited string can be used instead: + # ` + # automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, + # MyCompany::User.notify' + # ` + # 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: + # ` + # export + # NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, + # MyCompany::User.notify' + # `` + # automatic_custom_instrumentation_method_list: [] + # Specify a list of constants that should prevent the agent from starting # automatically. Separate individual constants with a comma ,. For example, # "Rails::Console,UninstrumentedBackgroundJob". @@ -535,6 +582,10 @@ common: &default_settings # prepend, chain, disabled. # instrumentation.rake: auto + # Controls auto-instrumentation of the rdkafka library at start-up. May be one + # of auto, prepend, chain, disabled. + # instrumentation.rdkafka: auto + # Controls auto-instrumentation of Redis at start-up. May be one of: auto, # prepend, chain, disabled. # instrumentation.redis: auto @@ -547,6 +598,10 @@ common: &default_settings # prepend, chain, disabled. # instrumentation.roda: auto + # Controls auto-instrumentation of the ruby-kafka library at start-up. May be + # one of auto, prepend, chain, disabled. + # instrumentation.ruby_kafka: auto + # Controls auto-instrumentation of the ruby-openai gem at start-up. May be one # of: auto, prepend, chain, disabled. Defaults to disabled in high security # mode. From 48b1d7c10f217d906ddf63f135fb1d2c84d8fc75 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:38:09 -0700 Subject: [PATCH 74/81] Update newrelic.yml --- newrelic.yml | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/newrelic.yml b/newrelic.yml index 3ef6c05b77..a9f475437d 100644 --- a/newrelic.yml +++ b/newrelic.yml @@ -117,47 +117,47 @@ common: &default_settings # 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. + # # Use fully qualified class names (using the :: delimiter) that include any # module or class namespacing. + # # 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: - # `` + # # module MyCompany - # class Image - # def render_png - # # code to render a PNG - # end - # end - # class User - # def self.notify - # # code to notify users - # end - # end + # class Image + # def render_png + # # code to render a PNG + # end + # end + # + # class User + # def self.notify + # # code to notify users + # end + # end # end - # ` + # # Given that source code, the newrelic.yml config file might request # instrumentation for both of these methods like so: - # ` + # # automatic_custom_instrumentation_method_list: # - MyCompany::Image#render_png # - MyCompany::User.notify - # ` + # # That configuration example uses YAML array syntax to specify both methods. # Alternatively, a comma-delimited string can be used instead: - # ` - # automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, - # MyCompany::User.notify' - # ` + # + # automatic_custom_instrumentation_method_list: 'MyCompany::Image#render_png, MyCompany::User.notify' + # # 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: - # ` - # export - # NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, - # MyCompany::User.notify' - # `` + # + # export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify' + # # automatic_custom_instrumentation_method_list: [] # Specify a list of constants that should prevent the agent from starting From 44bcb745ec6726f7a41dcedac261083f656260de Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Wed, 25 Sep 2024 16:12:43 -0700 Subject: [PATCH 75/81] Update changelog for 9.14.0 release --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 867e72ff0e..413cb880dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,15 +54,15 @@ Version 9.14.0 adds Apache Kafka instrumentation for the rdkafka and ruby-kafka export NEW_RELIC_AUTOMATIC_CUSTOM_INSTRUMENTATION_METHOD_LIST='MyCompany::Image#render_png, MyCompany::User.notify' ``` -[PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851) + [PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851) - **Feature: Collect just MIME type for AcionDispatch 7.0+ requests** - + 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) - **Bugfix: Corrected Boolean coercion for `newrelic.yml` configuration** - 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. + 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. Now, the agent strictly interprets Boolean configuration values. It recognizes both actual Boolean values and certain Strings/Symbols: - `'true'`, `'yes'`, or `'on'` (evaluates to `true`) @@ -70,13 +70,13 @@ Version 9.14.0 adds Apache Kafka instrumentation for the rdkafka and ruby-kafka Any other inputs will revert to the setting's default configuration value. [PR#2847](https://github.com/newrelic/newrelic-ruby-agent/pull/2847) -- **Bugfix: Jruby not saving configuration values correctly in configuration manager** +- **Bugfix: JRuby not saving configuration values correctly in configuration manager** 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) - **Bugfix: Update condition to verify Bundler.rubygems.installed_specs is available** - 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?`. + 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) ## v9.13.0 From df7149839b709f252848c73b017ff15c397ce68a Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Sat, 28 Sep 2024 18:46:13 +0200 Subject: [PATCH 76/81] Safety check in instrumentation render_in_with_tracing that identifier to self.class --- .../agent/instrumentation/view_component/instrumentation.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb b/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb index 71488ca9d0..3005214c36 100644 --- a/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +++ b/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb @@ -11,7 +11,10 @@ def render_in_with_tracing(*args) begin segment = NewRelic::Agent::Tracer.start_segment( - name: metric_name(self.class.identifier, self.class.name) + name: metric_name( + self.class.respond_to?(:identifier) ? self.class.identifier : nil, + self.class.name + ) ) yield rescue => e From 9587e776e978f86fefb5265a2c9372572c11b854 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Mon, 30 Sep 2024 09:56:13 -0700 Subject: [PATCH 77/81] Remove railsedge testing from Ruby 3.1 --- .github/workflows/ci_cron.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index 7e783d5401..02dd0a08aa 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -52,7 +52,7 @@ jobs: - name: Install Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # tag v1.191.0 with: - ruby-version: ${{ matrix.ruby-version }} + ruby-version: ${{ matrix.ruby-version }} - name: Set up mini-envs for ruby version uses: ./.github/actions/variable-mapper @@ -76,7 +76,7 @@ jobs: "rails": "norails,rails61,rails60,rails70,rails71" }, "3.1.6": { - "rails": "norails,rails61,rails70,rails71,rails72,railsedge" + "rails": "norails,rails61,rails70,rails71,rails72" }, "3.2.5": { "rails": "norails,rails61,rails70,rails71,rails72,railsedge" From 485b1f2b88637a09d2d97483b849926c5f06cb64 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Mon, 30 Sep 2024 10:35:51 -0700 Subject: [PATCH 78/81] Update Envfiles for Rails 8.0 --- test/multiverse/suites/active_record_pg/Envfile | 5 +++-- .../suites/active_support_broadcast_logger/Envfile | 3 ++- test/multiverse/suites/rails/Envfile | 2 +- test/multiverse/suites/rails_prepend/Envfile | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/multiverse/suites/active_record_pg/Envfile b/test/multiverse/suites/active_record_pg/Envfile index 68d718c94d..de48cc5477 100644 --- a/test/multiverse/suites/active_record_pg/Envfile +++ b/test/multiverse/suites/active_record_pg/Envfile @@ -12,7 +12,8 @@ end serialize! ACTIVERECORD_VERSIONS = [ - [nil, 3.1], + [nil, 3.2], + ['7.2.0', 3.1], ['7.1.0', 2.7], ['7.0.0', 2.7], ['6.1.0', 2.5], @@ -28,7 +29,7 @@ def gem_list(activerecord_version = nil) <<~RB gem 'activerecord'#{activerecord_version} gem 'pg' - + gem 'rack' gem 'minitest', '~> 5.2.3' RB diff --git a/test/multiverse/suites/active_support_broadcast_logger/Envfile b/test/multiverse/suites/active_support_broadcast_logger/Envfile index 9a274d0c4f..cb5286d84c 100644 --- a/test/multiverse/suites/active_support_broadcast_logger/Envfile +++ b/test/multiverse/suites/active_support_broadcast_logger/Envfile @@ -7,7 +7,8 @@ instrumentation_methods :chain, :prepend # ActiveSupport::BroadcastLogger introduced in Rails 7.1. # Rails 7.1 is the latest version at the time of writing. ACTIVE_SUPPORT_VERSIONS = [ - [nil, 3.1], + [nil, 3.2], + ['7.2.0', 3.1], ['7.1.0', 2.7] ] diff --git a/test/multiverse/suites/rails/Envfile b/test/multiverse/suites/rails/Envfile index d5e9b485b6..f645b1b712 100644 --- a/test/multiverse/suites/rails/Envfile +++ b/test/multiverse/suites/rails/Envfile @@ -3,7 +3,7 @@ # frozen_string_literal: true RAILS_VERSIONS = [ - [nil, 3.1], + [nil, 3.2], ['7.2.0', 3.1], ['7.1.0', 2.7], ['7.0.4', 2.7], diff --git a/test/multiverse/suites/rails_prepend/Envfile b/test/multiverse/suites/rails_prepend/Envfile index 18884f4cb1..801a0e8ab2 100644 --- a/test/multiverse/suites/rails_prepend/Envfile +++ b/test/multiverse/suites/rails_prepend/Envfile @@ -3,7 +3,8 @@ # frozen_string_literal: true RAILS_VERSIONS = [ - [nil, 3.1], + [nil, 3.2], + ['7.2.0', 3.1], ['7.1.0', 2.7], ['7.0.0', 2.7], ['6.1.0', 2.5], From bbfdb8e3eb5b485b75b1eb71c8e9922eab836cc2 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Mon, 30 Sep 2024 10:58:53 -0700 Subject: [PATCH 79/81] Update min version in unshift_rails_edge --- test/multiverse/lib/multiverse/envfile.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/multiverse/lib/multiverse/envfile.rb b/test/multiverse/lib/multiverse/envfile.rb index 1c7d33b0db..5158996689 100644 --- a/test/multiverse/lib/multiverse/envfile.rb +++ b/test/multiverse/lib/multiverse/envfile.rb @@ -169,7 +169,7 @@ def unshift_rails_edge(gem_version_array = []) # NOTE: The Rails Edge version is not tested unless the Ruby version in # play is greater than or equal to (>=) the version number at the # end of the unshifted inner array - gem_version_array.unshift(["github: 'rails'", 3.1]) + gem_version_array.unshift(["github: 'rails'", 3.2]) end # are we running in a CI context intended for PR approvals? From 72d32e9574cc77fd22985ea01de19886237554e1 Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 30 Sep 2024 12:58:58 -0700 Subject: [PATCH 80/81] unit test and CHANGELOG entry for 2870 unit test branch coverage and a CHANGELOG bugfix writeup to accompany PR 2870 --- CHANGELOG.md | 4 ++++ .../view_component_instrumentation_test.rb | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 413cb880dc..fb4598636c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,10 @@ Version 9.14.0 adds Apache Kafka instrumentation for the rdkafka and ruby-kafka 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) +- **Bugfix: Support view_component v3.15.0+** + + 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). + ## v9.13.0 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. diff --git a/test/multiverse/suites/view_component/view_component_instrumentation_test.rb b/test/multiverse/suites/view_component/view_component_instrumentation_test.rb index b3cfbd908d..01e36d291c 100644 --- a/test/multiverse/suites/view_component/view_component_instrumentation_test.rb +++ b/test/multiverse/suites/view_component/view_component_instrumentation_test.rb @@ -57,4 +57,15 @@ def test_error_raised assert_equal(500, get('/view_components')) end end + + # Test metric name being built when the controller class doesn't respond to :identifier + # https://github.com/newrelic/newrelic-ruby-agent/pull/2870 + def test_the_metric_name_omits_the_identifier_when_absent + in_transaction do |txn| + FAKE_CLASS.render_in_with_tracing { 11 * 38 } + actual_name = txn.segments.last.name + + assert_equal 'View/component/DummyViewComponentInstrumentationClass', actual_name + end + end end From ad5f9b8ee769cc15c01751d21ec2fbbee6b2d724 Mon Sep 17 00:00:00 2001 From: fallwith Date: Mon, 30 Sep 2024 13:23:27 -0700 Subject: [PATCH 81/81] add ViewComponent fix mention to the v9.14.0 summary reference the ViewComponent community PR fix in the v9.14.0 summary --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb4598636c..8cec4b666f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v9.14.0 -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, and fixes a bug related to `Bundler.rubygems.installed_specs`. +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+. - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems**