diff --git a/lib/splunk/otel/instrumentation/action_pack/railtie.rb b/lib/splunk/otel/instrumentation/action_pack/railtie.rb index afbc7b4..d2e0de8 100644 --- a/lib/splunk/otel/instrumentation/action_pack/railtie.rb +++ b/lib/splunk/otel/instrumentation/action_pack/railtie.rb @@ -6,8 +6,6 @@ module ActionPack # Install the Rack middleware for RUM responses class Railtie < ::Rails::Railtie config.before_initialize do |app| - OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) - app.middleware.insert_before( 0, Splunk::Otel::Rack::RumMiddleware diff --git a/splunk-otel.gemspec b/splunk-otel.gemspec index 524aace..7730f49 100644 --- a/splunk-otel.gemspec +++ b/splunk-otel.gemspec @@ -34,6 +34,7 @@ Gem::Specification.new do |spec| # development dependencies spec.add_development_dependency "rubocop-rake", "~> 0.6.0" spec.add_development_dependency "simplecov", "~> 0.21.2" + spec.add_development_dependency "rails", "~> 7.0.0" spec.metadata = { "rubygems_mfa_required" => "true" diff --git a/test/splunk/instrumentation/rails_test.rb b/test/splunk/instrumentation/rails_test.rb new file mode 100644 index 0000000..75448c5 --- /dev/null +++ b/test/splunk/instrumentation/rails_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails' + +require "test_helper" +require "opentelemetry/sdk" +require "opentelemetry/instrumentation/rack" +require "splunk/otel" +require "splunk/otel/instrumentation/rack" +require "rack/test" +require 'test_helpers/app_config.rb' + +module Splunk + class RumRailsTest < Test::Unit::TestCase + include Rack::Test::Methods + + DEFAULT_RAILS_APP = AppConfig.initialize_app + ::Rails.application = DEFAULT_RAILS_APP + + def setup + with_env("OTEL_SERVICE_NAME" => "test-service") do + Splunk::Otel.configure do |c| + c.use 'OpenTelemetry::Instrumentation::ActionPack' + end + end + end + + def teardown + OpenTelemetry.tracer_provider.shutdown + end + + def app + DEFAULT_RAILS_APP + end + + test "RUM response from Rack middleware" do + get "/ok" + + assert last_response.ok? + assert_equal "OK", last_response.body + + response_headers = last_response.headers + assert_equal "Server-Timing", response_headers["Access-Control-Expose-Headers"] + assert_match(/traceparent;desc="00-\w{32}-\w{16}-01"/, response_headers["Server-Timing"]) + end + end +end diff --git a/test/test_helpers/app_config.rb b/test/test_helpers/app_config.rb new file mode 100644 index 0000000..c20cd8a --- /dev/null +++ b/test/test_helpers/app_config.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +class Application < Rails::Application; end +require 'action_controller/railtie' +require_relative 'middlewares' +require_relative 'controllers' +require_relative 'routes' + +module AppConfig + extend self + + def initialize_app(use_exceptions_app: false, remove_rack_tracer_middleware: false) + new_app = Application.new + new_app.config.secret_key_base = 'secret_key_base' + + # Ensure we don't see this Rails warning when testing + new_app.config.eager_load = false + + # Prevent tests from creating log/*.log + new_app.config.logger = Logger.new(File::NULL) + + new_app.config.filter_parameters = [:param_to_be_filtered] + + case Rails.version + when /^6\.0/ + apply_rails_6_0_configs(new_app) + when /^6\.1/ + apply_rails_6_1_configs(new_app) + when /^7\./ + apply_rails_7_configs(new_app) + end + + remove_rack_middleware(new_app) if remove_rack_tracer_middleware + add_exceptions_app(new_app) if use_exceptions_app + add_middlewares(new_app) + + new_app.initialize! + + draw_routes(new_app) + + new_app + end + + private + + def remove_rack_middleware(application) + application.middleware.delete( + OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware + ) + end + + def add_exceptions_app(application) + application.config.exceptions_app = lambda do |env| + ExceptionsController.action(:show).call(env) + end + end + + def add_middlewares(application) + application.middleware.insert_after( + ActionDispatch::DebugExceptions, + ExceptionRaisingMiddleware + ) + + application.middleware.insert_after( + ActionDispatch::DebugExceptions, + RedirectMiddleware + ) + end + + def apply_rails_6_0_configs(application) + # Required in Rails 6 + application.config.hosts << 'example.org' + # Creates a lot of deprecation warnings on subsequent app initializations if not explicitly set. + application.config.action_view.finalize_compiled_template_methods = ActionView::Railtie::NULL_OPTION + end + + def apply_rails_6_1_configs(application) + # Required in Rails 6 + application.config.hosts << 'example.org' + end + + def apply_rails_7_configs(application) + # Required in Rails 7 + application.config.hosts << 'example.org' + + # Unfreeze values which may have been frozen on previous initializations. + ActiveSupport::Dependencies.autoload_paths = + ActiveSupport::Dependencies.autoload_paths.dup + ActiveSupport::Dependencies.autoload_once_paths = + ActiveSupport::Dependencies.autoload_once_paths.dup + end +end diff --git a/test/test_helpers/controllers.rb b/test/test_helpers/controllers.rb new file mode 100644 index 0000000..1f58e14 --- /dev/null +++ b/test/test_helpers/controllers.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'controllers/example_controller' +require_relative 'controllers/exceptions_controller' diff --git a/test/test_helpers/controllers/example_controller.rb b/test/test_helpers/controllers/example_controller.rb new file mode 100644 index 0000000..f4a3f88 --- /dev/null +++ b/test/test_helpers/controllers/example_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +class ExampleController < ActionController::Base + include ::Rails.application.routes.url_helpers + + def ok + render plain: "OK" + end + + def internal_server_error + raise :internal_server_error + end +end diff --git a/test/test_helpers/controllers/exceptions_controller.rb b/test/test_helpers/controllers/exceptions_controller.rb new file mode 100644 index 0000000..5466983 --- /dev/null +++ b/test/test_helpers/controllers/exceptions_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +class ExceptionsController < ActionController::Base + def show + render plain: 'oops', status: :internal_server_error + end +end diff --git a/test/test_helpers/middlewares.rb b/test/test_helpers/middlewares.rb new file mode 100644 index 0000000..2e09386 --- /dev/null +++ b/test/test_helpers/middlewares.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'middlewares/exception_raising_middleware' +require_relative 'middlewares/redirect_middleware' diff --git a/test/test_helpers/middlewares/exception_raising_middleware.rb b/test/test_helpers/middlewares/exception_raising_middleware.rb new file mode 100644 index 0000000..257cb69 --- /dev/null +++ b/test/test_helpers/middlewares/exception_raising_middleware.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +class ExceptionRaisingMiddleware + def initialize(app, _options = {}) + @app = app + end + + def call(env) + raise 'a little hell' if should_raise?(env) + + @app.call(env) + end + + private + + def should_raise?(env) + env['PATH_INFO'] == '/exception' || env['QUERY_STRING'].include?('raise_in_middleware') + end +end diff --git a/test/test_helpers/middlewares/redirect_middleware.rb b/test/test_helpers/middlewares/redirect_middleware.rb new file mode 100644 index 0000000..c531fe3 --- /dev/null +++ b/test/test_helpers/middlewares/redirect_middleware.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +class RedirectMiddleware + def initialize(app, _options = {}) + @app = app + end + + def call(env) + return [307, {}, 'Temporary Redirect'] if should_redirect?(env) + + @app.call(env) + end + + private + + def should_redirect?(env) + env['PATH_INFO'] == '/redirection' || env['QUERY_STRING'].include?('redirect_in_middleware') + end +end diff --git a/test/test_helpers/routes.rb b/test/test_helpers/routes.rb new file mode 100644 index 0000000..88f0565 --- /dev/null +++ b/test/test_helpers/routes.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +def draw_routes(rails_app) + rails_app.routes.draw do + get '/ok', to: 'example#ok' + get '/internal_server_error', to: 'example#internal_server_error' + end +end