diff --git a/CHANGELOG.md b/CHANGELOG.md index 01be8a2dba..6a01b0bde9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ The experimental feature, Configurable Security Policies (CSP), is no longer supported and has been removed. [PR#3292](https://github.com/newrelic/newrelic-ruby-agent/pull/3292) +- **Feature: Add Active Support notification allowlist configuration option** + + A new configuration option, `instrumentation.active_support_notifications.active_support_events`, allows users to define an allowlist of Active + Support notifications event names for the agent to subscribe to. By default, the agent subscribes to all [Active Support: Caching](https://guides.rubyonrails.org/active_support_instrumentation.html#active-support-caching) and [Active Support: Messages](https://guides.rubyonrails.org/active_support_instrumentation.html#active-support-messages) events. [PR#3327](https://github.com/newrelic/newrelic-ruby-agent/pull/3327) + - **Feature: Use Ruby's built-in Gzip compression** The agent now uses the built-in `Zlib.gzip` method from the Ruby standard library for compression, replacing the previous custom implementation. [PR#3332](https://github.com/newrelic/newrelic-ruby-agent/pull/3332) diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 0ac5cd2180..72d328a145 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -4,6 +4,7 @@ require 'forwardable' require_relative '../../constants' +require_relative '../instrumentation/active_support_subscriber' module NewRelic module Agent @@ -1495,6 +1496,24 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :description => 'Configures the TCP/IP port for the trace observer Host' }, # Instrumentation + :'instrumentation.active_support_notifications.active_support_events' => { + :default => NewRelic::Agent::Instrumentation::ActiveSupportSubscriber::EVENT_NAME_TO_METHOD_NAME.keys, + :public => true, + :type => Array, + :allowed_from_server => false, + :description => <<~ACTIVE_SUPPORT_EVENTS.chomp.tr("\n", ' ') + An allowlist array of Active Support notifications events specific to the Active Support library + itself that the agent should subscribe to. The Active Support library specific events focus primarily + on caching. Any event name not included in this list will be ignored by the agent. Provide complete event + names such as 'cache_fetch_hit.active_support'. Do not provide asterisks or regex patterns, and do not + escape any characters with backslashes. + + For a complete list of all possible Active Support event names, see the + [list of caching names](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#active-support-caching) + and the [list of messages names](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#active-support-messages) + from the official Rails documentation. + ACTIVE_SUPPORT_EVENTS + }, :'instrumentation.active_support_broadcast_logger' => { :default => instrumentation_value_from_boolean(:'application_logging.enabled'), :documentation_default => 'auto', diff --git a/lib/new_relic/agent/instrumentation/active_support.rb b/lib/new_relic/agent/instrumentation/active_support.rb index ec4f722981..8dbf5cdf83 100644 --- a/lib/new_relic/agent/instrumentation/active_support.rb +++ b/lib/new_relic/agent/instrumentation/active_support.rb @@ -7,6 +7,8 @@ DependencyDetection.defer do named :active_support + EVENT_NAMES_PARAMETER = :'instrumentation.active_support_notifications.active_support_events' + depends_on do !NewRelic::Agent.config[:disable_active_support] end @@ -16,12 +18,17 @@ !NewRelic::Agent::Instrumentation::ActiveSupportSubscriber.subscribed? end + depends_on do + !NewRelic::Agent.config[EVENT_NAMES_PARAMETER].empty? + end + executes do NewRelic::Agent.logger.info('Installing ActiveSupport instrumentation') end executes do - ActiveSupport::Notifications.subscribe(/\.active_support$/, + event_names = NewRelic::Agent.config[EVENT_NAMES_PARAMETER].map { |n| Regexp.escape(n) }.join('|') + ActiveSupport::Notifications.subscribe(/\A(?:#{event_names})\z/, NewRelic::Agent::Instrumentation::ActiveSupportSubscriber.new) end end diff --git a/lib/new_relic/agent/instrumentation/active_support_subscriber.rb b/lib/new_relic/agent/instrumentation/active_support_subscriber.rb index 326efd4b16..6c04edc48d 100644 --- a/lib/new_relic/agent/instrumentation/active_support_subscriber.rb +++ b/lib/new_relic/agent/instrumentation/active_support_subscriber.rb @@ -2,12 +2,30 @@ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true -require 'new_relic/agent/instrumentation/notifications_subscriber' +require_relative 'notifications_subscriber' module NewRelic module Agent module Instrumentation class ActiveSupportSubscriber < NotificationsSubscriber + EVENT_NAME_TO_METHOD_NAME = { + 'cache_fetch_hit.active_support' => 'fetch_hit', + 'cache_generate.active_support' => 'generate', + 'cache_read.active_support' => 'read', + 'cache_write.active_support' => 'write', + 'cache_delete.active_support' => 'delete', + 'cache_exist?.active_support' => 'exist?', + 'cache_read_multi.active_support' => 'read_multi', + 'cache_write_multi.active_support' => 'write_multi', + 'cache_delete_multi.active_support' => 'delete_multi', + 'cache_delete_matched.active_support' => 'delete_matched', + 'cache_cleanup.active_support' => 'cleanup', + 'cache_increment.active_support' => 'increment', + 'cache_decrement.active_support' => 'decrement', + 'cache_prune.active_support' => 'prune', + 'message_serializer_fallback.active_support' => 'message_serializer_fallback' + }.freeze + def add_segment_params(segment, payload) segment.params[:key] = payload[:key] segment.params[:store] = payload[:store] @@ -18,22 +36,12 @@ def add_segment_params(segment, payload) def metric_name(name, payload) store = payload[:store] - method = method_from_name(name) + method = method_name(name) "Ruby/ActiveSupport#{"/#{store}" if store}/#{method}" end - PATTERN = /\Acache_([^\.]*)\.active_support\z/ - - METHOD_NAME_MAPPING = Hash.new do |h, k| - if PATTERN =~ k - h[k] = $1 - else - h[k] = NewRelic::UNKNOWN - end - end - - def method_from_name(name) - METHOD_NAME_MAPPING[name] + def method_name(name) + EVENT_NAME_TO_METHOD_NAME.fetch(name, name.delete_prefix('cache_').delete_suffix('.active_support')) end end end diff --git a/test/new_relic/agent/instrumentation/rails/active_support_subscriber.rb b/test/new_relic/agent/instrumentation/rails/active_support_subscriber.rb index 5b5fc8790d..493ae7b5e3 100644 --- a/test/new_relic/agent/instrumentation/rails/active_support_subscriber.rb +++ b/test/new_relic/agent/instrumentation/rails/active_support_subscriber.rb @@ -75,11 +75,12 @@ def test_metric_recorded_for_new_event_names end def test_failsafe_if_event_does_not_match_expected_pattern + name = 'charcuterie_build_a_board_workshop' in_transaction('test') do - generate_event('charcuterie_build_a_board_workshop') + generate_event(name) end - assert_metrics_recorded "#{METRIC_PREFIX}#{DEFAULT_STORE}/Unknown" + assert_metrics_recorded "#{METRIC_PREFIX}#{DEFAULT_STORE}/#{name}" end def test_key_recorded_as_attribute_on_traces