Skip to content

Commit

Permalink
Merge pull request #2144 from newrelic/roda_instrumentation
Browse files Browse the repository at this point in the history
Add Roda instrumentation
  • Loading branch information
hannahramadan authored Aug 10, 2023
2 parents bedd758 + 138176b commit b6acbfd
Show file tree
Hide file tree
Showing 17 changed files with 465 additions and 3 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

## 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 adds [Roda](https://roda.jeremyevans.net/) instrumentation, introduces improved error tracking functionality by associating a transaction id with each error, and uses more reliable network timeout logic.

- **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)

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

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).'
},
: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`.'
},
:'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
33 changes: 33 additions & 0 deletions lib/new_relic/agent/instrumentation/roda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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'

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
require_relative '../../rack/agent_hooks'
require_relative '../../rack/browser_monitoring'

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

if use_prepend?
require_relative 'roda/prepend'
prepend_instrument Roda.singleton_class, NewRelic::Agent::Instrumentation::Roda::Build::Prepend
prepend_instrument Roda, NewRelic::Agent::Instrumentation::Roda::Prepend
else
require_relative 'roda/chain'
chain_instrument NewRelic::Agent::Instrumentation::Roda::Build::Chain
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
52 changes: 52 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/instrumentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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]
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)
perform_action_with_newrelic_trace(
category: :roda,
name: TransactionNamer.transaction_name(request),
params: ::NewRelic::Agent::ParameterFiltering::apply_filters(request.env, rack_request_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
30 changes: 30 additions & 0 deletions lib/new_relic/agent/instrumentation/roda/roda_transaction_namer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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
REGEX_MULTIPLE_SLASHES = %r{^[/^\A]*(.*?)[/$?\z]*$}.freeze

def transaction_name(request)
path = request.path || ::NewRelic::Agent::UNKNOWN_METRIC
name = path.gsub(REGEX_MULTIPLE_SLASHES, '\1') # remove any rogue slashes
name = ROOT if name.empty?
name = "#{request.request_method} #{name}" if request.respond_to?(:request_method)

name
rescue => e
::NewRelic::Agent.logger.debug("#{e.class} : #{e.message} - Error encountered trying to identify Roda transaction name")
::NewRelic::Agent::UNKNOWN_METRIC
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 = %i[action_cable controller grape middleware rack roda sinatra web uri].freeze

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

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[grape padrino roda sinatra],
'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'
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

0 comments on commit b6acbfd

Please sign in to comment.