-
Notifications
You must be signed in to change notification settings - Fork 375
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
Squash nested sinatra/rack request span #2217
Changes from 16 commits
4a765ff
4d57dac
1d88b01
504c0b1
6ec77d6
25fd552
72b2569
19f5d78
e2db6b1
49f89bc
49b667e
406b38b
20b24e5
cf11e06
c918a12
02fbc40
e4c22c4
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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
require 'sinatra/base' | ||
require 'ddtrace' | ||
|
||
class Parent < Sinatra::Base | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,4 @@ | ||
require 'ddtrace' | ||
require_relative 'app/acme' | ||
|
||
use Datadog::Tracing::Contrib::Rack::TraceMiddleware | ||
|
||
run Acme |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,16 +56,18 @@ def call(env) | |
# Find out if this is rack within rack | ||
previous_request_span = env[Ext::RACK_ENV_REQUEST_SPAN] | ||
|
||
return @app.call(env) if previous_request_span | ||
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. Does this work okay with AppSec? We're now short circuiting Rack instrumentation whenever there was a Rack span before. My understanding was AppSec produces its own Rack span as well... would it get skipped? CC @lloeki 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. Oh my I think this is going to be a problem. In fact I think the whole PR might be. 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. After discussion, it should be fine with a passing test suite. |
||
|
||
# Extract distributed tracing context before creating any spans, | ||
# so that all spans will be added to the distributed trace. | ||
if configuration[:distributed_tracing] && previous_request_span.nil? | ||
if configuration[:distributed_tracing] | ||
trace_digest = Tracing::Propagation::HTTP.extract(env) | ||
Tracing.continue_trace!(trace_digest) | ||
end | ||
|
||
# Create a root Span to keep track of frontend web servers | ||
# (i.e. Apache, nginx) if the header is properly set | ||
frontend_span = compute_queue_time(env) if previous_request_span.nil? | ||
frontend_span = compute_queue_time(env) | ||
|
||
trace_options = { span_type: Tracing::Metadata::Ext::HTTP::TYPE_INBOUND } | ||
trace_options[:service] = configuration[:service_name] if configuration[:service_name] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,12 @@ module Sinatra | |
module Env | ||
module_function | ||
|
||
def datadog_span(env, app) | ||
request_span = env[Ext::RACK_ENV_REQUEST_SPAN] | ||
request_span && request_span[app] | ||
def datadog_span(env) | ||
env[Ext::RACK_ENV_SINATRA_REQUEST_SPAN] | ||
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. We no longer stored multiple spans, but one. |
||
end | ||
|
||
def set_datadog_span(env, app, span) | ||
hash = (env[Ext::RACK_ENV_REQUEST_SPAN] ||= {}) | ||
hash[app] = span | ||
def set_datadog_span(env, span) | ||
env[Ext::RACK_ENV_SINATRA_REQUEST_SPAN] = span | ||
end | ||
|
||
def request_header_tags(env, headers) | ||
|
@@ -40,24 +38,15 @@ def header_to_rack_header(name) | |
"HTTP_#{name.to_s.upcase.gsub(/[-\s]/, '_')}" | ||
end | ||
|
||
# Was a Sinatra already traced in this request? | ||
# We don't want to create spans for intermediate Sinatra | ||
# middlewares that don't match the request at hand. | ||
def middleware_traced?(env) | ||
TonyCTHsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
env[Ext::RACK_ENV_MIDDLEWARE_TRACED] | ||
end | ||
|
||
def set_middleware_traced(env, bool) | ||
env[Ext::RACK_ENV_MIDDLEWARE_TRACED] = bool | ||
end | ||
def route_path(env, use_script_names: Datadog.configuration.tracing[:sinatra][:resource_script_names]) | ||
return unless env['sinatra.route'] | ||
|
||
# The start time of the top-most Sinatra middleware. | ||
def middleware_start_time(env) | ||
env[Ext::RACK_ENV_MIDDLEWARE_START_TIME] | ||
end | ||
|
||
def set_middleware_start_time(env, time = Time.now.utc) | ||
env[Ext::RACK_ENV_MIDDLEWARE_START_TIME] = time | ||
_, path = env['sinatra.route'].split(' ', 2) | ||
if use_script_names | ||
env['SCRIPT_NAME'].to_s + path | ||
else | ||
path | ||
end | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,7 @@ module Ext | |
ENV_ENABLED = 'DD_TRACE_SINATRA_ENABLED'.freeze | ||
ENV_ANALYTICS_ENABLED = 'DD_TRACE_SINATRA_ANALYTICS_ENABLED'.freeze | ||
ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_SINATRA_ANALYTICS_SAMPLE_RATE'.freeze | ||
RACK_ENV_REQUEST_SPAN = 'datadog.sinatra_request_span'.freeze | ||
RACK_ENV_MIDDLEWARE_START_TIME = 'datadog.sinatra_middleware_start_time'.freeze | ||
RACK_ENV_MIDDLEWARE_TRACED = 'datadog.sinatra_middleware_traced'.freeze | ||
Comment on lines
-14
to
-15
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. Not used 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. Should be careful renaming/removing constants like this. This may constitute a breaking change. Might be safer to flag this as deprecated and keep the existing constants for now. |
||
RACK_ENV_SINATRA_REQUEST_SPAN = 'datadog.sinatra_request_span'.freeze | ||
SPAN_RENDER_TEMPLATE = 'sinatra.render_template'.freeze | ||
SPAN_REQUEST = 'sinatra.request'.freeze | ||
SPAN_ROUTE = 'sinatra.route'.freeze | ||
|
@@ -25,6 +23,12 @@ module Ext | |
TAG_SCRIPT_NAME = 'sinatra.script_name'.freeze | ||
TAG_TEMPLATE_ENGINE = 'sinatra.template_engine'.freeze | ||
TAG_TEMPLATE_NAME = 'sinatra.template_name'.freeze | ||
|
||
# === Deprecated: To be removed === | ||
RACK_ENV_REQUEST_SPAN = 'datadog.sinatra_request_span'.freeze | ||
RACK_ENV_MIDDLEWARE_START_TIME = 'datadog.sinatra_middleware_start_time'.freeze | ||
RACK_ENV_MIDDLEWARE_TRACED = 'datadog.sinatra_middleware_traced'.freeze | ||
# === Deprecated: To be removed === | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,69 +17,12 @@ module Sinatra | |
# Datadog::Tracing::Contrib::Sinatra::Tracer is a Sinatra extension which traces | ||
# requests. | ||
module Tracer | ||
def route(verb, action, *) | ||
TonyCTHsu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Keep track of the route name when the app is instantiated for an | ||
# incoming request. | ||
condition do | ||
# If the option to prepend script names is enabled, then | ||
# prepend the script name from the request onto the action. | ||
# | ||
# DEV: env['sinatra.route'] already exists with very similar information, | ||
# DEV: but doesn't account for our `resource_script_names` logic. | ||
# | ||
@datadog_route = if Datadog.configuration.tracing[:sinatra][:resource_script_names] | ||
"#{request.script_name}#{action}" | ||
else | ||
action | ||
end | ||
end | ||
|
||
super | ||
end | ||
|
||
def self.registered(app) | ||
app.use TracerMiddleware, app_instance: app | ||
|
||
app.after do | ||
next unless Tracing.enabled? | ||
|
||
span = Sinatra::Env.datadog_span(env, app) | ||
|
||
# TODO: `route` should *only* be populated if @datadog_route is defined. | ||
# TODO: If @datadog_route is not defined, then this Sinatra app is not responsible | ||
# TODO: for handling this request. | ||
# TODO: | ||
# TODO: This change would be BREAKING for any Sinatra app (classic or modular), | ||
# TODO: as it affects the `resource` value for requests not handled by the Sinatra app. | ||
# TODO: Currently we use "#{method} #{path}" in such aces, but `path` is the raw, | ||
# TODO: high-cardinality HTTP path, and can contain PII. | ||
# TODO: | ||
# TODO: The value we should use as the `resource` when the Sinatra app is not | ||
# TODO: responsible for the request is a tricky subject. | ||
# TODO: The best option is a value that clearly communicates that this app did not | ||
# TODO: handle this request. It's important to keep in mind that an unhandled request | ||
# TODO: by this Sinatra app might still be handled by another Rack middleware (which can | ||
# TODO: be a Sinatra app itself) or it might just 404 if not handled at all. | ||
# TODO: | ||
# TODO: A possible value for `resource` could set a high level description, e.g. | ||
# TODO: `request.request_method`, given we don't have the response object available yet. | ||
Comment on lines
-48
to
-65
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. Yes! Nice removal! |
||
route = if defined?(@datadog_route) | ||
@datadog_route | ||
else | ||
# Fallback in case no routes have matched | ||
request.path | ||
end | ||
|
||
span.resource = "#{request.request_method} #{route}" | ||
span.set_tag(Ext::TAG_ROUTE_PATH, route) | ||
end | ||
end | ||
|
||
# Method overrides for Sinatra::Base | ||
module Base | ||
MISSING_REQUEST_SPAN_ONLY_ONCE = Core::Utils::OnlyOnce.new | ||
private_constant :MISSING_REQUEST_SPAN_ONLY_ONCE | ||
|
||
def render(engine, data, *) | ||
return super unless Tracing.enabled? | ||
|
||
|
@@ -102,19 +45,21 @@ def render(engine, data, *) | |
|
||
# Invoked when a matching route is found. | ||
# This method yields directly to user code. | ||
# rubocop:disable Metrics/MethodLength | ||
def route_eval | ||
configuration = Datadog.configuration.tracing[:sinatra] | ||
return super unless Tracing.enabled? | ||
|
||
datadog_route = Sinatra::Env.route_path(env) | ||
|
||
Tracing.trace( | ||
Ext::SPAN_ROUTE, | ||
service: configuration[:service_name], | ||
span_type: Tracing::Metadata::Ext::HTTP::TYPE_INBOUND, | ||
resource: "#{request.request_method} #{@datadog_route}", | ||
resource: "#{request.request_method} #{datadog_route}", | ||
) do |span, trace| | ||
span.set_tag(Ext::TAG_APP_NAME, settings.name || settings.superclass.name) | ||
span.set_tag(Ext::TAG_ROUTE_PATH, @datadog_route) | ||
span.set_tag(Ext::TAG_ROUTE_PATH, datadog_route) | ||
|
||
if request.script_name && !request.script_name.empty? | ||
span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name) | ||
end | ||
|
@@ -124,32 +69,15 @@ def route_eval | |
|
||
trace.resource = span.resource | ||
|
||
sinatra_request_span = | ||
if self.class <= ::Sinatra::Application # Classic style (top-level) application | ||
Sinatra::Env.datadog_span(env, ::Sinatra::Application) | ||
else | ||
Sinatra::Env.datadog_span(env, self.class) | ||
end | ||
if sinatra_request_span | ||
sinatra_request_span.resource = span.resource | ||
else | ||
MISSING_REQUEST_SPAN_ONLY_ONCE.run do | ||
Datadog.logger.warn do | ||
'Sinatra integration is misconfigured, reported traces will be missing request metadata ' \ | ||
'such as path and HTTP status code. ' \ | ||
'Did you forget to add `register Datadog::Tracing::Contrib::Sinatra::Tracer` to your ' \ | ||
'`Sinatra::Base` subclass? ' \ | ||
'See <https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#sinatra> for more details.' | ||
end | ||
end | ||
end | ||
sinatra_request_span = Sinatra::Env.datadog_span(env) | ||
|
||
sinatra_request_span.resource = span.resource | ||
|
||
Contrib::Analytics.set_measured(span) | ||
|
||
super | ||
end | ||
end | ||
# rubocop:enable Metrics/MethodLength | ||
end | ||
end | ||
end | ||
|
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.
There a few
pry
references left in this PR. Should be be kept?