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

Add Roda instrumentation #2144

Merged
merged 32 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
283279b
Initial commit Roda instrumentation
hannahramadan Jul 25, 2023
64b6ac9
disable Lint/DuplicateMethods
hannahramadan Jul 25, 2023
31a1ee2
Address feedback
hannahramadan Jul 26, 2023
4612fe8
Remove uncessary alias
hannahramadan Jul 26, 2023
6ee5ef9
Refactor naming method
hannahramadan Aug 1, 2023
05cc30f
Roda test files
hannahramadan Aug 1, 2023
67cacf6
Add some tests
hannahramadan Aug 2, 2023
1852fb9
Add test
hannahramadan Aug 4, 2023
0926775
test: remove minitest from env
hannahramadan Aug 4, 2023
db7734d
debug txn
hannahramadan Aug 4, 2023
8fbbe4e
Ruby 2.4 fix
hannahramadan Aug 4, 2023
ca48d74
test: and another one
hannahramadan Aug 4, 2023
cc43194
test: middleware disabling
hannahramadan Aug 7, 2023
13ca306
test: middleware fix
hannahramadan Aug 7, 2023
b91579d
Merge branch 'dev' into roda_instrumentation
hannahramadan Aug 7, 2023
10d8b0e
Add CHANGELOG
hannahramadan Aug 7, 2023
6ec648c
Add tests
hannahramadan Aug 8, 2023
160a64b
Refactor
hannahramadan Aug 8, 2023
72010af
Update CHANGELOG.md
hannahramadan Aug 8, 2023
8a57394
Apply suggestions from code review
hannahramadan Aug 8, 2023
2834638
Update test/multiverse/suites/roda/roda_instrumentation_test.rb
hannahramadan Aug 8, 2023
545c126
Updates
hannahramadan Aug 8, 2023
57ca227
Apply suggestions from code review
hannahramadan Aug 8, 2023
c79d978
Code feedback
hannahramadan Aug 9, 2023
e4ca1e0
Update lib/new_relic/agent/instrumentation/roda/instrumentation.rb
hannahramadan Aug 9, 2023
8123d11
Don't stub any_instance
hannahramadan Aug 9, 2023
c08cf6b
Move require to top of file
hannahramadan Aug 9, 2023
9b89d72
Relocate rack/browser requires
hannahramadan Aug 9, 2023
ed60437
Refactor
hannahramadan Aug 9, 2023
8e90f4f
put requires back
hannahramadan Aug 9, 2023
6b1fcc1
remove from instrum class
hannahramadan Aug 9, 2023
138176b
Update lib/new_relic/agent/instrumentation/roda/roda_transaction_name…
hannahramadan Aug 9, 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## dev

Version <dev> of the agent introduces improved error tracking functionality by associating a transaction id with each error, and uses more reliable network timeout logic.
Version <dev> of the agent introduces improved error tracking functionality by associating a transaction id with each error, uses more reliable network timeout logic, and adds [Roda](https://roda.jeremyevans.net/) instrumentation.

- **Feature: Improved error tracking transaction linking**

Expand All @@ -12,6 +12,10 @@ Version <dev> of the agent introduces improved error tracking functionality by a

In line with current Ruby best practices, make use of Net::HTTP's own timeout logic and avoid the use of `Timeout.timeout()` when possible. The agent's data transmissions and cloud provider detection routines have been updated accordingly. [PR#2147](https://github.com/newrelic/newrelic-ruby-agent/pull/2147)

- **Feature: Add Roda instrumentation**

[Roda](https://roda.jeremyevans.net/) is a now an instrumented framework. The agent currently supports Roda versions 3.19.0+. [PR#2144](https://github.com/newrelic/newrelic-ruby-agent/pull/2144)

## v9.3.1

Version 9.3.1 of the agent fixes `NewRelic::Agent.require_test_helper`.
Expand Down
16 changes: 16 additions & 0 deletions lib/new_relic/agent/configuration/default_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def self.framework
:rails_notifications
end
when defined?(::Sinatra) && defined?(::Sinatra::Base) then :sinatra
when defined?(::Roda) then :roda
when defined?(::NewRelic::IA) then :external
else :ruby
end
Expand Down Expand Up @@ -1227,6 +1228,13 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:allowed_from_server => false,
:description => 'If `true`, disables [Sidekiq instrumentation](/docs/agents/ruby-agent/background-jobs/sidekiq-instrumentation).'
},
:disable_roda_auto_middleware => {
:default => false,
:public => true,
:type => Boolean,
:allowed_from_server => false,
:description => 'If `true`, disables agent middleware for Roda. This middleware is responsible for advanced feature support such as [page load timing](/docs/browser/new-relic-browser/getting-started/new-relic-browser) and [error collection](/docs/apm/applications-menu/events/view-apm-error-analytics).'
},
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved
:disable_sinatra_auto_middleware => {
:default => false,
:public => true,
Expand Down Expand Up @@ -1564,6 +1572,14 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:allowed_from_server => false,
:description => 'Controls auto-instrumentation of resque at start up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
},
:'instrumentation.roda' => {
:default => 'auto',
:public => true,
:type => String,
:dynamic_name => true,
:allowed_from_server => false,
:description => 'Controls auto-instrumentation of Roda at start up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
fallwith marked this conversation as resolved.
Show resolved Hide resolved
},
:'instrumentation.sinatra' => {
:default => 'auto',
:public => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ def self.prefix_for_category(txn, category = nil)
when :background then ::NewRelic::Agent::Transaction::TASK_PREFIX
when :rack then ::NewRelic::Agent::Transaction::RACK_PREFIX
when :uri then ::NewRelic::Agent::Transaction::CONTROLLER_PREFIX
when :roda then ::NewRelic::Agent::Transaction::RODA_PREFIX
when :sinatra then ::NewRelic::Agent::Transaction::SINATRA_PREFIX
when :middleware then ::NewRelic::Agent::Transaction::MIDDLEWARE_PREFIX
when :grape then ::NewRelic::Agent::Transaction::GRAPE_PREFIX
Expand Down
40 changes: 40 additions & 0 deletions lib/new_relic/agent/instrumentation/roda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 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 'roda/instrumentation'
require_relative 'roda/chain'
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
require_relative 'roda/prepend'

DependencyDetection.defer do
named :roda

depends_on do
defined?(Roda) &&
Gem::Version.new(Roda::RodaVersion) >= Gem::Version.new('3.19.0') &&
Roda::RodaPlugins::Base::ClassMethods.private_method_defined?(:build_rack_app) &&
Roda::RodaPlugins::Base::InstanceMethods.method_defined?(:_roda_handle_main_route)
end

executes do
# These requires are inside an executes block because they require rack, and
# we can't be sure that rack is available when this file is first required.
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
require 'new_relic/rack/agent_hooks'
require 'new_relic/rack/browser_monitoring'
if use_prepend?
prepend_instrument Roda.singleton_class, NewRelic::Agent::Instrumentation::Roda::Build::Prepend
else
chain_instrument NewRelic::Agent::Instrumentation::Roda::Build::Chain
end
end

executes do
NewRelic::Agent.logger.info('Installing Roda instrumentation')

if use_prepend?
prepend_instrument Roda, NewRelic::Agent::Instrumentation::Roda::Prepend
else
chain_instrument NewRelic::Agent::Instrumentation::Roda::Chain
end
end
end
43 changes: 43 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/chain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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 Chain
def self.instrument!
::Roda.class_eval do
include ::NewRelic::Agent::Instrumentation::Roda::Tracer

alias_method(:_roda_handle_main_route_without_tracing, :_roda_handle_main_route)

def _roda_handle_main_route(*args)
_roda_handle_main_route_with_tracing(*args) do
_roda_handle_main_route_without_tracing(*args)
end
end
end
end
end

module Build
module Chain
def self.instrument!
::Roda.class_eval do
include ::NewRelic::Agent::Instrumentation::Roda::Tracer

class << self
alias_method(:build_rack_app_without_tracing, :build_rack_app)

def build_rack_app
build_rack_app_with_tracing do
build_rack_app_without_tracing
end
end
end
end
end
end
end
end
end
54 changes: 54 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/instrumentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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 Tracer
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation

def self.included(clazz)
clazz.extend(self)
end

def newrelic_middlewares
middlewares = [NewRelic::Rack::BrowserMonitoring]
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved
if NewRelic::Rack::AgentHooks.needed?
middlewares << NewRelic::Rack::AgentHooks
end
middlewares
end

def build_rack_app_with_tracing
unless NewRelic::Agent.config[:disable_roda_auto_middleware]
newrelic_middlewares.each do |middleware_class|
self.use middleware_class
end
end
yield
end

# Roda makes use of Rack, so we can get params from the request object
def rack_request_params
begin
@_request.params
rescue => e
NewRelic::Agent.logger.debug('Failed to get params from Rack request.', e)
NewRelic::EMPTY_HASH
end
end

def _roda_handle_main_route_with_tracing(*args)
request_params = rack_request_params
filtered_params = ::NewRelic::Agent::ParameterFiltering::apply_filters(request.env, request_params)
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
name = TransactionNamer.transaction_name(request)

perform_action_with_newrelic_trace(:category => :roda,
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
:name => name,
:params => filtered_params) do
yield
end
end
end
end
end
24 changes: 24 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/prepend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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 Prepend
include ::NewRelic::Agent::Instrumentation::Roda::Tracer

def _roda_handle_main_route(*args)
_roda_handle_main_route_with_tracing(*args) { super }
end
end

module Build
module Prepend
include ::NewRelic::Agent::Instrumentation::Roda::Tracer
def build_rack_app
build_rack_app_with_tracing { super }
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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
module Agent
module Instrumentation
module Roda
module TransactionNamer
extend self

ROOT = '/'.freeze
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
REGEX_MUTIPLE_SLASHES = %r{^[/^\\A]*(.*?)[/\$\?\\z]*$}.freeze
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved

def transaction_name(request)
verb = http_verb(request)
path = request.path || ::NewRelic::Agent::UNKNOWN_METRIC
name = path.gsub(REGEX_MUTIPLE_SLASHES, '\1') # remove any rogue slashes
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
name = ROOT if name.empty?
name = "#{verb} #{name}" unless verb.nil?
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved

name
rescue => e
::NewRelic::Agent.logger.debug("#{e.class} : #{e.message} - Error encountered trying to identify Roda transaction name")
::NewRelic::Agent::UNKNOWN_METRIC
end

def http_verb(request)
request.request_method if request.respond_to?(:request_method)
end
end
end
end
end
end
3 changes: 2 additions & 1 deletion lib/new_relic/agent/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ class Transaction
RAKE_PREFIX = "#{OTHER_TRANSACTION_PREFIX}Rake/"
MESSAGE_PREFIX = "#{OTHER_TRANSACTION_PREFIX}Message/"
RACK_PREFIX = "#{CONTROLLER_PREFIX}Rack/"
RODA_PREFIX = "#{CONTROLLER_PREFIX}Roda/"
SINATRA_PREFIX = "#{CONTROLLER_PREFIX}Sinatra/"
GRAPE_PREFIX = "#{CONTROLLER_PREFIX}Grape/"
ACTION_CABLE_PREFIX = "#{CONTROLLER_PREFIX}ActionCable/"

WEB_TRANSACTION_CATEGORIES = [:web, :controller, :uri, :rack, :sinatra, :grape, :middleware, :action_cable].freeze
WEB_TRANSACTION_CATEGORIES = [:web, :controller, :uri, :rack, :sinatra, :grape, :middleware, :action_cable, :roda].freeze
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved

MIDDLEWARE_SUMMARY_METRICS = ['Middleware/all'].freeze
WEB_SUMMARY_METRIC = 'HttpDispatcher'
Expand Down
20 changes: 20 additions & 0 deletions lib/new_relic/control/frameworks/roda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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/control/frameworks/ruby'
module NewRelic
class Control
module Frameworks
# Contains basic control logic for Roda
class Roda < NewRelic::Control::Frameworks::Ruby
protected
fallwith marked this conversation as resolved.
Show resolved Hide resolved

def install_shim
super
::Roda.class_eval { include NewRelic::Agent::Instrumentation::ControllerInstrumentation::Shim }
end
end
end
end
end
2 changes: 1 addition & 1 deletion test/multiverse/lib/multiverse/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def execute_suites(filter, opts)
'background_2' => ['rake'],
'database' => %w[elasticsearch mongo redis sequel],
'rails' => %w[active_record active_record_pg rails rails_prepend activemerchant],
'frameworks' => %w[sinatra padrino grape],
'frameworks' => %w[sinatra padrino grape roda],
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
'httpclients' => %w[curb excon httpclient],
'httpclients_2' => %w[typhoeus net_http httprb],
'infinite_tracing' => ['infinite_tracing'],
Expand Down
20 changes: 20 additions & 0 deletions test/multiverse/suites/roda/Envfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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

RODA_VERSIONS = [
[nil, 2.4],
['3.19.0', 2.4]
]

def gem_list(roda_version = nil)
<<~RB
gem 'roda'#{roda_version}
gem 'rack', '~> 2.2'
hannahramadan marked this conversation as resolved.
Show resolved Hide resolved
gem 'rack-test', '>= 0.8.0', :require => 'rack/test'
RB
end

create_gemfiles(RODA_VERSIONS)
19 changes: 19 additions & 0 deletions test/multiverse/suites/roda/config/newrelic.yml
Original file line number Diff line number Diff line change
@@ -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:
roda: <%= $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
Loading