diff --git a/.toys/.data/releases.yml b/.toys/.data/releases.yml index 156bf4a369..ed1b00b89a 100644 --- a/.toys/.data/releases.yml +++ b/.toys/.data/releases.yml @@ -31,6 +31,10 @@ commit_lint: # * changelog_path: Path to CHANGLEOG.md relative to the gem directory. # (Required only if it is not in the expected location.) gems: + - name: opentelemetry-instrumentation-action_view + directory: instrumentation/action_view + version_constant: [OpenTelemetry, Instrumentation, ActionView, VERSION] + - name: opentelemetry-instrumentation-active_job directory: instrumentation/active_job version_constant: [OpenTelemetry, Instrumentation, ActiveJob, VERSION] diff --git a/instrumentation/action_view/.rubocop.yml b/instrumentation/action_view/.rubocop.yml new file mode 100644 index 0000000000..26d21d4644 --- /dev/null +++ b/instrumentation/action_view/.rubocop.yml @@ -0,0 +1,5 @@ +inherit_from: ../.rubocop-examples.yml + +Naming/FileName: + Exclude: + - "lib/opentelemetry-instrumentation-action_view.rb" diff --git a/instrumentation/action_view/.yardopts b/instrumentation/action_view/.yardopts new file mode 100644 index 0000000000..f46e2da66c --- /dev/null +++ b/instrumentation/action_view/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry Action View Instrumentation +--markup=markdown +--main=README.md +./lib/opentelemetry/instrumentation/**/*.rb +./lib/opentelemetry/instrumentation.rb +- +README.md +CHANGELOG.md diff --git a/instrumentation/action_view/Appraisals b/instrumentation/action_view/Appraisals new file mode 100644 index 0000000000..6c0a211ef7 --- /dev/null +++ b/instrumentation/action_view/Appraisals @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# Rails 5.2 is incompatible with Ruby 3. +if RUBY_VERSION < '3' + appraise 'rails-5.2' do + gem 'rails', '~> 5.2.0' + end +end + +appraise 'rails-6.0' do + gem 'rails', '~> 6.0.0' +end + +appraise 'rails-6.1' do + gem 'rails', '~> 6.1.0' +end diff --git a/instrumentation/action_view/CHANGELOG.md b/instrumentation/action_view/CHANGELOG.md new file mode 100644 index 0000000000..ea137bf7e7 --- /dev/null +++ b/instrumentation/action_view/CHANGELOG.md @@ -0,0 +1 @@ +# Release History: opentelemetry-instrumentation-action_view diff --git a/instrumentation/action_view/Gemfile b/instrumentation/action_view/Gemfile new file mode 100644 index 0000000000..6f5e957a5b --- /dev/null +++ b/instrumentation/action_view/Gemfile @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +gem 'opentelemetry-api', path: '../../api' +gem 'opentelemetry-instrumentation-base', path: '../base' + +group :test do + gem 'byebug' + gem 'opentelemetry-common', path: '../../common' + gem 'opentelemetry-sdk', path: '../../sdk' + gem 'opentelemetry-semantic_conventions', path: '../../semantic_conventions' + gem 'pry-byebug' +end diff --git a/instrumentation/action_view/LICENSE b/instrumentation/action_view/LICENSE new file mode 100644 index 0000000000..1ef7dad2c5 --- /dev/null +++ b/instrumentation/action_view/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/action_view/README.md b/instrumentation/action_view/README.md new file mode 100644 index 0000000000..1bb299ca9a --- /dev/null +++ b/instrumentation/action_view/README.md @@ -0,0 +1,55 @@ +# OpenTelemetry ActionView Instrumentation + +The ActionView instrumentation is a community-maintained instrumentation for the ActionView portion of the [Ruby on Rails][rails-home] web-application framework. + +## How do I get started? + +Install the gem using: + +``` +gem install opentelemetry-instrumentation-action_view +gem install opentelemetry-instrumentation-rails +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-action_view` in your `Gemfile`. + +## Usage + +To use the instrumentation, call `use` with the name of the instrumentation: + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::Rails' + c.use 'OpenTelemetry::Instrumentation::ActionView' +end +``` + +Alternatively, you can also call `use_all` to install all the available instrumentation. + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use_all +end +``` + +## Examples + +Example usage can be seen in the `./example/trace_request_demonstration.ru` file [here](https://github.com/open-telemetry/opentelemetry-ruby/blob/main/instrumentation/action_view/example/trace_request_demonstration.ru) + +## How can I get involved? + +The `opentelemetry-instrumentation-action_view` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-instrumentation-action_view` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[rails-home]: https://github.com/rails/rails +[bundler-home]: https://bundler.io +[repo-github]: https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: https://github.com/open-telemetry/opentelemetry-ruby/blob/main/LICENSE +[ruby-sig]: https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: https://github.com/open-telemetry/community#community-meetings +[discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/instrumentation/action_view/Rakefile b/instrumentation/action_view/Rakefile new file mode 100644 index 0000000000..1a64ba842e --- /dev/null +++ b/instrumentation/action_view/Rakefile @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/instrumentation/action_view/example/trace_request_demonstration.ru b/instrumentation/action_view/example/trace_request_demonstration.ru new file mode 100644 index 0000000000..80e50c26b4 --- /dev/null +++ b/instrumentation/action_view/example/trace_request_demonstration.ru @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/inline' + +gemfile(true) do + source 'https://rubygems.org' + + gem 'rails' + gem 'opentelemetry-sdk' + gem 'opentelemetry-instrumentation-rails', path: '../../rails' + gem 'opentelemetry-instrumentation-action_view', path: '../' +end + +require 'active_support/railtie' +require 'action_controller/railtie' +require 'action_view/railtie' + +# TraceRequestApp is a minimal Rails application inspired by the Rails +# bug report template for action controller. +# The configuration is compatible with Rails 6.0 +class TraceRequestApp < Rails::Application + config.root = __dir__ + config.hosts << 'example.org' + secrets.secret_key_base = 'secret_key_base' + + config.eager_load = false + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + routes.draw do + get '/' => 'test#index' + end +end + +# A minimal test controller +class TestController < ActionController::Base + include Rails.application.routes.url_helpers + + def index; end +end + +# Simple setup for demonstration purposes, simple span processor should not be +# used in a production environment +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new( + OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new +) + +OpenTelemetry::SDK.configure do |c| + # At present, the Rails instrumentation is required. + c.use 'OpenTelemetry::Instrumentation::Rails' + c.use 'OpenTelemetry::Instrumentation::ActionView' + c.add_span_processor(span_processor) +end + +Rails.application.initialize! + +run Rails.application + +# To run this example run the `rackup` command with this file +# Example: rackup trace_request_demonstration.ru +# Navigate to http://localhost:9292/ +# Spans for the requests will appear in the console diff --git a/instrumentation/action_view/gemfiles/rails_5.2.gemfile b/instrumentation/action_view/gemfiles/rails_5.2.gemfile new file mode 100644 index 0000000000..0012175584 --- /dev/null +++ b/instrumentation/action_view/gemfiles/rails_5.2.gemfile @@ -0,0 +1,17 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" +gem "rails", "~> 5.2.0" + +group :test do + gem "byebug" + gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" + gem "pry-byebug" +end + +gemspec path: "../" diff --git a/instrumentation/action_view/gemfiles/rails_6.0.gemfile b/instrumentation/action_view/gemfiles/rails_6.0.gemfile new file mode 100644 index 0000000000..b7b20e8fe1 --- /dev/null +++ b/instrumentation/action_view/gemfiles/rails_6.0.gemfile @@ -0,0 +1,17 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" +gem "rails", "~> 6.0.0" + +group :test do + gem "byebug" + gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" + gem "pry-byebug" +end + +gemspec path: "../" diff --git a/instrumentation/action_view/gemfiles/rails_6.1.gemfile b/instrumentation/action_view/gemfiles/rails_6.1.gemfile new file mode 100644 index 0000000000..9fb4b4a3f1 --- /dev/null +++ b/instrumentation/action_view/gemfiles/rails_6.1.gemfile @@ -0,0 +1,17 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" +gem "rails", "~> 6.1.0" + +group :test do + gem "byebug" + gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" + gem "pry-byebug" +end + +gemspec path: "../" diff --git a/instrumentation/action_view/lib/opentelemetry-instrumentation-action_view.rb b/instrumentation/action_view/lib/opentelemetry-instrumentation-action_view.rb new file mode 100644 index 0000000000..baceb3cb76 --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry-instrumentation-action_view.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative './opentelemetry/instrumentation' diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation.rb new file mode 100644 index 0000000000..37c26c3c1c --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Instrumentation should be able to handle the case when the library is not installed on a user's system. + module Instrumentation + end +end + +require_relative './instrumentation/action_view' diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view.rb new file mode 100644 index 0000000000..d2285b3d21 --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry' +require 'opentelemetry-instrumentation-base' + +module OpenTelemetry + module Instrumentation + # Contains the OpenTelemetry instrumentation for the ActionView gem + module ActionView + end + end +end + +require_relative './action_view/instrumentation' +require_relative './action_view/version' diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb new file mode 100644 index 0000000000..7059325011 --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionView + # The Instrumentation class contains logic to detect and install the ActionView instrumentation + class Instrumentation < OpenTelemetry::Instrumentation::Base + MINIMUM_VERSION = Gem::Version.new('5.2.0') + install do |_config| + require_dependencies + end + + present do + defined?(::ActionView) + end + + compatible do + Gem.loaded_specs['actionview'].version >= MINIMUM_VERSION + end + + private + + def require_dependencies + require_relative 'railtie' + end + end + end + end +end diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/railtie.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/railtie.rb new file mode 100644 index 0000000000..035824484f --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/railtie.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionView + SUBSCRIPTIONS = %w[ + render_template.action_view + render_partial.action_view + render_collection.action_view + ].freeze + + # This Railtie sets up subscriptions to relevant ActionView notifications + class Railtie < ::Rails::Railtie + config.before_initialize do + OpenTelemetry::Instrumentation::Rails::Instrumentation.instance.install({}) + end + + config.after_initialize do + SUBSCRIPTIONS.each do |subscription_name| + subscriber = OpenTelemetry::Instrumentation::Rails::SpanSubscriber.new( + name: subscription_name, + tracer: ActionView::Instrumentation.instance.tracer + ) + ::ActiveSupport::Notifications.notifier.subscribe(subscription_name, subscriber) + end + end + end + end + end +end diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb new file mode 100644 index 0000000000..1405424d8a --- /dev/null +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionView + VERSION = '0.19.0' + end + end +end diff --git a/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec b/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec new file mode 100644 index 0000000000..57fd5c503b --- /dev/null +++ b/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'opentelemetry/instrumentation/action_view/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-instrumentation-action_view' + spec.version = OpenTelemetry::Instrumentation::ActionView::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'ActionView instrumentation for the OpenTelemetry framework' + spec.description = 'ActionView instrumentation for the OpenTelemetry framework' + spec.homepage = 'https://github.com/open-telemetry/opentelemetry-ruby' + spec.license = 'Apache-2.0' + + spec.files = ::Dir.glob('lib/**/*.rb') + + ::Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 2.5.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0.0.rc1' + spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.18.0' + spec.add_dependency 'opentelemetry-instrumentation-rails', '~> 0.18.0' + + spec.add_development_dependency 'appraisal', '~> 2.2.0' + spec.add_development_dependency 'bundler', '>= 1.17' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-sdk' + spec.add_development_dependency 'rake', '~> 12.3.3' + spec.add_development_dependency 'rubocop', '~> 0.73.0' + spec.add_development_dependency 'simplecov', '~> 0.17.1' + spec.add_development_dependency 'webmock', '~> 3.7.6' + spec.add_development_dependency 'yard', '~> 0.9' + spec.add_development_dependency 'yard-doctest', '~> 0.1.6' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-instrumentation-action_view/v#{OpenTelemetry::Instrumentation::ActionView::VERSION}/file.CHANGELOG.html" + spec.metadata['source_code_uri'] = 'https://github.com/open-telemetry/opentelemetry-ruby/tree/main/instrumentation/action_view' + spec.metadata['bug_tracker_uri'] = 'https://github.com/open-telemetry/opentelemetry-ruby/issues' + spec.metadata['documentation_uri'] = "https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-instrumentation-action_view/v#{OpenTelemetry::Instrumentation::ActionView::VERSION}" + end +end diff --git a/instrumentation/action_view/test/.rubocop.yml b/instrumentation/action_view/test/.rubocop.yml new file mode 100644 index 0000000000..dd94258585 --- /dev/null +++ b/instrumentation/action_view/test/.rubocop.yml @@ -0,0 +1,4 @@ +inherit_from: ../.rubocop.yml + +Metrics/BlockLength: + Enabled: false diff --git a/instrumentation/action_view/test/opentelemetry/instrumentation/action_view/instrumentation_test.rb b/instrumentation/action_view/test/opentelemetry/instrumentation/action_view/instrumentation_test.rb new file mode 100644 index 0000000000..ff05f4ab7c --- /dev/null +++ b/instrumentation/action_view/test/opentelemetry/instrumentation/action_view/instrumentation_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../lib/opentelemetry/instrumentation/action_view' + +describe OpenTelemetry::Instrumentation::ActionView do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActionView::Instrumentation.instance } + + it 'has #name' do + _(instrumentation.name).must_equal 'OpenTelemetry::Instrumentation::ActionView' + end + + it 'has #version' do + _(instrumentation.version).wont_be_nil + _(instrumentation.version).wont_be_empty + end + + describe '#install' do + it 'accepts argument' do + _(instrumentation.install({})).must_equal(true) + instrumentation.instance_variable_set(:@installed, false) + end + end +end diff --git a/instrumentation/action_view/test/test_helper.rb b/instrumentation/action_view/test/test_helper.rb new file mode 100644 index 0000000000..dc1ca5758c --- /dev/null +++ b/instrumentation/action_view/test/test_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/sdk' + +require 'pry' +require 'minitest/autorun' +require 'webmock/minitest' + +require 'rails' +require 'action_view' +require 'opentelemetry-instrumentation-action_view' + +# global opentelemetry-sdk setup: +EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER) + +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::ActionView' + c.add_span_processor span_processor +end diff --git a/instrumentation/all/Gemfile b/instrumentation/all/Gemfile index 228cb62811..eff13798a4 100644 --- a/instrumentation/all/Gemfile +++ b/instrumentation/all/Gemfile @@ -10,6 +10,7 @@ gemspec gem 'opentelemetry-api', path: '../../api' gem 'opentelemetry-common', path: '../../common' +gem 'opentelemetry-instrumentation-action_view', path: '../action_view' gem 'opentelemetry-instrumentation-active_job', path: '../active_job' gem 'opentelemetry-instrumentation-active_model_serializers', path: '../active_model_serializers' gem 'opentelemetry-instrumentation-active_record', path: '../active_record' diff --git a/instrumentation/all/lib/opentelemetry/instrumentation/all.rb b/instrumentation/all/lib/opentelemetry/instrumentation/all.rb index 6fe97a5d45..913b9e34c1 100644 --- a/instrumentation/all/lib/opentelemetry/instrumentation/all.rb +++ b/instrumentation/all/lib/opentelemetry/instrumentation/all.rb @@ -4,6 +4,7 @@ # # SPDX-License-Identifier: Apache-2.0 +require 'opentelemetry-instrumentation-action_view' require 'opentelemetry-instrumentation-active_job' require 'opentelemetry-instrumentation-active_record' require 'opentelemetry-instrumentation-bunny' diff --git a/instrumentation/all/opentelemetry-instrumentation-all.gemspec b/instrumentation/all/opentelemetry-instrumentation-all.gemspec index 840bea5cf2..a83c6f6fef 100644 --- a/instrumentation/all/opentelemetry-instrumentation-all.gemspec +++ b/instrumentation/all/opentelemetry-instrumentation-all.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.5.0' + spec.add_dependency 'opentelemetry-instrumentation-action_view', '~> 0.19.0' spec.add_dependency 'opentelemetry-instrumentation-active_job', '~> 0.1.0' spec.add_dependency 'opentelemetry-instrumentation-active_model_serializers', '~> 0.18.1' spec.add_dependency 'opentelemetry-instrumentation-active_record', '~> 0.1.0' diff --git a/instrumentation/rails/gemfiles/rails_5.2.gemfile b/instrumentation/rails/gemfiles/rails_5.2.gemfile index 1f835f5ba8..9ec01ef4c5 100644 --- a/instrumentation/rails/gemfiles/rails_5.2.gemfile +++ b/instrumentation/rails/gemfiles/rails_5.2.gemfile @@ -3,13 +3,16 @@ source "https://rubygems.org" gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" gem "rails", "~> 5.2.0" group :test do gem "byebug" gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-instrumentation-active_record", path: "../../../instrumentation/active_record" gem "opentelemetry-instrumentation-rack", path: "../../../instrumentation/rack" gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" gem "pry-byebug" end diff --git a/instrumentation/rails/gemfiles/rails_6.0.gemfile b/instrumentation/rails/gemfiles/rails_6.0.gemfile index 489953b3a2..6c688099bf 100644 --- a/instrumentation/rails/gemfiles/rails_6.0.gemfile +++ b/instrumentation/rails/gemfiles/rails_6.0.gemfile @@ -3,13 +3,16 @@ source "https://rubygems.org" gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" gem "rails", "~> 6.0.0" group :test do gem "byebug" gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-instrumentation-active_record", path: "../../../instrumentation/active_record" gem "opentelemetry-instrumentation-rack", path: "../../../instrumentation/rack" gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" gem "pry-byebug" end diff --git a/instrumentation/rails/gemfiles/rails_6.1.gemfile b/instrumentation/rails/gemfiles/rails_6.1.gemfile index e5260b7878..3e530805fa 100644 --- a/instrumentation/rails/gemfiles/rails_6.1.gemfile +++ b/instrumentation/rails/gemfiles/rails_6.1.gemfile @@ -3,13 +3,16 @@ source "https://rubygems.org" gem "opentelemetry-api", path: "../../../api" +gem "opentelemetry-instrumentation-base", path: "../../base" gem "rails", "~> 6.1.0" group :test do gem "byebug" gem "opentelemetry-common", path: "../../../common" + gem "opentelemetry-instrumentation-active_record", path: "../../../instrumentation/active_record" gem "opentelemetry-instrumentation-rack", path: "../../../instrumentation/rack" gem "opentelemetry-sdk", path: "../../../sdk" + gem "opentelemetry-semantic_conventions", path: "../../../semantic_conventions" gem "pry-byebug" end diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/fanout.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/fanout.rb new file mode 100644 index 0000000000..7391da9696 --- /dev/null +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/fanout.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Rails + # This is a replacement for the default Fanout notifications queue, which adds special + # handling around returned context from the SpanSubscriber notification handlers. + # Used together, it allows us to trace arbitrary ActiveSupport::Notifications safely. + class Fanout < ::ActiveSupport::Notifications::Fanout + def start(name, id, payload) + listeners_for(name).map do |s| + result = [s] + state = s.start(name, id, payload) + if state.is_a?(Array) && state[0].is_a?(OpenTelemetry::Trace::Span) && state[1] # rubocop:disable Style/IfUnlessModifier + result << state + end + + result + end + end + + def finish(name, id, payload, listeners = listeners_for(name)) + listeners.each do |(s, arr)| + span, token = arr + if span.is_a?(OpenTelemetry::Trace::Span) && token + s.finish( + name, + id, + payload.merge( + __opentelemetry_span: span, + __opentelemetry_ctx_token: token + ) + ) + else + s.finish(name, id, payload) + end + end + end + + def listeners_for(name) + listeners = super + listeners.sort_by do |l| + l.instance_variable_get(:@delegate).is_a?(SpanSubscriber) ? -1 : 1 + end + end + end + end + end +end diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb index 12d6189f67..fbcb1a9408 100644 --- a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb @@ -23,11 +23,15 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end option :enable_recognize_route, default: false, validate: :boolean + option :disallowed_notification_payload_keys, default: [], validate: :array + option :notification_payload_transform, default: nil, validate: :callable private def require_dependencies require_relative 'patches/action_controller/metal' + require_relative 'fanout' + require_relative 'span_subscriber' end def require_railtie diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/railtie.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/railtie.rb index f899276b33..263db87888 100644 --- a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/railtie.rb +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/railtie.rb @@ -9,9 +9,11 @@ module Instrumentation module Rails # The Instrumentation class contains logic to detect and install the Rails # instrumentation, while this Railtie is used to conventionally instrument - # the Rails application through its initialization hooks + # the Rails application through its initialization hooks. class Railtie < ::Rails::Railtie config.before_initialize do |app| + ::ActiveSupport::Notifications.notifier = Fanout.new + OpenTelemetry::Instrumentation::ActiveRecord::Instrumentation.instance.install({}) OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/span_subscriber.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/span_subscriber.rb new file mode 100644 index 0000000000..b54a0dc69b --- /dev/null +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/span_subscriber.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Rails + # The SpanSubscriber is a special ActiveSupport::Notification subscription + # handler which turns notifications into generic spans, taking care to handle + # context appropriately. + class SpanSubscriber + ALWAYS_VALID_PAYLOAD_TYPES = [TrueClass, FalseClass, String, Numeric, Symbol].freeze + + def initialize(name:, tracer:) + @span_name = name.split('.')[0..1].reverse.join(' ').freeze + @tracer = tracer + end + + def start(_name, _id, payload) + span = @tracer.start_span(@span_name, kind: :internal) + token = OpenTelemetry::Context.attach( + OpenTelemetry::Trace.context_with_span(span) + ) + + [span, token] + end + + def finish(_name, _id, payload) # rubocop:disable Metrics/AbcSize + span = payload.delete(:__opentelemetry_span) + token = payload.delete(:__opentelemetry_ctx_token) + return unless span && token + + payload = transform_payload(payload) + attrs = payload.map do |k, v| + [k.to_s, sanitized_value(v)] if valid_payload_key?(k) && valid_payload_value?(v) + end + span.add_attributes(attrs.compact.to_h) + + if (e = payload[:exception_object]) + span.record_exception(e) + span.status = OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{e.class}") + end + + span.finish + OpenTelemetry::Context.detach(token) + end + + private + + def instrumentation_config + Rails::Instrumentation.instance.config + end + + def transform_payload(payload) + return payload if instrumentation_config[:notification_payload_transform].nil? + + instrumentation_config[:notification_payload_transform].call(payload) + end + + def valid_payload_key?(key) + %i[exception exception_object].none?(key) && instrumentation_config[:disallowed_notification_payload_keys].none?(key) + end + + def valid_payload_value?(value) + if value.is_a?(Array) + return true if value.empty? + + value.map(&:class).uniq.size == 1 && ALWAYS_VALID_PAYLOAD_TYPES.any? { |t| value.first.is_a?(t) } + else + ALWAYS_VALID_PAYLOAD_TYPES.any? { |t| value.is_a?(t) } + end + end + + # We'll accept symbols as values, but stringify them; and we'll stringify symbols within an array. + def sanitized_value(value) + if value.is_a?(Array) + value.map { |v| v.is_a?(Symbol) ? v.to_s : v } + elsif value.is_a?(Symbol) + value.to_s + else + value + end + end + end + end + end +end diff --git a/instrumentation/rails/test/opentelemetry/instrumentation/rails/fanout_test.rb b/instrumentation/rails/test/opentelemetry/instrumentation/rails/fanout_test.rb new file mode 100644 index 0000000000..e045166a29 --- /dev/null +++ b/instrumentation/rails/test/opentelemetry/instrumentation/rails/fanout_test.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Instrumentation::Rails::Fanout do + let(:tracer) { OpenTelemetry::Instrumentation::Rails::Instrumentation.instance.tracer } + let(:exporter) { EXPORTER } + let(:spans) { exporter.finished_spans } + let(:last_span) { spans.last } + let(:span_subscriber) do + OpenTelemetry::Instrumentation::Rails::SpanSubscriber.new( + name: 'bar.foo', + tracer: tracer + ) + end + + before do + exporter.reset + ::ActiveSupport::Notifications.notifier = OpenTelemetry::Instrumentation::Rails::Fanout.new + end + + it 'sorts span subscribers first' do + ::ActiveSupport::Notifications.subscribe('bar.foo') do |event| + # pass + end + ::ActiveSupport::Notifications.subscribe('bar.foo', span_subscriber) + + listeners = ::ActiveSupport::Notifications.notifier.listeners_for('bar.foo') + _(listeners.first.instance_variable_get(:@delegate)).must_equal(span_subscriber) + end + + it 'traces an event when a span subscriber is used' do + ::ActiveSupport::Notifications.subscribe('bar.foo', span_subscriber) + ::ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal('foo bar') + _(last_span.attributes['extra']).must_equal('context') + end + + it 'does not trace an event by default' do + ::ActiveSupport::Notifications.subscribe('bar.foo') do + # pass + end + ::ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + _(last_span).must_be_nil + end + + it 'finishes spans even when other subscribers blow up' do + ::ActiveSupport::Notifications.subscribe('bar.foo', span_subscriber) + ::ActiveSupport::Notifications.subscribe('bar.foo') { raise 'boom' } + + expect do + ::ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + end.must_raise RuntimeError + + _(last_span).wont_be_nil + _(last_span.name).must_equal('foo bar') + _(last_span.attributes['extra']).must_equal('context') + end +end diff --git a/instrumentation/rails/test/opentelemetry/instrumentation/rails/span_subscriber_test.rb b/instrumentation/rails/test/opentelemetry/instrumentation/rails/span_subscriber_test.rb new file mode 100644 index 0000000000..0092159c97 --- /dev/null +++ b/instrumentation/rails/test/opentelemetry/instrumentation/rails/span_subscriber_test.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Instrumentation::Rails::SpanSubscriber do + let(:instrumentation) { OpenTelemetry::Instrumentation::Rails::Instrumentation.instance } + let(:tracer) { instrumentation.tracer } + let(:exporter) { EXPORTER } + let(:last_span) { exporter.finished_spans.last } + let(:subscriber) do + OpenTelemetry::Instrumentation::Rails::SpanSubscriber.new( + name: 'bar.foo', + tracer: tracer + ) + end + + before do + exporter.reset + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install({}) + end + + it 'memoizes the span name' do + span, = subscriber.start('oh.hai', 'abc', {}) + _(span.name).must_equal('foo bar') + end + + it 'uses the provided tracer' do + subscriber = OpenTelemetry::Instrumentation::Rails::SpanSubscriber.new( + name: 'oh.hai', + tracer: OpenTelemetry.tracer_provider.tracer('foo') + ) + span, = subscriber.start('oh.hai', 'abc', {}) + _(span.instrumentation_library.name).must_equal('foo') + end + + it 'finishes the passed span' do + span, token = subscriber.start('hai', 'abc', {}) + subscriber.finish('hai', 'abc', __opentelemetry_span: span, __opentelemetry_ctx_token: token) + + # If it's in exporter.finished_spans ... it's finished. + _(last_span).wont_be_nil + end + + it 'sets attributes as expected' do + span, token = subscriber.start('hai', 'abc', {}) + # We only use the finished attributes - could change in the future, perhaps. + subscriber.finish( + 'hai', + 'abc', + __opentelemetry_span: span, + __opentelemetry_ctx_token: token, + string: 'keys_are_present', + numeric_is_fine: 1, + boolean_okay?: true, + symbols: :are_stringified, + empty_array_is_okay: [], + homogeneous_arrays_are_fine: %i[one two], + heterogeneous_arrays_are_not: [1, false], + exception: %w[Exception is_not_set_as_attribute], + exception_object: Exception.new('is_not_set_as_attribute'), + nil_values_are_rejected: nil, + complex_values_are_rejected: { foo: :bar } + ) + + _(last_span).wont_be_nil + _(last_span.attributes['string']).must_equal('keys_are_present') + _(last_span.attributes['numeric_is_fine']).must_equal(1) + _(last_span.attributes['boolean_okay?']).must_equal(true) + _(last_span.attributes['symbols']).must_equal('are_stringified') + _(last_span.attributes['empty_array_is_okay']).must_equal([]) + _(last_span.attributes['homogeneous_arrays_are_fine']).must_equal(%w[one two]) + _(last_span.attributes.key?('heterogeneous_arrays_are_not')).must_equal(false) + _(last_span.attributes.key?('exception')).must_equal(false) + _(last_span.attributes.key?('exception_object')).must_equal(false) + _(last_span.attributes.key?('nil_values_are_rejected')).must_equal(false) + _(last_span.attributes.key?('complex_values_are_rejected')).must_equal(false) + end + + it 'logs an exception_object correctly' do + span, token = subscriber.start('hai', 'abc', {}) + # We only use the finished attributes - could change in the + # future, perhaps. + subscriber.finish( + 'hai', + 'abc', + __opentelemetry_span: span, + __opentelemetry_ctx_token: token, + exception_object: Exception.new('boom') + ) + + status = last_span.status + _(status.code).must_equal(OpenTelemetry::Trace::Status::ERROR) + _(status.description).must_equal('Unhandled exception of type: Exception') + + event = last_span.events.first + _(event.name).must_equal('exception') + _(event.attributes['exception.message']).must_equal('boom') + end + + describe 'instrumentation option - disallowed_notification_payload_keys' do + before do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(disallowed_notification_payload_keys: [:foo]) + end + + after do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install({}) + end + + it 'does not set disallowed attributes from notification payloads' do + span, token = subscriber.start('hai', 'abc', {}) + subscriber.finish( + 'hai', + 'abc', + __opentelemetry_span: span, + __opentelemetry_ctx_token: token, + foo: 'bar', + baz: 'bat' + ) + + _(last_span).wont_be_nil + _(last_span.attributes.key?('foo')).must_equal(false) + _(last_span.attributes['baz']).must_equal('bat') + end + end + + describe 'instrumentation option - notification_payload_transform' do + let(:transformer_proc) { ->(v) { v.transform_values { 'optimus prime' } } } + + before do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(notification_payload_transform: transformer_proc) + end + + after do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install({}) + end + + it 'allows a callable to transform all payload values' do + span, token = subscriber.start('hai', 'abc', {}) + subscriber.finish( + 'hai', + 'abc', + __opentelemetry_span: span, + __opentelemetry_ctx_token: token, + thing: 'a semi truck' + ) + + _(last_span).wont_be_nil + _(last_span.attributes['thing']).must_equal('optimus prime') + end + end +end