Skip to content

Commit b1d707f

Browse files
committed
Enable support for Regexp patterns when subscribing to Active Support's instrumentation Events
1 parent 45d0a54 commit b1d707f

File tree

2 files changed

+198
-85
lines changed

2 files changed

+198
-85
lines changed

instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/span_subscriber.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ class SpanSubscriber
6565

6666
# rubocop:disable Metrics/ParameterLists
6767
def initialize(name:, tracer:, notification_payload_transform: nil, disallowed_notification_payload_keys: nil, kind: nil, span_name_formatter: nil)
68-
@span_name = safe_span_name_for(span_name_formatter, name).dup.freeze
68+
@name = name
6969
@tracer = tracer
7070
@notification_payload_transform = notification_payload_transform
7171
@disallowed_notification_payload_keys = Array(disallowed_notification_payload_keys)
7272
@kind = kind || :internal
73+
@span_name_formatter = span_name_formatter
7374
end
7475
# rubocop:enable Metrics/ParameterLists
7576

7677
def start(name, id, payload)
77-
span = @tracer.start_span(@span_name, kind: @kind)
78+
span = @tracer.start_span(span_name(name).dup.freeze, kind: @kind)
7879
token = OpenTelemetry::Context.attach(
7980
OpenTelemetry::Trace.context_with_span(span)
8081
)
@@ -137,11 +138,20 @@ def sanitized_value(value)
137138
end
138139
end
139140

141+
def span_name(name)
142+
case @name
143+
when Regexp
144+
safe_span_name_for(name)
145+
else
146+
@span_name ||= safe_span_name_for(@name)
147+
end
148+
end
149+
140150
# Helper method to try an shield the span name formatter from errors
141151
#
142152
# It wraps the user supplied formatter in a rescue block and returns the original name if a StandardError is raised by the formatter
143-
def safe_span_name_for(span_name_formatter, name)
144-
span_name_formatter&.call(name) || name
153+
def safe_span_name_for(name)
154+
@span_name_formatter&.call(name) || name
145155
rescue StandardError => e
146156
OpenTelemetry.handle_error(exception: e, message: 'Error calling span_name_formatter. Using default span name.')
147157
name

instrumentation/active_support/test/opentelemetry/instrumentation/active_support/span_subscriber_test.rb

Lines changed: 184 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -205,123 +205,226 @@ def finish(name, id, payload)
205205
end
206206

207207
describe 'instrument' do
208-
before do
209-
ActiveSupport::Notifications.unsubscribe(notification_name)
208+
after do
209+
ActiveSupport::Notifications.notifier.all_listeners_for(notification_name).each do |listener|
210+
ActiveSupport::Notifications.unsubscribe(listener)
211+
end
210212
end
211213

212-
it 'does not trace an event by default' do
213-
ActiveSupport::Notifications.subscribe(notification_name) do
214-
# pass
214+
describe 'when subscribing to events directly' do
215+
it 'does not trace an event by default' do
216+
ActiveSupport::Notifications.subscribe(notification_name) do
217+
# pass
218+
end
219+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
220+
_(last_span).must_be_nil
215221
end
216-
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
217-
_(last_span).must_be_nil
218-
end
219222

220-
it 'traces an event when a span subscriber is used' do
221-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
222-
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
223+
it 'traces an event when a span subscriber is used' do
224+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
225+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
223226

224-
_(last_span).wont_be_nil
225-
_(last_span.name).must_equal(notification_name)
226-
_(last_span.attributes['extra']).must_equal('context')
227-
_(last_span.kind).must_equal(:internal)
228-
end
227+
_(last_span).wont_be_nil
228+
_(last_span.name).must_equal(notification_name)
229+
_(last_span.attributes['extra']).must_equal('context')
230+
_(last_span.kind).must_equal(:internal)
231+
end
229232

230-
describe 'when using a custom span name formatter' do
231-
describe 'when using the LEGACY_NAME_FORMATTER' do
232-
let(:span_name_formatter) { OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER }
233-
it 'uses the user supplied formatter' do
234-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter)
235-
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
233+
describe 'when using a custom span name formatter' do
234+
describe 'when using the LEGACY_NAME_FORMATTER' do
235+
let(:span_name_formatter) { OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER }
236+
it 'uses the user supplied formatter' do
237+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter)
238+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
239+
240+
_(last_span).wont_be_nil
241+
_(last_span.name).must_equal('foo bar')
242+
_(last_span.attributes['extra']).must_equal('context')
243+
end
244+
end
236245

237-
_(last_span).wont_be_nil
238-
_(last_span.name).must_equal('foo bar')
239-
_(last_span.attributes['extra']).must_equal('context')
246+
describe 'when using a custom formatter' do
247+
let(:span_name_formatter) { ->(name) { "custom.#{name}" } }
248+
249+
it 'uses the user supplied formatter' do
250+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter)
251+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
252+
253+
_(last_span).wont_be_nil
254+
_(last_span.name).must_equal('custom.bar.foo')
255+
_(last_span.attributes['extra']).must_equal('context')
256+
end
240257
end
241-
end
242258

243-
describe 'when using a custom formatter' do
244-
let(:span_name_formatter) { ->(name) { "custom.#{name}" } }
259+
describe 'when using a invalid formatter' do
260+
it 'defaults to the notification name' do
261+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) {})
262+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
245263

246-
it 'uses the user supplied formatter' do
247-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter)
248-
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
264+
_(last_span).wont_be_nil
265+
_(last_span.name).must_equal(notification_name)
266+
_(last_span.attributes['extra']).must_equal('context')
267+
end
268+
end
269+
270+
describe 'when using a unstable formatter' do
271+
it 'defaults to the notification name' do
272+
allow(OpenTelemetry).to receive(:handle_error).with(exception: RuntimeError, message: String)
273+
274+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) { raise 'boom' })
275+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
249276

250-
_(last_span).wont_be_nil
251-
_(last_span.name).must_equal('custom.bar.foo')
252-
_(last_span.attributes['extra']).must_equal('context')
277+
_(last_span).wont_be_nil
278+
_(last_span.name).must_equal(notification_name)
279+
_(last_span.attributes['extra']).must_equal('context')
280+
end
253281
end
254282
end
255283

256-
describe 'when using a invalid formatter' do
257-
it 'defaults to the notification name' do
258-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) {})
284+
it 'finishes spans even when block subscribers blow up' do
285+
ActiveSupport::Notifications.subscribe(notification_name) { raise 'boom' }
286+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
287+
288+
expect do
259289
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
290+
end.must_raise RuntimeError
260291

261-
_(last_span).wont_be_nil
262-
_(last_span.name).must_equal(notification_name)
263-
_(last_span.attributes['extra']).must_equal('context')
264-
end
292+
_(last_span).wont_be_nil
293+
_(last_span.name).must_equal(notification_name)
294+
_(last_span.attributes['extra']).must_equal('context')
265295
end
266296

267-
describe 'when using a unstable formatter' do
268-
it 'defaults to the notification name' do
269-
allow(OpenTelemetry).to receive(:handle_error).with(exception: RuntimeError, message: String)
297+
it 'finishes spans even when complex subscribers blow up' do
298+
ActiveSupport::Notifications.subscribe(notification_name, CrashingEndSubscriber.new)
299+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
270300

271-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) { raise 'boom' })
301+
expect do
272302
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
303+
end.must_raise RuntimeError
273304

274-
_(last_span).wont_be_nil
275-
_(last_span.name).must_equal(notification_name)
276-
_(last_span.attributes['extra']).must_equal('context')
277-
end
305+
_(last_span).wont_be_nil
306+
_(last_span.name).must_equal(notification_name)
307+
_(last_span.attributes['extra']).must_equal('context')
278308
end
279-
end
280309

281-
it 'finishes spans even when block subscribers blow up' do
282-
ActiveSupport::Notifications.subscribe(notification_name) { raise 'boom' }
283-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
310+
it 'supports unsubscribe' do
311+
obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
312+
ActiveSupport::Notifications.unsubscribe(obj)
284313

285-
expect do
286314
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
287-
end.must_raise RuntimeError
288315

289-
_(last_span).wont_be_nil
290-
_(last_span.name).must_equal(notification_name)
291-
_(last_span.attributes['extra']).must_equal('context')
316+
_(obj.class).must_equal(ActiveSupport::Notifications::Fanout::Subscribers::Evented)
317+
_(last_span).must_be_nil
318+
end
319+
320+
it 'supports setting the span kind' do
321+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo', nil, [], kind: :client)
322+
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
323+
324+
_(last_span).wont_be_nil
325+
_(last_span.name).must_equal('bar.foo')
326+
_(last_span.attributes['extra']).must_equal('context')
327+
_(last_span.kind).must_equal(:client)
328+
end
292329
end
293330

294-
it 'finishes spans even when complex subscribers blow up' do
295-
ActiveSupport::Notifications.subscribe(notification_name, CrashingEndSubscriber.new)
296-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
331+
describe 'when subscribing to events matching a regular expression' do
332+
let(:notification_pattern) { /.*\.foo/ }
297333

298-
expect do
334+
it 'does not trace an event by default' do
335+
ActiveSupport::Notifications.subscribe(notification_pattern) do
336+
# pass
337+
end
299338
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
300-
end.must_raise RuntimeError
339+
_(last_span).must_be_nil
340+
end
301341

302-
_(last_span).wont_be_nil
303-
_(last_span.name).must_equal(notification_name)
304-
_(last_span.attributes['extra']).must_equal('context')
305-
end
342+
it 'traces an event when a span subscriber is used' do
343+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern)
344+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
306345

307-
it 'supports unsubscribe' do
308-
obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
309-
ActiveSupport::Notifications.unsubscribe(obj)
346+
_(last_span).wont_be_nil
347+
_(last_span.name).must_equal(notification_name)
348+
_(last_span.attributes['extra']).must_equal('context')
349+
_(last_span.kind).must_equal(:internal)
350+
end
310351

311-
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
352+
describe 'when using a custom span name formatter' do
353+
describe 'when using the LEGACY_NAME_FORMATTER' do
354+
let(:span_name_formatter) { OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER }
355+
it 'uses the user supplied formatter' do
356+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern, nil, nil, span_name_formatter: span_name_formatter)
357+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
358+
359+
_(last_span).wont_be_nil
360+
_(last_span.name).must_equal('foo bar')
361+
_(last_span.attributes['extra']).must_equal('context')
362+
end
363+
end
312364

313-
_(obj.class).must_equal(ActiveSupport::Notifications::Fanout::Subscribers::Evented)
314-
_(last_span).must_be_nil
315-
end
365+
describe 'when using a custom formatter' do
366+
let(:span_name_formatter) { ->(name) { "custom.#{name}" } }
316367

317-
it 'supports setting the span kind' do
318-
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo', nil, [], kind: :client)
319-
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
368+
it 'uses the user supplied formatter' do
369+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern, nil, nil, span_name_formatter: span_name_formatter)
370+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
320371

321-
_(last_span).wont_be_nil
322-
_(last_span.name).must_equal('bar.foo')
323-
_(last_span.attributes['extra']).must_equal('context')
324-
_(last_span.kind).must_equal(:client)
372+
_(last_span).wont_be_nil
373+
_(last_span.name).must_equal('custom.bar.foo')
374+
_(last_span.attributes['extra']).must_equal('context')
375+
end
376+
end
377+
378+
describe 'when using a invalid formatter' do
379+
it 'defaults to the notification name' do
380+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern, nil, nil, span_name_formatter: ->(_) {})
381+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
382+
383+
_(last_span).wont_be_nil
384+
_(last_span.name).must_equal(notification_name)
385+
_(last_span.attributes['extra']).must_equal('context')
386+
end
387+
end
388+
389+
describe 'when using a unstable formatter' do
390+
it 'defaults to the notification name' do
391+
allow(OpenTelemetry).to receive(:handle_error).with(exception: RuntimeError, message: String)
392+
393+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern, nil, nil, span_name_formatter: ->(_) { raise 'boom' })
394+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
395+
396+
_(last_span).wont_be_nil
397+
_(last_span.name).must_equal(notification_name)
398+
_(last_span.attributes['extra']).must_equal('context')
399+
end
400+
end
401+
end
402+
403+
it 'finishes spans even when block subscribers blow up' do
404+
ActiveSupport::Notifications.subscribe(notification_pattern) { raise 'boom' }
405+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern)
406+
407+
expect do
408+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
409+
end.must_raise RuntimeError
410+
411+
_(last_span).wont_be_nil
412+
_(last_span.name).must_equal(notification_name)
413+
_(last_span.attributes['extra']).must_equal('context')
414+
end
415+
416+
it 'finishes spans even when complex subscribers blow up' do
417+
ActiveSupport::Notifications.subscribe(notification_pattern, CrashingEndSubscriber.new)
418+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_pattern)
419+
420+
expect do
421+
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
422+
end.must_raise RuntimeError
423+
424+
_(last_span).wont_be_nil
425+
_(last_span.name).must_equal(notification_name)
426+
_(last_span.attributes['extra']).must_equal('context')
427+
end
325428
end
326429
end
327430
end

0 commit comments

Comments
 (0)