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

Implement Sinatra Env and Headers extensions #441

Merged
merged 5 commits into from
Jun 5, 2018

Conversation

delner
Copy link
Contributor

@delner delner commented May 31, 2018

As a suggestion for #427, I felt inspired to try a spin on @pawelchcki 's idea.

This pull request implements a Sinatra::Env and Sinatra::Headers extension, which decorates the env passed into the middleware with some Sinatra-tracing specific features. The benefit of doing this means our utility features become available, and context specific, across all the components of our Sinatra tracing, wherever env is available.

Another potentially cool benefit is the Env and Headers modules aren't very Sinatra specific: they could be extracted and used for any Rack apps, so this might aid our efforts to undertake that refactor as well.

This PR would be intended to be merged into #427 . @pawelchcki , let me know what you think!

@delner delner added integrations Involves tracing integrations dev/refactor Involves refactoring existing components labels May 31, 2018
@delner delner self-assigned this May 31, 2018
@delner delner requested a review from pawelchcki May 31, 2018 21:54
@delner delner force-pushed the feature/sinatra_env_headers_extensions branch from 2018809 to d4175f2 Compare May 31, 2018 22:05
def initialize(app)
@app = app
end

def call(env)
span = RequestSpan.span!(env)
# Extend the Env with Sinatra tracing functions
env.extend(Sinatra::Env)
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting approach. I'm worried however that this might introduce somewhat unstable API to rack envs.

Imagine another rack middleware used in the same application. That does use env.datadog_span accessor methods. It all works well as long as the order of MWs is TracerMiddleware -> OtherMiddleware.

When the order gets reversed then calling env.datadog_span will throw NoMethodError.

As such I think it would be much safer to use module functions to share the code operating on env.
If avoiding object allocations wasn't one of our goals, I would suggest using Composition over Inheritance. Because env.extend is Ruby specific runtime inheritance.

In general also extending objects this way should be avoided due to reasons mentioned above, where order of execution change would mean the code could suddenly throw NoMethodError.

Copy link
Contributor Author

@delner delner Jun 4, 2018

Choose a reason for hiding this comment

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

Fair concern about datadog_span, regarding the change in middleware order. However, I don't think whether you extend or call the module will make a difference anyways.

My reason for saying so is imagine this second middleware that was dependent on env.datadog_span. Instead, it will call to a function like Sinatra::Env.datadog_span(env) which won't raise a NoMethodError now, but will still fail because the span will be missing from the env because TracerMiddleware didn't run yet. In this case, OtherMiddleware still depends on TracerMiddleware no matter how you articulate it. And if there is such a dependency, it's reasonable that the dependent be responsible for placing itself in the right position (i.e. the developer should be responsible for configuring their middleware stack correctly.)

However, I will yield that extend is a bit unorthodox, and by using it, you might have inconsistency between objects of the same type; something in the general sense that might not sit well with many developers. As such, I think it is reasonable to remove the extend implementation in favor of something simpler, albeit not as elegant.

Copy link
Contributor

@pawelchcki pawelchcki left a comment

Choose a reason for hiding this comment

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

Thanks @delner for creating this PR making the code more reusable.

However overall obj.extend can cause problems down the line by introducing API's that change based on order of execution. Using that in production code should be carefully weighed.

@delner
Copy link
Contributor Author

delner commented Jun 4, 2018

@pawelchcki Thanks for the feedback!

I've altered the modules to implement their functions as 'static', instead of being extended by the Rack objects directly. Will this resolve your concerns? Feel free to take another look!

Copy link
Contributor

@pawelchcki pawelchcki left a comment

Choose a reason for hiding this comment

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

Looks good, thanks for making this PR @delner!.

I've left a few minor remarks about TODOs and code comments describing what code below is doing.

One suggestion, would you mind rebasing this on 0.13-dev? So that this change would not block merging of
#427

I'm still happy to merge this once the remarks are addressed I only think we could do it after #427 is merged since this change is quite self contained.

module Contrib
module Sinatra
# Gets and sets trace information from a Rack Env
# TODO: Extract me?
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we handle this TODO i.e. implement the suggestion or create Trello card to track this ?

Copy link
Contributor Author

@delner delner Jun 5, 2018

Choose a reason for hiding this comment

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

I can remove this comment for now. We should extract this in a future PR, ideally as a part of the Rack/Sinatra header refactor PR you had opened. (#432)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll add that there as a TODO so we won't lose this idea

module Contrib
module Sinatra
# Gets and sets trace information from a Rack headers Hash
# TODO: Extract me?
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we handle this TODO as well ?

private

def tracer
configuration[:tracer]
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

service: configuration[:service_name],
span_type: Datadog::Ext::HTTP::TYPE
) do |span|
# Set the span on the Env
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment necessary? It seems to duplicate what the line below says.
Maybe we could thing about renaming the method below so that this comment is necessary ?

ensure
span.finish
end
# Tag request headers
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment necessary? It seems to duplicate what the line below says.
Maybe we could thing about renaming the method below so that this comment is necessary ?

if env.key?(rack_header)
result[Datadog::Ext::HTTP::RequestHeaders.to_tag(header)] = env[rack_header]
end
# Tag response headers
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment necessary? It seems to duplicate what the line below says.
Maybe we could thing about renaming the method below so that this comment is necessary ?

end

# Return response
Copy link
Contributor

Choose a reason for hiding this comment

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

Since ruby has implicit returns and and I think we enforce them in Rubocop, maybe we should consider removing that restriction so you could explicitly state return [status, headers, response_body] if you feel it improves readablity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose you're right. I'll remove all of these extraneous comments if you don't think it helps.

@delner
Copy link
Contributor Author

delner commented Jun 5, 2018

@pawelchcki I think that's reasonable. We can merge this one after.

@delner delner force-pushed the feature/sinatra_env_headers_extensions branch from de49de7 to 7905dd6 Compare June 5, 2018 15:18
@delner delner changed the base branch from feature/sinatra_add_request_id to 0.13-dev June 5, 2018 15:19
@delner
Copy link
Contributor Author

delner commented Jun 5, 2018

@pawelchcki Fixed the comments and rebased on 0.13-dev. This one should be ready for another review.

@pawelchcki
Copy link
Contributor

Thanks for making these changes @delner. Looks really good now! Lets merge it.

@delner delner merged commit 851ad82 into 0.13-dev Jun 5, 2018
@delner delner deleted the feature/sinatra_env_headers_extensions branch June 5, 2018 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dev/refactor Involves refactoring existing components integrations Involves tracing integrations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants