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

Add Rails middleware option #552

Merged
merged 3 commits into from
Sep 20, 2018
Merged

Conversation

delner
Copy link
Contributor

@delner delner commented Sep 20, 2018

As per #149, Rails instrumentation is auto-injecting a Railtie and trace middleware into the Rails application by virtue of loading. This means that it isn't really possible to disable trace middleware in this case.

This pull request seeks to make Rails instrumentation optional, by:

  • Preventing the Railtie by loading automatically, and apply instrumentation from the Rails patcher instead.
  • Add a middleware option, which defaults to true. When set to false, trace middleware will not be added to the application.

This would be good to have for users who want to use instrumentation in Rails apps without instrumenting Rails, and for OpenTracing users who'd want to prevent conflicting middleware from running in their Rails applications.

@delner delner added integrations Involves tracing integrations feature Involves a product feature labels Sep 20, 2018
@delner delner added this to the 0.16.0 milestone Sep 20, 2018
@delner delner self-assigned this Sep 20, 2018
@delner
Copy link
Contributor Author

delner commented Sep 20, 2018

We were given a choice in this pull request on how users should enable Datadog tracing with Rails:

  1. Require users to add require 'ddtrace/contrib/rails/railtie' to their application.rb file. This is the "Rails way" of dealing with Railties. The Railtie must be loaded before the application is defined in application.rb, which is before our own Rails initializer can run.
  2. Do not use Railties to load, and instead allow Datadog.configure { |c| c.use :rails } in a datadog.rb initializer file to inject the proper instrumentation. This uses the ActiveSupport.load_hooks instead to inject tracing.

In this pull request I ultimately decided to purse option 2. My main motivations here were to avoid a breaking change (by requiring users to update their applications with a require to receive Rails tracing), and instead preserve our Datadog.configure convention for patching as much as possible.

Copy link
Contributor Author

@delner delner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some notes about what's going on here.

@@ -39,7 +39,8 @@ def patch
@patched = true
end

if !@middleware_patched && get_option(:middleware_names)
if (!instance_variable_defined?(:@middleware_patched) || !@middleware_patched) \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prevents a deprecation warning.

# Add a callback hook to add the trace middleware before the application initializes.
# Otherwise the middleware stack will be frozen.
do_once(:rails_before_initialize_hook) do
::ActiveSupport.on_load(:before_initialize) do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is equivalent to the Railtie config.before_initialize call. Middleware previously was being added to the Rails global configuration. By putting it in this callback, we can make it load at the appropriate time instead. Has the additional benefit of making our Rails test app setup simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


# Add a callback hook to finish configuring the tracer after the application is initialized.
# We need to wait for some things, like application name, middleware stack, etc.
do_once(:rails_after_initialize_hook) do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is equivalent to the Railtie config.after_initialize call. This is basically what we had before in the Railtie.

# Add instrumentation to Rails components
Datadog::Contrib::Rails::ActionController.instrument
Datadog::Contrib::Rails::ActionView.instrument
Datadog::Contrib::Rails::ActiveSupport.instrument
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: its important we do this after_initialize because it depends on the application to be loaded to properly instrument.

@@ -49,5 +87,3 @@ def compatible?
end
end
end

require 'ddtrace/contrib/rails/railtie' if Datadog.registry[:rails].compatible?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this line effectively nullifies the Railtie, and also prevents the instrumentation from forcefully loading. Very important.

# it catches and swallows the error. If it's too far after, custom middleware can find itself
# between, and raise exceptions that don't end up getting tagged on the request properly (e.g lost stack trace.)
config.app_middleware.insert_after(ActionDispatch::ShowExceptions, Datadog::Contrib::Rails::ExceptionMiddleware)
# Add the trace middleware to the application stack
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the Railtie to use proper callbacks. Users could use require 'ddtrace/contrib/rails/railtie' directly if they wanted, but shouldn't have to.

@@ -113,16 +112,10 @@ def draw_test_routes!(mapper)
def reset_rails_configuration!
Rails.class_variable_set(:@@application, nil)
Rails::Application.class_variable_set(:@@instance, nil)
Rails::Railtie::Configuration.class_variable_set(:@@app_middleware, app_middleware)
if Rails::Railtie::Configuration.class_variable_defined?(:@@app_middleware)
Rails::Railtie::Configuration.class_variable_set(:@@app_middleware, Rails::Configuration::MiddlewareStackProxy.new)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to carry over the trace middleware on the global constant in order to preserve it between tests. But now that middleware configuration is wrapped in a proper callback, we can now reset the middleware completely and allow the tests to re-add it more naturally.

@pawelchcki
Copy link
Contributor

Awesome Stuff! Thanks for looking into it - not autoloading rails instrumentation is the way to go!

# Add a callback hook to add the trace middleware before the application initializes.
# Otherwise the middleware stack will be frozen.
do_once(:rails_before_initialize_hook) do
::ActiveSupport.on_load(:before_initialize) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@delner delner merged commit 6d85d00 into 0.16-dev Sep 20, 2018
@delner delner deleted the feature/rails_middleware_option branch September 20, 2018 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Involves a product feature integrations Involves tracing integrations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants