Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subscribe to additional Action controller topics #1744

Merged
merged 40 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4607b3c
Subscribe to render_layout
hannahramadan Dec 30, 2022
a4d709f
WIP add subscribers for additional
tannalynn Jan 17, 2023
37f5010
add test for halted_callback
tannalynn Jan 19, 2023
d16512f
updated names/paths
tannalynn Jan 19, 2023
30346c9
added unpermitted parameter test
tannalynn Jan 19, 2023
4d82aa9
moved generic start and finish to parent class
tannalynn Jan 19, 2023
9224ab5
added parameters to tests
tannalynn Jan 20, 2023
d24827c
move action controller that aren't process_action
tannalynn Jan 20, 2023
a6ad626
Merge branch 'dev' into action_controller_subscriber_additional_topic
tannalynn Jan 20, 2023
ee1c789
moved test for start and finish into
tannalynn Jan 20, 2023
e2d9788
removed start and finish from subclasses that are
tannalynn Jan 20, 2023
7d1caec
rename file
tannalynn Jan 20, 2023
97da112
fix for rails 6 tests
tannalynn Jan 20, 2023
39ad8b2
skip when topic not available in rails version
tannalynn Jan 20, 2023
30b9939
removed binding.irb
tannalynn Jan 20, 2023
247bf5a
fix Gem::Version.new
tannalynn Jan 20, 2023
92402a8
update to match all rails version filter
tannalynn Jan 20, 2023
5363967
pr feedback updates
tannalynn Jan 20, 2023
fbccb6e
fix spelling
tannalynn Jan 20, 2023
512bfea
Merge branch 'action_controller_subscriber_additional_topic' into NR1…
hannahramadan Jan 20, 2023
319e972
Add test and refactor
hannahramadan Jan 20, 2023
c834c46
Add test for when there is no segment
hannahramadan Jan 20, 2023
e675cb8
added test for not implemented methods
tannalynn Jan 20, 2023
7e3a4f8
Protect for unknown action view topics
hannahramadan Jan 20, 2023
bbeff88
DRYed up notification subscriber some more
tannalynn Jan 21, 2023
57d6f61
Merge pull request #1748 from newrelic/NR1512_subscribe_actionview
hannahramadan Jan 21, 2023
8a1b890
Merge branch 'dev' into action_controller_subscriber_additional_topic
tannalynn Jan 21, 2023
ac50f7c
DRYed up notification subscriber subclasses
tannalynn Jan 21, 2023
7172943
Merge branch 'action_controller_subscriber_additional_topic' of githu…
tannalynn Jan 21, 2023
acf7507
comment out hanging test
tannalynn Jan 23, 2023
1e7e1bc
add test for segment getting created by
tannalynn Jan 23, 2023
66c6dd9
disable weird rubocop failure
tannalynn Jan 23, 2023
5fd2e2a
Merge branch 'dev' into action_controller_subscriber_additional_topic
tannalynn Jan 23, 2023
1c6ddda
added config
tannalynn Jan 23, 2023
f7a252f
Use NewRelic::UNKNOWN const
hannahramadan Jan 23, 2023
ac8f980
Merge pull request #1755 from newrelic/actionview-updates
hannahramadan Jan 24, 2023
55ed14f
fix for frozen segment name in action mailer
tannalynn Jan 24, 2023
6eb12b3
test fixes
tannalynn Jan 24, 2023
3a82667
rubocop
tannalynn Jan 24, 2023
a41dba4
don't test action mailer on ruby 2.3
tannalynn Jan 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The upcoming release of the agent introduces additional Ruby on Rails instrument

The agent now newly supports or has updated support for the following libraries:

- Action Controller [PR#1744](https://github.com/newrelic/newrelic-ruby-agent/pull/1744/)
- Action Dispatch (for middleware) [PR#1745](https://github.com/newrelic/newrelic-ruby-agent/pull/1745)
- Action Mailbox (for sending mail) [PR#1740](https://github.com/newrelic/newrelic-ruby-agent/pull/1740)
- Action Mailer (for routing mail) [PR#1740](https://github.com/newrelic/newrelic-ruby-agent/pull/1740)
Expand All @@ -19,6 +20,7 @@ The upcoming release of the agent introduces additional Ruby on Rails instrument

| Configuration name | Default | Behavior |
| ----- | ----- | ----- |
| `disable_action_controller` | `false` | If `true`, disables Action Controller instrumentation. |
| `disable_action_dispatch` | `false` | If `true`, disables Action Dispatch instrumentation. |
| `disable_action_mailbox` | `false` | If `true`, disables Action Mailbox instrumentation. |
| `disable_action_mailer` | `false` | If `true`, disables Action Mailer instrumentation. |
Expand Down
7 changes: 7 additions & 0 deletions lib/new_relic/agent/configuration/default_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,13 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:allowed_from_server => false,
:description => 'If `true`, disables Action Dispatch instrumentation.'
},
:disable_action_controller => {
:default => false,
:public => true,
:type => Boolean,
:allowed_from_server => false,
:description => 'If `true`, disables Action Controller instrumentation.'
},
:disable_action_mailbox => {
:default => false,
:public => true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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/instrumentation/notifications_subscriber'
require 'new_relic/agent/instrumentation/ignore_actions'
require 'new_relic/agent/parameter_filtering'

module NewRelic
module Agent
module Instrumentation
class ActionControllerOtherSubscriber < NotificationsSubscriber
def add_segment_params(segment, payload)
segment.params[:filter] = payload[:filter] if payload[:filter]
segment.params[:keys] = payload[:keys] if payload[:keys]
segment.params[:original_path] = payload[:request].original_fullpath if payload[:request]

if payload[:context]
segment.params[:action] = payload[:context][:action]
segment.params[:controller] = payload[:context][:controller]
end
end

def metric_name(name, payload)
controller_name = controller_name_for_metric(payload)
"Ruby/ActionController#{"/#{controller_name}" if controller_name}/#{name.gsub(/\.action_controller/, '')}"
end

def controller_name_for_metric(payload)
# redirect_to
return payload[:request].controller_class.controller_path if payload[:request] && payload[:request].controller_class

# unpermitted_parameters
::NewRelic::LanguageSupport.constantize(payload[:context][:controller]).controller_path if payload[:context] && payload[:context][:controller]
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,19 @@ module NewRelic
module Agent
module Instrumentation
class ActionMailboxSubscriber < NotificationsSubscriber
def start(name, id, payload)
return unless state.is_execution_traced?

start_segment(name, id, payload)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
return unless state.is_execution_traced?

finish_segment(id, payload)
rescue => e
log_notification_error(e, name, 'finish')
end

def start_segment(name, id, payload)
segment = Tracer.start_segment(name: metric_name(name, payload))
push_segment(id, segment)
end

def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
end

def metric_name(name, payload)
mailbox = payload[:mailbox].class.name
method = method_from_name(name)
"Ruby/ActionMailbox/#{mailbox}/#{method}"
end

PATTERN = /\A([^\.]*)\.action_mailbox\z/
UNKNOWN = "unknown".freeze

METHOD_NAME_MAPPING = Hash.new do |h, k|
if PATTERN =~ k
h[k] = $1
else
h[k] = UNKNOWN
h[k] = NewRelic::UNKNOWN
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ class ActionMailerSubscriber < NotificationsSubscriber
BASE_NAME = 'Ruby/ActionMailer'
PAYLOAD_KEYS = %i[action data key mailer message_id perform_deliveries subject]
PATTERN = /\A([^\.]+)\.action_mailer\z/
UNKNOWN = 'unknown'
UNKNOWN_MAILER = %r{^#{BASE_NAME}/#{UNKNOWN}/}

METHOD_NAME_MAPPING = Hash.new do |h, k|
if PATTERN =~ k
h[k] = $1
else
h[k] = UNKNOWN
h[k] = NewRelic::UNKNOWN
end
end

Expand Down
14 changes: 7 additions & 7 deletions lib/new_relic/agent/instrumentation/action_view_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,25 @@ module NewRelic
module Agent
module Instrumentation
class ActionViewSubscriber < NotificationsSubscriber
def start(name, id, payload) # THREAD_LOCAL_ACCESS
def start_segment(name, id, payload)
parent = segment_stack[id].last
metric_name = format_metric_name(name, payload, parent)

event = ActionViewEvent.new(metric_name, payload[:identifier])
if state.is_execution_traced? && recordable?(name, metric_name)

if recordable?(name, metric_name)
event.finishable = Tracer.start_segment(name: metric_name)
end
push_segment(id, event)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
rescue => e
log_notification_error(e, name, 'finish')
end

def format_metric_name(event_name, payload, parent)
Expand Down Expand Up @@ -61,12 +58,15 @@ def recordable?(event_name, metric_name)
RENDER_TEMPLATE_EVENT_NAME = 'render_template.action_view'.freeze
RENDER_PARTIAL_EVENT_NAME = 'render_partial.action_view'.freeze
RENDER_COLLECTION_EVENT_NAME = 'render_collection.action_view'.freeze
RENDER_LAYOUT_EVENT_NAME = 'render_layout.action_view'.freeze

def metric_action(name)
case name
when /#{RENDER_TEMPLATE_EVENT_NAME}$/o then 'Rendering'
when RENDER_PARTIAL_EVENT_NAME then 'Partial'
when RENDER_COLLECTION_EVENT_NAME then 'Partial'
when RENDER_LAYOUT_EVENT_NAME then 'Layout'
else NewRelic::UNKNOWN
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,9 @@ module NewRelic
module Agent
module Instrumentation
class ActiveStorageSubscriber < NotificationsSubscriber
def start(name, id, payload)
return unless state.is_execution_traced?

start_segment(name, id, payload)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
return unless state.is_execution_traced?

finish_segment(id, payload)
rescue => e
log_notification_error(e, name, 'finish')
end

def start_segment(name, id, payload)
segment = Tracer.start_segment(name: metric_name(name, payload))
def add_segment_params(segment, payload)
segment.params[:key] = payload[:key]
segment.params[:exist] = payload[:exist] if payload.key?(:exist)
push_segment(id, segment)
end

def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
end

def metric_name(name, payload)
Expand All @@ -47,13 +20,12 @@ def metric_name(name, payload)
end

PATTERN = /\Aservice_([^\.]*)\.active_storage\z/
UNKNOWN = "unknown".freeze

METHOD_NAME_MAPPING = Hash.new do |h, k|
if PATTERN =~ k
h[k] = $1
else
h[k] = UNKNOWN
h[k] = NewRelic::UNKNOWN
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,6 @@ module NewRelic
module Agent
module Instrumentation
class ActiveSupportSubscriber < NotificationsSubscriber
def start(name, id, payload)
return unless state.is_execution_traced?

start_segment(name, id, payload)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
return unless state.is_execution_traced?

finish_segment(id, payload)
rescue => e
log_notification_error(e, name, 'finish')
end

def start_segment(name, id, payload)
segment = Tracer.start_segment(name: metric_name(name, payload))

add_segment_params(segment, payload)
push_segment(id, segment)
end

def add_segment_params(segment, payload)
segment.params[:key] = payload[:key]
segment.params[:store] = payload[:store]
Expand All @@ -39,29 +16,19 @@ def add_segment_params(segment, payload)
segment
end

def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
end

def metric_name(name, payload)
store = payload[:store]
method = method_from_name(name)
"Ruby/ActiveSupport#{"/#{store}" if store}/#{method}"
end

PATTERN = /\Acache_([^\.]*)\.active_support\z/
UNKNOWN = "unknown".freeze

METHOD_NAME_MAPPING = Hash.new do |h, k|
if PATTERN =~ k
h[k] = $1
else
h[k] = UNKNOWN
h[k] = NewRelic::UNKNOWN
end
end

Expand Down
41 changes: 41 additions & 0 deletions lib/new_relic/agent/instrumentation/notifications_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@ def self.subscribe(pattern)
end
end

def start(name, id, payload)
return unless state.is_execution_traced?

start_segment(name, id, payload)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
return unless state.is_execution_traced?

finish_segment(id, payload)
rescue => e
log_notification_error(e, name, 'finish')
end

def start_segment(name, id, payload)
segment = Tracer.start_segment(name: metric_name(name, payload))
add_segment_params(segment, payload)
push_segment(id, segment)
end

def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
end

# for subclasses
def add_segment_params(segment, payload)
# no op
end

# for subclasses
def metric_name(name, payload)
"Ruby/#{name}"
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great cleanup, thank you.


def log_notification_error(error, name, event_type)
# These are important enough failures that we want the backtraces
# logged at error level, hence the explicit log_exception call.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# frozen_string_literal: true

require 'new_relic/agent/instrumentation/action_controller_subscriber'
require 'new_relic/agent/instrumentation/action_controller_other_subscriber'
require 'new_relic/agent/prepend_supportability'

DependencyDetection.defer do
Expand All @@ -13,7 +14,8 @@
end

depends_on do
defined?(ActionController) && (defined?(ActionController::Base) || defined?(ActionController::API))
!NewRelic::Agent.config[:disable_action_controller] &&
defined?(ActionController) && (defined?(ActionController::Base) || defined?(ActionController::API))
end

executes do
Expand All @@ -29,5 +31,15 @@

NewRelic::Agent::Instrumentation::ActionControllerSubscriber \
.subscribe(/^process_action.action_controller$/)

subs = %w[send_file
send_data
redirect_to
halted_callback
unpermitted_parameters]

# have to double escape period because its going from string -> regex
NewRelic::Agent::Instrumentation::ActionControllerOtherSubscriber \
.subscribe(Regexp.new("^(#{subs.join('|')})\\.action_controller$"))
end
end
Loading