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

Improve Rails middleware error handling #345

Merged
merged 11 commits into from
Feb 22, 2018

Conversation

delner
Copy link
Contributor

@delner delner commented Feb 13, 2018

There are currently two issues with Rails middleware errors:

  1. Exceptions raised in Rails middleware which sits above Datadog::Contrib::Rails::ExceptionMiddleware but below ActionDispatch::ShowExceptions can result in the exception stack trace being excluded from the trace.

  2. Exceptions raised in Rails middleware that are equivalent to 4XX errors (e.g. ActionController::RoutingError) can cause the trace to incorrectly be tagged as an error.

This pull request aims to address both of these, respectively by:

  1. Placing Datadog::Contrib::Rails::ExceptionMiddleware directly below ActionDispatch::ShowExceptions middleware. Users should take care to avoid placing custom middleware between these two, otherwise they'll see this issue re-occur.
  2. Changing the Datadog::Contrib::Rails::ExceptionMiddleware to only tag traces whose exceptions have known equivalent statuses to 5XX. Exceptions that are not whitelisted via config.action_dispatch.rescue_responses will be tagged as errors by default. This includes custom errors: if custom middleware raises custom exceptions that should not be tagged as an error, then they should be added to config.action_dispatch.rescue_responses in application.rb.

With the changes in this pull request, Rails applications produce the following behavior:

Trace for exception raised from custom Rails middleware (with custom ErrorsController):

class SampleMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env)
    raise NotImplementedError.new
  end
end

module MyRailsApp
  class Application < Rails::Application
    # Datadog::Contrib::Rails::ExceptionMiddleware
    config.middleware.insert_after Datadog::Contrib::Rails::ExceptionMiddleware, SampleMiddleware
  end
end

screen shot 2018-02-13 at 1 09 11 pm

Trace for a known 'not found' exception raised from custom Rails middleware (with custom ErrorsController):

class SampleMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env)
    raise ActionController::RoutingError.new('/missing_route')
  end
end

module MyRailsApp
  class Application < Rails::Application
    # Datadog::Contrib::Rails::ExceptionMiddleware
    config.middleware.insert_after Datadog::Contrib::Rails::ExceptionMiddleware, SampleMiddleware
  end
end

screen shot 2018-02-13 at 1 12 40 pm

Trace for custom exception equivalent to 'not found' raised from custom Rails middleware (with custom ErrorsController):

class CustomError < StandardError
end

class SampleMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env)
    raise CustomError.new
  end
end

module DdRailsTest
  class Application < Rails::Application
    # Datadog::Contrib::Rails::ExceptionMiddleware
    config.middleware.insert_after Datadog::Contrib::Rails::ExceptionMiddleware, SampleMiddleware

    config.action_dispatch.rescue_responses.merge!(
      "CustomError" => :not_found
    )
  end
end

screen shot 2018-02-13 at 1 16 15 pm

@delner delner added bug Involves a bug integrations Involves tracing integrations do-not-merge/WIP Not ready for merge labels Feb 13, 2018
@delner delner added this to the 0.11.3 milestone Feb 13, 2018
@delner delner self-assigned this Feb 13, 2018
@delner delner requested a review from palazzem February 13, 2018 18:18
@@ -158,7 +158,7 @@ class TracingControllerTest < ActionController::TestCase
assert_equal(1, span.status, 'span should be flagged as an error')
assert_equal('ZeroDivisionError', span.get_tag('error.type'), 'type should contain the class name of the error')
assert_equal('divided by 0', span.get_tag('error.msg'), 'msg should state we tried to divided by 0')
assert_nil(span.get_tag('error.stack'))
refute_nil(span.get_tag('error.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.

@palazzem We were asserting that we should not put stack traces on the controller spans when it raises an error, only its parent rack.request span. Is this right? Seems like it isn't.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this was done to avoid having the stacktrace in 2 spans (the rack and the controller)

@delner delner force-pushed the feature/improve_rails_middleware_error_handling branch from ad5512b to d21fcb6 Compare February 13, 2018 22:29
@delner
Copy link
Contributor Author

delner commented Feb 13, 2018

Working on adding some middleware tests, to guard against regression. They're a bit trickier to implement, but I think I'm close to having something working.

@dasch
Copy link
Contributor

dasch commented Feb 16, 2018

❤️

@delner
Copy link
Contributor Author

delner commented Feb 16, 2018

Middleware tests are in this pull request: #347

@delner delner force-pushed the feature/improve_rails_middleware_error_handling branch from d21fcb6 to b589fd3 Compare February 16, 2018 22:36
@delner delner mentioned this pull request Feb 20, 2018
@delner delner force-pushed the feature/improve_rails_middleware_error_handling branch 3 times, most recently from 52306ce to 7c4cf8a Compare February 21, 2018 18:06
@delner delner force-pushed the feature/improve_rails_middleware_error_handling branch from 7c4cf8a to bad4e73 Compare February 21, 2018 19:51
Copy link
Contributor

@palazzem palazzem left a comment

Choose a reason for hiding this comment

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

I think we should merge the other tests PR, be sure this big PR is properly rebased and move it in 0.12.x release branch.

Left one question just to be sure we're properly testing everything.

if defined?(::ActionDispatch::ExceptionWrapper)
# Gets the equivalent status code for the exception (not all are 5XX)
# You can add custom errors via `config.action_dispatch.rescue_responses`
status = ::ActionDispatch::ExceptionWrapper.status_code_for_exception(exception.class.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this compatibility has been tested in the other PR, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it has. And it's kind of a copy-paste from the Rails code we had before.

@palazzem palazzem removed the do-not-merge/WIP Not ready for merge label Feb 22, 2018
@delner delner merged commit 98ab52f into master Feb 22, 2018
@palazzem palazzem deleted the feature/improve_rails_middleware_error_handling branch February 23, 2018 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Involves a bug integrations Involves tracing integrations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants