Skip to content

Commit 92d59eb

Browse files
robbkiddkaylareopellearielvalentin
authored
fix: use AS::N subscriber for serialize events (#1075)
* fix: use AS::N subscriber for serialize events The details for Context management (i.e. setting current span) are already handled by the OTel ActiveSupport instrumentation. Reuse the notifications subscriber here for ActiveModel serialization events. Reworked the example app into two: one Rails which works with the usual SDK configuration and one standalone (no Rails) to demonstrate that the subscription needs to be made after the SDK configuration is complete. If the subscription is created during instrumentation install, the subscription's tracer will be a NO-OP API tracer and won't produce spans. * update comments to reference Rails components consistently Co-authored-by: Kayla Reopelle <[email protected]> * default to console exporter ... but leave OTLP exporter gem in dependencies so that a curious person can override without code changes to send to some OTLP receiver by setting the appropriate environment variables. * differentiate example app output from trace console output * fixup! default to console exporter * fixup! differentiate example app output from trace console output --------- Co-authored-by: Kayla Reopelle <[email protected]> Co-authored-by: Ariel Valentin <[email protected]>
1 parent 2d07298 commit 92d59eb

File tree

10 files changed

+199
-98
lines changed

10 files changed

+199
-98
lines changed

instrumentation/active_model_serializers/example/Gemfile

Lines changed: 0 additions & 9 deletions
This file was deleted.

instrumentation/active_model_serializers/example/active_model_serializers.rb

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
require 'bundler/inline'
8+
9+
gemfile(true) do
10+
source 'https://rubygems.org'
11+
12+
gem 'rails'
13+
gem 'active_model_serializers'
14+
gem 'opentelemetry-api'
15+
gem 'opentelemetry-common'
16+
gem 'opentelemetry-instrumentation-active_model_serializers', path: '../'
17+
gem 'opentelemetry-sdk'
18+
gem 'opentelemetry-exporter-otlp'
19+
end
20+
21+
ENV['OTEL_TRACES_EXPORTER'] ||= 'console'
22+
OpenTelemetry::SDK.configure do |c|
23+
c.service_name = 'active_model_serializers_example'
24+
c.use 'OpenTelemetry::Instrumentation::ActiveModelSerializers'
25+
end
26+
27+
# no manual subscription trigger
28+
29+
at_exit do
30+
OpenTelemetry.tracer_provider.shutdown
31+
end
32+
33+
# TraceRequestApp is a minimal Rails application inspired by the Rails
34+
# bug report template for Action Controller.
35+
# The configuration is compatible with Rails 6.0
36+
class TraceRequestApp < Rails::Application
37+
config.root = __dir__
38+
config.hosts << 'example.org'
39+
credentials.secret_key_base = 'secret_key_base'
40+
41+
config.eager_load = false
42+
43+
config.logger = Logger.new($stdout)
44+
Rails.logger = config.logger
45+
end
46+
47+
# Rails app initialization will pick up the instrumentation Railtie
48+
# and subscribe to Active Support notifications
49+
TraceRequestApp.initialize!
50+
51+
ExampleAppTracer = OpenTelemetry.tracer_provider.tracer('example_app')
52+
53+
class TestModel
54+
include ActiveModel::API
55+
include ActiveModel::Serialization
56+
57+
attr_accessor :name
58+
59+
def attributes
60+
{ 'name' => nil,
61+
'screaming_name' => nil }
62+
end
63+
64+
def screaming_name
65+
ExampleAppTracer.in_span('screaming_name transform') do |span|
66+
name.upcase
67+
end
68+
end
69+
end
70+
71+
model = TestModel.new(name: 'test object')
72+
serialized_model = ActiveModelSerializers::SerializableResource.new(model).serializable_hash
73+
74+
puts "\n*** The serialized object: #{serialized_model}"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
require 'bundler/inline'
8+
9+
gemfile(true) do
10+
source 'https://rubygems.org'
11+
12+
gem 'active_model_serializers'
13+
gem 'opentelemetry-api'
14+
gem 'opentelemetry-common'
15+
gem 'opentelemetry-instrumentation-active_model_serializers', path: '../'
16+
gem 'opentelemetry-sdk'
17+
gem 'opentelemetry-exporter-otlp'
18+
end
19+
20+
ENV['OTEL_TRACES_EXPORTER'] ||= 'console'
21+
OpenTelemetry::SDK.configure do |c|
22+
c.service_name = 'active_model_serializers_example'
23+
c.use_all
24+
end
25+
26+
# without Rails and the Railtie automation, must manually trigger
27+
# instrumentation subscription after SDK is configured
28+
OpenTelemetry::Instrumentation::ActiveModelSerializers.subscribe
29+
30+
at_exit do
31+
OpenTelemetry.tracer_provider.shutdown
32+
end
33+
34+
ExampleAppTracer = OpenTelemetry.tracer_provider.tracer('example_app')
35+
36+
class TestModel
37+
include ActiveModel::API
38+
include ActiveModel::Serialization
39+
40+
attr_accessor :name
41+
42+
def attributes
43+
{ 'name' => nil,
44+
'screaming_name' => nil }
45+
end
46+
47+
def screaming_name
48+
ExampleAppTracer.in_span('screaming_name transform') do |span|
49+
name.upcase
50+
end
51+
end
52+
end
53+
54+
model = TestModel.new(name: 'test object')
55+
serialized_model = ActiveModelSerializers::SerializableResource.new(model).serializable_hash
56+
57+
puts "\n*** The serialized object: #{serialized_model}"

instrumentation/active_model_serializers/lib/opentelemetry/instrumentation/active_model_serializers/event_handler.rb

Lines changed: 0 additions & 48 deletions
This file was deleted.

instrumentation/active_model_serializers/lib/opentelemetry/instrumentation/active_model_serializers/instrumentation.rb

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,24 @@
44
#
55
# SPDX-License-Identifier: Apache-2.0
66

7+
require 'opentelemetry-instrumentation-active_support'
8+
79
module OpenTelemetry
810
module Instrumentation
911
module ActiveModelSerializers
1012
# Instrumentation class that detects and installs the ActiveModelSerializers instrumentation
1113
class Instrumentation < OpenTelemetry::Instrumentation::Base
14+
# Minimum supported version of the `active_model_serializers` gem
1215
MINIMUM_VERSION = Gem::Version.new('0.10.0')
1316

17+
# ActiveSupport::Notification topics to which the instrumentation subscribes
18+
SUBSCRIPTIONS = %w[
19+
render.active_model_serializers
20+
].freeze
21+
1422
install do |_config|
23+
install_active_support_instrumenation
1524
require_dependencies
16-
register_event_handler
1725
end
1826

1927
present do
@@ -24,24 +32,39 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
2432
!defined?(::ActiveSupport::Notifications).nil? && gem_version >= MINIMUM_VERSION
2533
end
2634

35+
def subscribe
36+
SUBSCRIPTIONS.each do |subscription_name|
37+
OpenTelemetry.logger.debug("Subscribing to #{subscription_name} notifications with #{_tracer}")
38+
OpenTelemetry::Instrumentation::ActiveSupport.subscribe(_tracer, subscription_name, default_attribute_transformer)
39+
end
40+
end
41+
2742
private
2843

44+
def _tracer
45+
self.class.instance.tracer
46+
end
47+
2948
def gem_version
3049
Gem::Version.new(::ActiveModel::Serializer::VERSION)
3150
end
3251

33-
def require_dependencies
34-
require_relative 'event_handler'
52+
def install_active_support_instrumenation
53+
OpenTelemetry::Instrumentation::ActiveSupport::Instrumentation.instance.install({})
3554
end
3655

37-
def register_event_handler
38-
::ActiveSupport::Notifications.subscribe(event_name) do |_name, start, finish, _id, payload|
39-
EventHandler.handle(start, finish, payload)
40-
end
56+
def require_dependencies
57+
require_relative 'railtie'
4158
end
4259

43-
def event_name
44-
'render.active_model_serializers'
60+
def default_attribute_transformer
61+
lambda { |payload|
62+
{
63+
'serializer.name' => payload[:serializer].name,
64+
'serializer.renderer' => 'active_model_serializers',
65+
'serializer.format' => payload[:adapter]&.class&.name || 'default'
66+
}
67+
}
4568
end
4669
end
4770
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
module OpenTelemetry
8+
module Instrumentation
9+
module ActiveModelSerializers # :nodoc:
10+
def self.subscribe
11+
Instrumentation.instance.subscribe
12+
end
13+
14+
if defined?(::Rails::Railtie)
15+
# This Railtie sets up subscriptions to relevant ActiveModelSerializers notifications
16+
class Railtie < ::Rails::Railtie
17+
config.after_initialize do
18+
::OpenTelemetry::Instrumentation::ActiveModelSerializers.subscribe
19+
end
20+
end
21+
end
22+
end
23+
end
24+
end

instrumentation/active_model_serializers/opentelemetry-instrumentation-active_model_serializers.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
2626
spec.required_ruby_version = '>= 3.0'
2727

2828
spec.add_dependency 'opentelemetry-api', '~> 1.0'
29+
spec.add_dependency 'opentelemetry-instrumentation-active_support', '>= 0.6.0'
2930
spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1'
3031

3132
spec.add_development_dependency 'active_model_serializers', '>= 0.10.0'
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
require_relative '../../../test_helper'
88

99
# require instrumentation so we do not have to depend on the install hook being called
10-
require_relative '../../../../lib/opentelemetry/instrumentation/active_model_serializers/event_handler'
10+
require_relative '../../../../lib/opentelemetry/instrumentation/active_model_serializers/instrumentation'
1111

12-
describe OpenTelemetry::Instrumentation::ActiveModelSerializers::EventHandler do
12+
describe OpenTelemetry::Instrumentation::ActiveModelSerializers::Instrumentation do
1313
let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveModelSerializers::Instrumentation.instance }
1414
let(:exporter) { EXPORTER }
1515
let(:span) { exporter.finished_spans.first }
1616
let(:model) { TestHelper::Model.new(name: 'test object') }
1717

1818
before do
1919
instrumentation.install
20+
instrumentation.subscribe
2021
exporter.reset
2122

2223
# this is currently a noop but this will future proof the test
@@ -38,7 +39,7 @@
3839
_(exporter.finished_spans.size).must_equal 1
3940

4041
_(span).must_be_kind_of OpenTelemetry::SDK::Trace::SpanData
41-
_(span.name).must_equal 'ModelSerializer render'
42+
_(span.name).must_equal 'render.active_model_serializers'
4243
_(span.attributes['serializer.name']).must_equal 'TestHelper::ModelSerializer'
4344
_(span.attributes['serializer.renderer']).must_equal 'active_model_serializers'
4445
_(span.attributes['serializer.format']).must_equal 'ActiveModelSerializers::Adapter::Attributes'
@@ -54,7 +55,7 @@
5455
_(exporter.finished_spans.size).must_equal 1
5556

5657
_(span).must_be_kind_of OpenTelemetry::SDK::Trace::SpanData
57-
_(span.name).must_equal 'ModelSerializer render'
58+
_(span.name).must_equal 'render.active_model_serializers'
5859
_(span.attributes['serializer.name']).must_equal 'TestHelper::ModelSerializer'
5960
_(span.attributes['serializer.renderer']).must_equal 'active_model_serializers'
6061
_(span.attributes['serializer.format']).must_equal 'TestHelper::Model'

instrumentation/active_model_serializers/test/opentelemetry/instrumentation/active_model_serializers_test.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@
3737
end
3838
end
3939

40-
describe 'install' do
40+
describe 'subscribe' do
41+
before do
42+
instrumentation.subscribe
43+
end
44+
4145
it 'subscribes to ActiveSupport::Notifications' do
4246
subscriptions = ActiveSupport::Notifications.notifier.instance_variable_get(:@string_subscribers)
4347
subscriptions = subscriptions['render.active_model_serializers']
44-
assert(subscriptions.detect { |s| s.is_a?(ActiveSupport::Notifications::Fanout::Subscribers::Timed) })
48+
assert(subscriptions.detect { |s| s.is_a?(ActiveSupport::Notifications::Fanout::Subscribers::Evented) })
4549
end
4650
end
4751
end

0 commit comments

Comments
 (0)