Skip to content

Commit 08fad6e

Browse files
feat: Make Rack install optional for sinatra (#1019)
* feat: Make Rack install optional for sinatra This allows for scenarios when there are multiple Rack Applications mounted in the same builder and you want to avoid installing the Rack Events middleware multiple times in the stack. A common example is a Rails application that mounts a Sinatra App in the routes file: ```ruby Rails.application.routes.draw do mount Sinatra::Application, at: '/sinatra' end ``` This results in the Rack middleware being installed multiple times. Once by the Rails ActionPack instrumentation and then by the Sinatra application leading to multiple Rack spans being created in the same trace. This change allows you to avoid this by setting the install_rack option to false when using Sinatra instrumentation and allowing users to manually configure the middleware in the stack as they see fit. * very strange * squash: will this work * squash: wtf * squash: fix tests * squash: Add docs PR feedback * squash: add example * squash: add more test * Update instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb Co-authored-by: Kayla Reopelle (she/her) <[email protected]> * Update instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb Co-authored-by: Kayla Reopelle (she/her) <[email protected]> --------- Co-authored-by: Kayla Reopelle (she/her) <[email protected]>
1 parent 52b4194 commit 08fad6e

File tree

6 files changed

+162
-20
lines changed

6 files changed

+162
-20
lines changed

instrumentation/sinatra/Appraisals

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ end
1515
appraise 'sinatra-2.x' do
1616
gem 'sinatra', '~> 2.1'
1717
end
18+
19+
appraise 'sinatra-latest' do
20+
gem 'sinatra'
21+
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env ruby
2+
3+
# frozen_string_literal: true
4+
5+
# Copyright The OpenTelemetry Authors
6+
#
7+
# SPDX-License-Identifier: Apache-2.0
8+
9+
# Example rack application that manually manages tracer middleware
10+
11+
require 'rubygems'
12+
require 'bundler/setup'
13+
14+
Bundler.require
15+
16+
ENV['OTEL_TRACES_EXPORTER'] = 'console'
17+
OpenTelemetry::SDK.configure do |c|
18+
c.use_all({
19+
'OpenTelemetry::Instrumentation::Rack' => { },
20+
'OpenTelemetry::Instrumentation::Sinatra' => { install_rack: false }
21+
})
22+
end
23+
24+
# Example application for the Sinatra instrumentation
25+
class App < Sinatra::Base
26+
set :show_exceptions, false
27+
28+
template :example_render do
29+
'Example Render'
30+
end
31+
32+
get '/example' do
33+
'Sinatra Instrumentation Example'
34+
end
35+
36+
# Uses `render` method
37+
get '/example_render' do
38+
erb :example_render
39+
end
40+
41+
get '/thing/:id' do
42+
'Thing 1'
43+
end
44+
45+
get '/error' do
46+
raise 'Panic!'
47+
end
48+
end
49+
50+
use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args)
51+
run App

instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
module OpenTelemetry
1111
module Instrumentation
12-
# Contains the OpenTelemetry instrumentation for the Sinatra gem
12+
# (see OpenTelemetry::Instrumentation::Sinatra::Instrumentation)
1313
module Sinatra
1414
end
1515
end

instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/extensions/tracer_extension.rb

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,25 @@ module Extensions
1313
# Sinatra extension that installs TracerMiddleware and provides
1414
# tracing for template rendering
1515
module TracerExtension
16-
# Sinatra hook after extension is registered
17-
def self.registered(app)
18-
# Create tracing `render` method
19-
::Sinatra::Base.module_eval do
20-
def render(_engine, data, *)
21-
template_name = data.is_a?(Symbol) ? data : :literal
16+
# Contants patches for `render` method
17+
module RenderPatches
18+
def render(_engine, data, *)
19+
template_name = data.is_a?(Symbol) ? data : :literal
2220

23-
Sinatra::Instrumentation.instance.tracer.in_span(
24-
'sinatra.render_template',
25-
attributes: { 'sinatra.template_name' => template_name.to_s }
26-
) do
27-
super
28-
end
21+
Sinatra::Instrumentation.instance.tracer.in_span(
22+
'sinatra.render_template',
23+
attributes: { 'sinatra.template_name' => template_name.to_s }
24+
) do
25+
super
2926
end
3027
end
31-
app.use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args)
32-
app.use(Middlewares::TracerMiddleware)
28+
end
29+
30+
# Sinatra hook after extension is registered
31+
def self.registered(app)
32+
# Create tracing `render` method
33+
::Sinatra::Base.prepend(RenderPatches)
34+
Sinatra::Instrumentation.instance.install_middleware(app)
3335
end
3436
end
3537
end

instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,51 @@
99
module OpenTelemetry
1010
module Instrumentation
1111
module Sinatra
12-
# The Instrumentation class contains logic to detect and install the Sinatra
13-
# instrumentation
12+
# The {OpenTelemetry::Instrumentation::Sinatra::Instrumentation} class contains logic to detect and install the Sinatra instrumentation
13+
#
14+
# Installation and configuration of this instrumentation is done within the
15+
# {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry/SDK#configure-instance_method OpenTelemetry::SDK#configure}
16+
# block, calling {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use use()}
17+
# or {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use_all use_all()}.
18+
#
19+
# ## Configuration keys and options
20+
#
21+
# ### `:install_rack`
22+
#
23+
# Default is `true`. Specifies whether or not to install the Rack instrumentation as part of installing the Sinatra instrumentation.
24+
# This is useful in cases where you have multiple Rack applications but want to manually specify where to insert the tracing middleware.
25+
#
26+
# @example Manually install Rack instrumentation.
27+
# OpenTelemetry::SDK.configure do |c|
28+
# c.use_all({
29+
# 'OpenTelemetry::Instrumentation::Rack' => { },
30+
# 'OpenTelemetry::Instrumentation::Sinatra' => {
31+
# install_rack: false
32+
# },
33+
# })
34+
# end
35+
#
1436
class Instrumentation < OpenTelemetry::Instrumentation::Base
15-
install do |_|
16-
OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({})
37+
install do |config|
38+
OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) if config[:install_rack]
39+
40+
unless OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.installed?
41+
OpenTelemetry.logger.warn('Rack instrumentation is required for Sinatra but not installed. Please see the docs for more details: https://opentelemetry.io/docs/languages/ruby/libraries/')
42+
end
1743

1844
::Sinatra::Base.register Extensions::TracerExtension
1945
end
2046

47+
option :install_rack, default: true, validate: :boolean
48+
2149
present do
2250
defined?(::Sinatra)
2351
end
52+
53+
def install_middleware(app)
54+
app.use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args) if config[:install_rack]
55+
app.use(Middlewares::TracerMiddleware)
56+
end
2457
end
2558
end
2659
end

instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
let(:instrumentation) { OpenTelemetry::Instrumentation::Sinatra::Instrumentation.instance }
1313
let(:exporter) { EXPORTER }
14+
let(:config) { {} }
1415

1516
class CustomError < StandardError; end
1617

@@ -66,7 +67,14 @@ class CustomError < StandardError; end
6667
end
6768

6869
before do
69-
instrumentation.install
70+
Sinatra::Base.reset!
71+
72+
OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.instance_variable_set(:@installed, false)
73+
OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.config.clear
74+
75+
instrumentation.instance_variable_set(:@installed, false)
76+
instrumentation.config.clear
77+
instrumentation.install(config)
7078
exporter.reset
7179
end
7280

@@ -169,4 +177,48 @@ class CustomError < StandardError; end
169177
_(exporter.finished_spans.first.events[0].attributes['exception.message']).must_equal('custom message')
170178
end
171179
end
180+
181+
describe 'when install_rack is set to false' do
182+
let(:config) { { install_rack: false } }
183+
184+
describe 'missing rack installation' do
185+
it 'disables tracing' do
186+
get '/one/endpoint'
187+
188+
_(exporter.finished_spans).must_be_empty
189+
end
190+
end
191+
192+
describe 'when rack is manually installed' do
193+
let(:app) do
194+
apps_to_build = apps
195+
Rack::Builder.new do
196+
use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args)
197+
198+
apps_to_build.each do |root, app|
199+
map root do
200+
run app
201+
end
202+
end
203+
end.to_app
204+
end
205+
206+
before do
207+
OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install
208+
end
209+
210+
it 'creates a span' do
211+
get '/one/endpoint'
212+
213+
_(exporter.finished_spans.first.attributes).must_equal(
214+
'http.method' => 'GET',
215+
'http.host' => 'example.org',
216+
'http.scheme' => 'http',
217+
'http.target' => '/one/endpoint',
218+
'http.route' => '/endpoint',
219+
'http.status_code' => 200
220+
)
221+
end
222+
end
223+
end
172224
end

0 commit comments

Comments
 (0)