Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!(action_pack): Use ActiveSupport instead of patches #703

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f0d83a2
feat!(action_pack): Use ActiveSupport instead of patches
xuan-cao-swi Oct 19, 2023
48080b0
feat!(action_pack): remove comments
xuan-cao-swi Oct 19, 2023
68a9404
feat: drop 6.0 on action_pack to verify the test case
xuan-cao-swi Oct 19, 2023
6dc9184
feat: add rails 6.0 back
xuan-cao-swi Oct 20, 2023
05060e0
Merge branch 'main' into action-pack-instrument-notification
arielvalentin Nov 9, 2023
13b349c
Merge branch 'main' into action-pack-instrument-notification
arielvalentin Nov 21, 2023
ec10392
Merge branch 'main' into action-pack-instrument-notification
arielvalentin Nov 22, 2023
5555980
feat!(action_pack): remove rails 6.0 support + update readme on error…
xuan-cao-swi Nov 23, 2023
5ca4053
Update instrumentation/action_pack/README.md
xuan-cao-swi Dec 5, 2023
988194c
Update instrumentation/action_pack/README.md
xuan-cao-swi Dec 5, 2023
7ceac03
Update instrumentation/action_pack/README.md
xuan-cao-swi Dec 5, 2023
297555d
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 5, 2023
8f827af
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 5, 2023
46775d5
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 5, 2023
a190c71
Merge branch 'main' into action-pack-instrument-notification
xuan-cao-swi Dec 5, 2023
3bbe073
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 6, 2023
5f8f76f
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 6, 2023
59401f7
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 6, 2023
bb720fc
Update instrumentation/action_pack/lib/opentelemetry/instrumentation/…
xuan-cao-swi Dec 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions instrumentation/action_pack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ OpenTelemetry::SDK.configure do |c|
end
```

## Active Support Instrumentation

Earlier versions of this instrumentation relied on patching custom `dispatch` hooks from Rails's [Action Controller](https://github.com/rails/rails/blob/main/actionpack/lib/action_controller/metal.rb#L224) to extract request information.

This instrumentation now relies on `ActiveSupport::Notifications` and registers a custom Subscriber that listens to relevant events to modify the Rack span.

See the table below for details of what [Rails Framework Hook Events](https://guides.rubyonrails.org/active_support_instrumentation.html#action-controller) are recorded by this instrumentation:

| Event Name | Subscribe? | Creates Span? | Notes |
| - | - | - | - |
| `process_action.action_controller` | :white_check_mark: | :x: | It modifies the existing Rack span |


### Error Handling for Action Controller

If an error is triggered by Action Controller (such as a 500 internal server error), Action Pack will typically employ the default `ActionDispatch::PublicExceptions.new(Rails.public_path)` as the `exceptions_app`, as detailed in the [documentation](https://guides.rubyonrails.org/configuring.html#config-exceptions-app).

The error object will be retained within `payload[:exception_object]`. Additionally, its storage in `request.env['action_dispatch.exception']` is contingent upon the configuration of `action_dispatch.show_exceptions` in Rails.

## Examples

Example usage can be seen in the `./example/trace_demonstration.rb` file [here](https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/action_pack/example/trace_demonstration.ru)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ Rails.application.initialize!
run Rails.application

# To run this example run the `rackup` command with this file
# Example: rackup trace_request_demonstration.ru
# Example: rackup trace_demonstration.ru
# Navigate to http://localhost:9292/
# Spans for the requests will appear in the console
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require_relative 'handlers/action_controller'

module OpenTelemetry
module Instrumentation
module ActionPack
# Module that contains custom event handlers, which are used to generate spans per event
module Handlers
module_function

def subscribe
return unless Array(@subscriptions).empty?

config = ActionPack::Instrumentation.instance.config
handlers_by_pattern = {
'process_action.action_controller' => Handlers::ActionController.new(config)
}

@subscriptions = handlers_by_pattern.map do |key, handler|
::ActiveSupport::Notifications.subscribe(key, handler)
end
end

# Removes Event Handler Subscriptions for Action Controller notifications
# @note this method is not thread-safe and should not be used in a multi-threaded context
def unsubscribe
@subscriptions&.each { |subscriber| ::ActiveSupport::Notifications.unsubscribe(subscriber) }
@subscriptions = nil
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

module OpenTelemetry
module Instrumentation
module ActionPack
module Handlers
# Action Controller handler to handle the notification from Active Support
class ActionController
# @param config [Hash] of instrumentation options
def initialize(config)
@config = config
end

# Invoked by ActiveSupport::Notifications at the start of the instrumentation block
#
# @param _name [String] of the event (unused)
# @param _id [String] of the event (unused)
# @param payload [Hash] the payload passed as a method argument
# @return [Hash] the payload passed as a method argument
def start(_name, _id, payload)
rack_span = OpenTelemetry::Instrumentation::Rack.current_span

request = payload[:request]

rack_span.name = "#{payload[:controller]}##{payload[:action]}" unless request.env['action_dispatch.exception']
arielvalentin marked this conversation as resolved.
Show resolved Hide resolved

attributes_to_append = {
OpenTelemetry::SemanticConventions::Trace::CODE_NAMESPACE => String(payload[:controller]),
OpenTelemetry::SemanticConventions::Trace::CODE_FUNCTION => String(payload[:action])
}

attributes_to_append[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] = request.filtered_path if request.filtered_path != request.fullpath

rack_span.add_attributes(attributes_to_append)
rescue StandardError => e
OpenTelemetry.handle_error(exception: e)
end

# Invoked by ActiveSupport::Notifications at the end of the instrumentation block
#
# @param _name [String] of the event (unused)
# @param _id [String] of the event (unused)
# @param payload [Hash] the payload passed as a method argument
# @return [Hash] the payload passed as a method argument
def finish(_name, _id, payload)
rack_span = OpenTelemetry::Instrumentation::Rack.current_span
rack_span.record_exception(payload[:exception_object]) if payload[:exception_object]
arielvalentin marked this conversation as resolved.
Show resolved Hide resolved
rescue StandardError => e
OpenTelemetry.handle_error(exception: e)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ def gem_version
end

def patch
::ActionController::Metal.prepend(Patches::ActionController::Metal)
Handlers.subscribe
end

def require_dependencies
require_relative 'patches/action_controller/metal'
require_relative 'handlers'
end

def require_railtie
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,30 @@

require 'test_helper'

require_relative '../../../../../../lib/opentelemetry/instrumentation/action_pack'
require_relative '../../../../../../lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal'
require_relative '../../../../../lib/opentelemetry/instrumentation/action_pack'
require_relative '../../../../../lib/opentelemetry/instrumentation/action_pack/handlers'

describe OpenTelemetry::Instrumentation::ActionPack::Patches::ActionController::Metal do
describe OpenTelemetry::Instrumentation::ActionPack::Handlers::ActionController do
include Rack::Test::Methods

let(:instrumentation) { OpenTelemetry::Instrumentation::ActionPack::Instrumentation.instance }
let(:exporter) { EXPORTER }
let(:spans) { exporter.finished_spans }
let(:span) { exporter.finished_spans.last }
let(:rails_app) { DEFAULT_RAILS_APP }
let(:config) { {} }

# Clear captured spans
before { exporter.reset }
before do
OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe

instrumentation.instance_variable_set(:@config, config)
instrumentation.instance_variable_set(:@installed, false)

instrumentation.install(config)

exporter.reset
end

it 'sets the span name to the format: ControllerName#action' do
get '/ok'
Expand Down Expand Up @@ -75,18 +86,57 @@
get 'internal_server_error'

_(span.name).must_equal 'ExampleController#internal_server_error'
_(span.kind).must_equal :server
_(span.status.ok?).must_equal false

_(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack'
_(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION

_(span.attributes['http.method']).must_equal 'GET'
_(span.attributes['http.host']).must_equal 'example.org'
_(span.attributes['http.scheme']).must_equal 'http'
_(span.attributes['http.target']).must_equal '/internal_server_error'
_(span.attributes['http.status_code']).must_equal 500
_(span.attributes['http.user_agent']).must_be_nil
_(span.attributes['code.namespace']).must_equal 'ExampleController'
_(span.attributes['code.function']).must_equal 'internal_server_error'
end

it 'does not set the span name when an exception is raised in middleware' do
get '/ok?raise_in_middleware'

_(span.name).must_equal 'HTTP GET'
_(span.kind).must_equal :server
_(span.status.ok?).must_equal false

_(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack'
_(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION

_(span.attributes['http.method']).must_equal 'GET'
_(span.attributes['http.host']).must_equal 'example.org'
_(span.attributes['http.scheme']).must_equal 'http'
_(span.attributes['http.target']).must_equal '/ok?raise_in_middleware'
_(span.attributes['http.status_code']).must_equal 500
_(span.attributes['http.user_agent']).must_be_nil
_(span.attributes['code.namespace']).must_be_nil
_(span.attributes['code.function']).must_be_nil
end

it 'does not set the span name when the request is redirected in middleware' do
get '/ok?redirect_in_middleware'

_(span.name).must_equal 'HTTP GET'
_(span.kind).must_equal :server
_(span.status.ok?).must_equal true

_(span.attributes['http.method']).must_equal 'GET'
_(span.attributes['http.host']).must_equal 'example.org'
_(span.attributes['http.scheme']).must_equal 'http'
_(span.attributes['http.target']).must_equal '/ok?redirect_in_middleware'
_(span.attributes['http.status_code']).must_equal 307
_(span.attributes['http.user_agent']).must_be_nil
_(span.attributes['code.namespace']).must_be_nil
_(span.attributes['code.function']).must_be_nil
end

describe 'when the application has exceptions_app configured' do
Expand All @@ -96,6 +146,20 @@
get 'internal_server_error'

_(span.name).must_equal 'ExampleController#internal_server_error'
_(span.kind).must_equal :server
_(span.status.ok?).must_equal false

_(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack'
_(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION

_(span.attributes['http.method']).must_equal 'GET'
_(span.attributes['http.host']).must_equal 'example.org'
_(span.attributes['http.scheme']).must_equal 'http'
_(span.attributes['http.target']).must_equal '/internal_server_error'
_(span.attributes['http.status_code']).must_equal 500
_(span.attributes['http.user_agent']).must_be_nil
_(span.attributes['code.namespace']).must_equal 'ExceptionsController'
_(span.attributes['code.function']).must_equal 'show'
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'test_helper'

require_relative '../../../../lib/opentelemetry/instrumentation/action_pack'

describe 'OpenTelemetry::Instrumentation::ActionPack::Handlers' do
let(:instrumentation) { OpenTelemetry::Instrumentation::ActionPack::Instrumentation.instance }
let(:config) { {} }

before do
OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe
instrumentation.instance_variable_set(:@config, config)
instrumentation.instance_variable_set(:@installed, false)

instrumentation.install(config)
end

it 'success subscribe the notification' do
subscriptions = OpenTelemetry::Instrumentation::ActionPack::Handlers.instance_variable_get(:@subscriptions)
_(subscriptions.count).must_equal 1
_(subscriptions[0].pattern).must_equal 'process_action.action_controller'
end

it 'success unsubscribe the notification' do
OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe
subscriptions = OpenTelemetry::Instrumentation::ActionPack::Handlers.instance_variable_get(:@subscriptions)
_(subscriptions).must_be_nil
end
end