-
Notifications
You must be signed in to change notification settings - Fork 373
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
Fix ActionView tracing contexts when span limit is reached #447
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,11 @@ module Datadog | |
module RailsRendererPatcher | ||
include Datadog::Patcher | ||
|
||
SPAN_NAME_RENDER_PARTIAL = 'rails.render_partial'.freeze | ||
SPAN_NAME_RENDER_TEMPLATE = 'rails.render_template'.freeze | ||
TAG_LAYOUT = 'rails.layout'.freeze | ||
TAG_TEMPLATE_NAME = 'rails.template_name'.freeze | ||
|
||
module_function | ||
|
||
def patch_renderer | ||
|
@@ -24,25 +29,22 @@ def patch_renderer | |
end | ||
|
||
def patch_template_renderer(klass) | ||
# rubocop:disable Metrics/BlockLength | ||
do_once(:patch_template_renderer) do | ||
klass.class_eval do | ||
def render_with_datadog(*args, &block) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can methods defined here be extracted to a module? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory, they could. I didn't want to do that here because doing so would depend on I would like to do this, but we should do it in a separate PR, because its more important we have a stable, verified fix to ship first. Then when that's out, we can clean up the implementation to make things look cleaner. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
# create a tracing context and start the rendering span | ||
# NOTE: Rails < 3.1 compatibility: preserve the tracing | ||
# context when a partial is rendered | ||
@tracing_context ||= {} | ||
if @tracing_context.empty? | ||
Datadog::Contrib::Rails::ActionView.start_render_template(tracing_context: @tracing_context) | ||
# NOTE: This check exists purely for Rails 3.0 compatibility. | ||
# The 'if' part can be removed when support for Rails 3.0 is removed. | ||
if active_datadog_span | ||
render_without_datadog(*args, &block) | ||
else | ||
datadog_tracer.trace( | ||
Datadog::RailsRendererPatcher::SPAN_NAME_RENDER_TEMPLATE, | ||
span_type: Datadog::Ext::HTTP::TEMPLATE | ||
) do |span| | ||
with_datadog_span(span) { render_without_datadog(*args, &block) } | ||
end | ||
end | ||
|
||
render_without_datadog(*args, &block) | ||
rescue Exception => e | ||
# attach the exception to the tracing context if any | ||
@tracing_context[:exception] = e | ||
raise e | ||
ensure | ||
# ensure that the template `Span` is finished even during exceptions | ||
Datadog::Contrib::Rails::ActionView.finish_render_template(tracing_context: @tracing_context) | ||
end | ||
|
||
def render_template_with_datadog(*args) | ||
|
@@ -60,8 +62,19 @@ def render_template_with_datadog(*args) | |
else | ||
layout_name.try(:[], 'virtual_path') | ||
end | ||
@tracing_context[:template_name] = template_name | ||
@tracing_context[:layout] = layout | ||
if template_name | ||
active_datadog_span.set_tag( | ||
Datadog::RailsRendererPatcher::TAG_TEMPLATE_NAME, | ||
template_name | ||
) | ||
end | ||
|
||
if layout | ||
active_datadog_span.set_tag( | ||
Datadog::RailsRendererPatcher::TAG_LAYOUT, | ||
layout | ||
) | ||
end | ||
rescue StandardError => e | ||
Datadog::Tracer.log.debug(e.message) | ||
end | ||
|
@@ -70,6 +83,21 @@ def render_template_with_datadog(*args) | |
render_template_without_datadog(*args) | ||
end | ||
|
||
private | ||
|
||
attr_accessor :active_datadog_span | ||
|
||
def datadog_tracer | ||
Datadog.configuration[:rails][:tracer] | ||
end | ||
|
||
def with_datadog_span(span) | ||
self.active_datadog_span = span | ||
yield | ||
ensure | ||
self.active_datadog_span = nil | ||
end | ||
|
||
# method aliasing to patch the class | ||
alias_method :render_without_datadog, :render | ||
alias_method :render, :render_with_datadog | ||
|
@@ -90,30 +118,23 @@ def patch_partial_renderer(klass) | |
do_once(:patch_partial_renderer) do | ||
klass.class_eval do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could methods defined here be extracted to a module as well ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same feedback as above. |
||
def render_with_datadog(*args, &block) | ||
# Create a tracing context and start the rendering span | ||
tracing_context = {} | ||
Datadog::Contrib::Rails::ActionView.start_render_partial(tracing_context: tracing_context) | ||
tracing_contexts[current_span_id] = tracing_context | ||
|
||
render_without_datadog(*args) | ||
rescue Exception => e | ||
# attach the exception to the tracing context if any | ||
tracing_contexts[current_span_id][:exception] = e | ||
raise e | ||
ensure | ||
# Ensure that the template `Span` is finished even during exceptions | ||
# Remove the existing tracing context (to avoid leaks) | ||
tracing_contexts.delete(current_span_id) | ||
|
||
# Then finish the span associated with the context | ||
Datadog::Contrib::Rails::ActionView.finish_render_partial(tracing_context: tracing_context) | ||
datadog_tracer.trace( | ||
Datadog::RailsRendererPatcher::SPAN_NAME_RENDER_PARTIAL, | ||
span_type: Datadog::Ext::HTTP::TEMPLATE | ||
) do |span| | ||
with_datadog_span(span) { render_without_datadog(*args) } | ||
end | ||
end | ||
|
||
def render_partial_with_datadog(*args) | ||
begin | ||
# update the tracing context with computed values before the rendering | ||
template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(@template.try('identifier')) | ||
tracing_contexts[current_span_id][:template_name] = template_name | ||
if template_name | ||
active_datadog_span.set_tag( | ||
Datadog::RailsRendererPatcher::TAG_TEMPLATE_NAME, | ||
template_name | ||
) | ||
end | ||
rescue StandardError => e | ||
Datadog::Tracer.log.debug(e.message) | ||
end | ||
|
@@ -122,15 +143,19 @@ def render_partial_with_datadog(*args) | |
render_partial_without_datadog(*args) | ||
end | ||
|
||
# Table of tracing contexts, one per partial/span, keyed by span_id | ||
# because there will be multiple concurrent contexts, depending on how | ||
# many partials are nested within one another. | ||
def tracing_contexts | ||
@tracing_contexts ||= {} | ||
private | ||
|
||
attr_accessor :active_datadog_span | ||
|
||
def datadog_tracer | ||
Datadog.configuration[:rails][:tracer] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
end | ||
|
||
def current_span_id | ||
Datadog.configuration[:rails][:tracer].call_context.current_span.span_id | ||
def with_datadog_span(span) | ||
self.active_datadog_span = span | ||
yield | ||
ensure | ||
self.active_datadog_span = nil | ||
end | ||
|
||
# method aliasing to patch the class | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider increasing this limit? So that adding exceptions will not be necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm okay with that. To me, this metric seems a bit arbitrary/silly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright I tried this, but it creates a bunch of other Rubocop problems in other files unrelated to this PR. I don't think we should do this right now; we have a task in the pipeline to cover Rubocop refactoring, so we should do this then.