Skip to content

Commit

Permalink
feat!: Custom ActiveSupport Span Names
Browse files Browse the repository at this point in the history
The current implementation of ActiveSupport instrumentation sets the span name to the reverse tokenized name,
e.g. `render_template.action_view` is converted to `action_view render_template`

This default behavior can sometimes seem counter intuitive for users who use ActiveSupport Notifications to instrument their own code
or users who are familiar with Rails instrumentation names.

This change does a few things to address the issues listed above:

1. Uses the notification name by default as oppossed to the legacy span name
2. Allows users to provide a custom span name formatter lambda
3. Provides a proc with backward compatible span name formatter `OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER`

See open-telemetry#957
  • Loading branch information
arielvalentin committed Jun 16, 2024
1 parent 2230403 commit 9bd4d99
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 20 deletions.
1 change: 1 addition & 0 deletions instrumentation/active_support/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# OpenTelemetry ActiveSupport Instrumentation

The Active Support instrumentation is a community-maintained instrumentation for the Active Support portion of the [Ruby on Rails][rails-home] web-application framework.

## How do I get started?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module OpenTelemetry
module Instrumentation
# rubocop:disable Style/Documentation
module ActiveSupport
LEGACY_NAME_FORMATTER = ->(name) { name.split('.')[0..1].reverse.join(' ').freeze }

# The SpanSubscriber is a special ActiveSupport::Notification subscription
# handler which turns notifications into generic spans, taking care to handle
# context appropriately.
Expand All @@ -24,6 +26,7 @@ def self.subscribe(
subscriber = OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new(
name: pattern,
tracer: tracer,
formatter: LEGACY_NAME_FORMATTER,
notification_payload_transform: notification_payload_transform,
disallowed_notification_payload_keys: disallowed_notification_payload_keys
)
Expand Down Expand Up @@ -54,9 +57,8 @@ def self.subscribe(

class SpanSubscriber
ALWAYS_VALID_PAYLOAD_TYPES = [TrueClass, FalseClass, String, Numeric, Symbol].freeze

def initialize(name:, tracer:, notification_payload_transform: nil, disallowed_notification_payload_keys: [])
@span_name = name.split('.')[0..1].reverse.join(' ').freeze
def initialize(name:, tracer:, formatter: nil, notification_payload_transform: nil, disallowed_notification_payload_keys: [])
@span_name = formatter&.call(name) || name
@tracer = tracer
@notification_payload_transform = notification_payload_transform
@disallowed_notification_payload_keys = disallowed_notification_payload_keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
let(:tracer) { instrumentation.tracer }
let(:exporter) { EXPORTER }
let(:last_span) { exporter.finished_spans.last }
let(:notification_name) { 'bar.foo' }
let(:subscriber) do
OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new(
name: 'bar.foo',
name: notification_name,
tracer: tracer
)
end
Expand All @@ -34,7 +35,7 @@ def finish(name, id, payload)

it 'memoizes the span name' do
span, = subscriber.start('oh.hai', 'abc', {})
_(span.name).must_equal('foo bar')
_(span.name).must_equal(notification_name)
end

it 'uses the provided tracer' do
Expand Down Expand Up @@ -112,7 +113,7 @@ def finish(name, id, payload)
describe 'instrumentation option - disallowed_notification_payload_keys' do
let(:subscriber) do
OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new(
name: 'bar.foo',
name: notification_name,
tracer: tracer,
notification_payload_transform: nil,
disallowed_notification_payload_keys: [:foo]
Expand Down Expand Up @@ -150,7 +151,7 @@ def finish(name, id, payload)
let(:transformer_proc) { ->(v) { v.transform_values { 'optimus prime' } } }
let(:subscriber) do
OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new(
name: 'bar.foo',
name: notification_name,
tracer: tracer,
notification_payload_transform: transformer_proc,
disallowed_notification_payload_keys: [:foo]
Expand Down Expand Up @@ -184,32 +185,32 @@ def finish(name, id, payload)

describe 'instrument' do
before do
ActiveSupport::Notifications.unsubscribe('bar.foo')
ActiveSupport::Notifications.unsubscribe(notification_name)
end

it 'does not trace an event by default' do
ActiveSupport::Notifications.subscribe('bar.foo') do
ActiveSupport::Notifications.subscribe(notification_name) do
# pass
end
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
_(last_span).must_be_nil
end

it 'traces an event when a span subscriber is used' do
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo')
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')

_(last_span).wont_be_nil
_(last_span.name).must_equal('foo bar')
_(last_span.attributes['extra']).must_equal('context')
end

it 'finishes spans even when block subscribers blow up' do
ActiveSupport::Notifications.subscribe('bar.foo') { raise 'boom' }
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo')
ActiveSupport::Notifications.subscribe(notification_name) { raise 'boom' }
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)

expect do
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
end.must_raise RuntimeError

_(last_span).wont_be_nil
Expand All @@ -218,11 +219,11 @@ def finish(name, id, payload)
end

it 'finishes spans even when complex subscribers blow up' do
ActiveSupport::Notifications.subscribe('bar.foo', CrashingEndSubscriber.new)
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo')
ActiveSupport::Notifications.subscribe(notification_name, CrashingEndSubscriber.new)
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)

expect do
ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')
end.must_raise RuntimeError

_(last_span).wont_be_nil
Expand All @@ -231,10 +232,10 @@ def finish(name, id, payload)
end

it 'supports unsubscribe' do
obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo')
obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name)
ActiveSupport::Notifications.unsubscribe(obj)

ActiveSupport::Notifications.instrument('bar.foo', extra: 'context')
ActiveSupport::Notifications.instrument(notification_name, extra: 'context')

_(obj.class).must_equal(ActiveSupport::Notifications::Fanout::Subscribers::Evented)
_(last_span).must_be_nil
Expand Down

0 comments on commit 9bd4d99

Please sign in to comment.