Skip to content

Commit

Permalink
Merge pull request #2267 from newrelic/roda_newrelic_ignore
Browse files Browse the repository at this point in the history
Add Roda support for newrelic_ignore*
  • Loading branch information
hannahramadan authored Oct 17, 2023
2 parents b9db71f + 3b755c1 commit 10e75d1
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 2 deletions.
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ Version <dev> gleans Docker container IDs from cgroups v2-based containers, reco

For compatibility with Ruby 3.4 and to silence compatibility warnings present in Ruby 3.3, declare a dependency on the `base64` gem. The New Relic Ruby agent uses the native Ruby `base64` gem for Base 64 encoding/decoding. The agent is joined by Ruby on Rails ([rails/rails@3e52adf](https://github.com/rails/rails/commit/3e52adf28e90af490f7e3bdc4bcc85618a4e0867)) and others in making this change in preparation for Ruby 3.3/3.4. [PR#2238](https://github.com/newrelic/newrelic-ruby-agent/pull/2238)

- **Fix: Stop sending duplicate log events for Rails 7.1 users**
-**Feature: Add Roda support for the newrelic_ignore\* family of methods**

The agent can now selectively disable instrumentation for particular requests within Roda applications. Supported methods include:
- `newrelic_ignore`: ignore a given route.
- `newrelic_ignore_apdex`: exclude a given route from consideration in overall Apdex calculations.
- `newrelic_ignore_enduser`: prevent automatic injection of the page load timing JavaScript when a route is rendered.

For more information, see [Roda Instrumentation](https://docs.newrelic.com/docs/apm/agents/ruby-agent/instrumented-gems/roda-instrumentation/). [PR#2267](https://github.com/newrelic/newrelic-ruby-agent/pull/2267)

- **Bugfix: Stop sending duplicate log events for Rails 7.1 users**

Rails 7.1 introduced the public API [`ActiveSupport::BroadcastLogger`](https://api.rubyonrails.org/classes/ActiveSupport/BroadcastLogger.html). This logger replaces a private API, `ActiveSupport::Logger.broadcast`. In Rails versions below 7.1, the agent uses the `broadcast` method to stop duplicate logs from being recoded by broadcasted loggers. Now, we've updated the code to provide a similar duplication fix for the `ActiveSupport::BroadcastLogger` class. [PR#2252](https://github.com/newrelic/newrelic-ruby-agent/pull/2252)

- **Fix: Resolve Sidekiq 8.0 error handler deprecation warning**
- **Bugfix: Resolve Sidekiq 8.0 error handler deprecation warning**

Sidekiq 8.0 will require procs passed to the error handler to include three arguments: error, context, and config. Users running sidekiq/main would receive a deprecation warning with this change any time an error was raised within a job. Thank you, [@fukayatsu](https://github.com/fukayatsu) for your proactive fix! [PR#2261](https://github.com/newrelic/newrelic-ruby-agent/pull/2261)

Expand Down
2 changes: 2 additions & 0 deletions lib/new_relic/agent/instrumentation/roda.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

require_relative 'roda/instrumentation'
require_relative 'roda/roda_transaction_namer'
require_relative 'roda/ignorer'

DependencyDetection.defer do
named :roda
Expand All @@ -30,5 +31,6 @@
chain_instrument NewRelic::Agent::Instrumentation::Roda::Build::Chain
chain_instrument NewRelic::Agent::Instrumentation::Roda::Chain
end
Roda.class_eval { extend NewRelic::Agent::Instrumentation::Roda::Ignorer }
end
end
45 changes: 45 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/ignorer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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 Roda
module Ignorer
def self.should_ignore?(app, type)
return false unless app.opts.include?(:newrelic_ignores)

app.opts[:newrelic_ignores][type].any? do |pattern|
pattern === app.request.path_info
end
end

def newrelic_ignore(*routes)
set_newrelic_ignore(:routes, *routes)
end

def newrelic_ignore_apdex(*routes)
set_newrelic_ignore(:apdex, *routes)
end

def newrelic_ignore_enduser(*routes)
set_newrelic_ignore(:enduser, *routes)
end

private

def set_newrelic_ignore(type, *routes)
# Create a newrelic_ignores hash if one doesn't exist
opts[:newrelic_ignores] = Hash.new([]) if !opts.include?(:newrelic_ignores)

if routes.empty?
opts[:newrelic_ignores][type] += [Regexp.new('.*')]
else
opts[:newrelic_ignores][type] += routes.map do |r|
# Roda adds leading slashes to routes, so we need to do the same
"#{'/' unless r.start_with?('/')}#{r}"
end
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ def _roda_handle_main_route_with_tracing(*args)
yield
end
end

def do_not_trace?
NewRelic::Agent::Instrumentation::Roda::Ignorer.should_ignore?(self, :routes)
end

def ignore_apdex?
NewRelic::Agent::Instrumentation::Roda::Ignorer.should_ignore?(self, :apdex)
end

def ignore_enduser?
NewRelic::Agent::Instrumentation::Roda::Ignorer.should_ignore?(self, :enduser)
end
end
end
end
203 changes: 203 additions & 0 deletions test/multiverse/suites/roda/ignorer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# 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 '../../../../lib/new_relic/agent/instrumentation/roda/instrumentation'
require_relative '../../../../lib/new_relic/agent/instrumentation/roda/roda_transaction_namer'
require_relative '../../../../lib/new_relic/agent/instrumentation/roda/ignorer'

JS_AGENT_LOADER = 'JS_AGENT_LOADER'

class RodaIgnorerTestApp < Roda
newrelic_ignore('/ignore_me', '/ignore_me_too')
newrelic_ignore('no_leading_slash')
newrelic_ignore('/ignored_erroring')

route do |r|
r.on('/home') { 'home' }
r.on('/ignore_me') { 'this page is ignored' }
r.on('/ignore_me_too') { 'this page is ignored too' }
r.on('/ignore_me_not') { 'our regex should not capture this' }
r.on('no_leading_slash') { 'user does not use leading slash for ignore' }
r.on('/no_apdex') { 'no apex should be recorded' }
r.on('/ignored_erroring') { raise 'boom' }
end
end

class RodaIgnoreTest < Minitest::Test
include Rack::Test::Methods
include MultiverseHelpers

setup_and_teardown_agent

def app
RodaIgnorerTestApp
end

def test_seen_route
get('/home')

assert_metrics_recorded('Controller/Roda/RodaIgnorerTestApp/GET home')
end

def test_ignore_route
get('/ignore_me')

assert_metrics_not_recorded([
'Controller/Roda/RodaIgnorerTestApp/GET ignore_me',
'Apdex/Roda/RodaIgnorerTestApp/GET ignore_me'
])
end

def test_regex_ignores_intended_route
get('/ignore_me')
get('/ignore_me_not')

assert_metrics_not_recorded([
'Controller/Roda/RodaIgnorerTestApp/GET ignore_me',
'Apdex/Roda/RodaIgnorerTestApp/GET ignore_me'
])

assert_metrics_recorded([
'Controller/Roda/RodaIgnorerTestApp/GET ignore_me_not',
'Apdex/Roda/RodaIgnorerTestApp/GET ignore_me_not'
])
end

def test_ignores_if_route_does_not_have_leading_slash
get('no_leading_slash')

assert_metrics_not_recorded([
'Controller/Roda/RodaIgnorerTestApp/GET no_leading_slash',
'Apdex/Roda/RodaIgnorerTestApp/GET no_leading_slash'
])
end

def test_ignore_errors_in_ignored_transactions
get('/ignored_erroring')

assert_metrics_not_recorded(['Errors/all'])
end
end

class RodaIgnoreAllRoutesApp < Roda
# newrelic_ignore called without any arguments will ignore the entire app
newrelic_ignore

route do |r|
r.on('home') { 'home' }
r.on('hello') { 'hello' }
end
end

class RodaIgnoreAllRoutesAppTest < Minitest::Test
include Rack::Test::Methods
include MultiverseHelpers

setup_and_teardown_agent

def app
RodaIgnoreAllRoutesApp
end

def test_ignores_by_splats
get('/hello')
get('/home')

assert_metrics_not_recorded([
'Controller/Roda/RodaIgnoreAllTestApp/GET hello',
'Apdex/Roda/RodaIgnoreAllTestApp/GET hello'
])

assert_metrics_not_recorded([
'Controller/Roda/RodaIgnoreAllTestApp/GET home',
'Apdex/Roda/RodaIgnoreAllTestApp/GET home'
])
end
end

class RodaIgnoreApdexApp < Roda
newrelic_ignore_apdex('/no_apdex')

route do |r|
r.on('home') { 'home' }
r.on('no_apdex') { 'do not record apdex' }
end
end

class RodaIgnoreApdexAppTest < Minitest::Test
include Rack::Test::Methods
include MultiverseHelpers

setup_and_teardown_agent

def app
RodaIgnoreApdexApp
end

def test_ignores_apdex_by_route
get('/no_apdex')

assert_metrics_not_recorded('Apdex/Roda/RodaIgnoreApdexApp/GET no_apdex')
end

def test_ignores_enduser_but_not_route
get('no_apdex')

assert_metrics_recorded('Controller/Roda/RodaIgnoreApdexApp/GET no_apdex')
assert_metrics_not_recorded('Apdex/Roda/RodaIgnoreApdexApp/GET no_apdex')
end
end

class RodaIgnoreEndUserApp < Roda
def fake_html_for_browser_timing_header
'<html><head><title></title></head><body></body></html>'
end

newrelic_ignore_enduser('ignore_enduser')

route do |r|
r.on('home') { fake_html_for_browser_timing_header }
r.on('ignore_enduser') { fake_html_for_browser_timing_header }
end
end

class RodaIgnoreEndUserAppTest < Minitest::Test
include Rack::Test::Methods
include MultiverseHelpers

def assert_enduser_ignored(response)
refute_match(/#{JS_AGENT_LOADER}/o, response.body)
end

def refute_enduser_ignored(response)
assert_match(/#{JS_AGENT_LOADER}/o, response.body)
end

setup_and_teardown_agent(:application_id => 'appId',
:beacon => 'beacon',
:browser_key => 'browserKey',
:js_agent_loader => 'JS_AGENT_LOADER')

def app
RodaIgnoreEndUserApp
end

def test_ignore_enduser_should_only_apply_to_specified_route
with_config(:application_id => 'appId',
:beacon => 'beacon',
:browser_key => 'browserKey',
:js_agent_loader => 'JS_AGENT_LOADER') do
get('home')

refute_enduser_ignored(last_response)
assert_metrics_recorded('Controller/Roda/RodaIgnoreEndUserApp/GET home')
end
end

def test_ignores_enduser
get('ignore_enduser')

assert_enduser_ignored(last_response)
end
end

0 comments on commit 10e75d1

Please sign in to comment.