lib/ddtrace/contrib/sinatra/tracer.rb in ddtrace-0.34.2 vs lib/ddtrace/contrib/sinatra/tracer.rb in ddtrace-0.35.0

- old
+ new

@@ -19,10 +19,14 @@ # 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[:sinatra][:resource_script_names] "#{request.script_name}#{action}" else action end @@ -30,56 +34,115 @@ super end def self.registered(app) - ::Sinatra::Base.module_eval do - def render(engine, data, *) - output = '' - tracer = Datadog.configuration[:sinatra][:tracer] - if tracer.enabled - tracer.trace(Ext::SPAN_RENDER_TEMPLATE, span_type: Datadog::Ext::HTTP::TEMPLATE) do |span| - # If data is a string, it is a literal template and we don't - # want to record it. - span.set_tag(Ext::TAG_TEMPLATE_NAME, data) if data.is_a? Symbol + app.use TracerMiddleware - # Measure service stats - Contrib::Analytics.set_measured(span) + app.after do + configuration = Datadog.configuration[:sinatra] + next unless configuration[:tracer].enabled - output = super - end - else - output = super - end + # Ensures we only create a span for the top-most Sinatra middleware. + # + # If we traced all Sinatra middleware apps, we would have a chain + # of nested spans that were not responsible for handling this request, + # adding little value to users. + next if Sinatra::Env.middleware_traced?(env) - output - end - end + span = Sinatra::Env.datadog_span(env) || Sinatra::Tracer.create_middleware_span(env, configuration) - app.use TracerMiddleware + route = if defined?(@datadog_route) + @datadog_route + else + # Fallback in case no routes have matched + request.path + end - app.before do - return unless Datadog.configuration[:sinatra][:tracer].enabled + span.resource = "#{request.request_method} #{route}" - span = Sinatra::Env.datadog_span(env) span.set_tag(Datadog::Ext::HTTP::URL, request.path) span.set_tag(Datadog::Ext::HTTP::METHOD, request.request_method) + span.set_tag(Ext::TAG_APP_NAME, app.settings.name) + span.set_tag(Ext::TAG_ROUTE_PATH, route) + if request.script_name && !request.script_name.empty? + span.set_tag(Ext::TAG_SCRIPT_NAME, request.script_name) + end + + span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status) + span.set_error(env['sinatra.error']) if response.server_error? + + Sinatra::Env.set_middleware_traced(env, true) end + end - app.after do - return unless Datadog.configuration[:sinatra][:tracer].enabled + # Initializes a span for the top-most Sinatra middleware. + def self.create_middleware_span(env, configuration) + tracer = configuration[:tracer] + span = tracer.trace( + Ext::SPAN_REQUEST, + service: configuration[:service_name], + span_type: Datadog::Ext::HTTP::TYPE_INBOUND, + start_time: Sinatra::Env.middleware_start_time(env) + ) - span = Sinatra::Env.datadog_span(env) + Sinatra::Env.set_datadog_span(env, span) + span + end - unless span - Datadog::Logger.log.error('missing request span in :after hook') - return + # Method overrides for Sinatra::Base + module Base + def render(engine, data, *) + tracer = Datadog.configuration[:sinatra][:tracer] + return super unless tracer.enabled + + tracer.trace(Ext::SPAN_RENDER_TEMPLATE, span_type: Datadog::Ext::HTTP::TEMPLATE) do |span| + span.set_tag(Ext::TAG_TEMPLATE_ENGINE, engine) + + # If data is a string, it is a literal template and we don't + # want to record it. + span.set_tag(Ext::TAG_TEMPLATE_NAME, data) if data.is_a? Symbol + + # Measure service stats + Contrib::Analytics.set_measured(span) + + super end + end - span.resource = "#{request.request_method} #{@datadog_route}" - span.set_tag(Ext::TAG_ROUTE_PATH, @datadog_route) - span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status) - span.set_error(env['sinatra.error']) if response.server_error? + # Invoked when a matching route is found. + # This method yields directly to user code. + def route_eval + configuration = Datadog.configuration[:sinatra] + tracer = configuration[:tracer] + + return super unless tracer.enabled + + # For initialization of Sinatra middleware span in order + # guarantee that the Ext::SPAN_ROUTE span being created below + # has the middleware span as its parent. + Sinatra::Tracer.create_middleware_span(env, configuration) unless Sinatra::Env.datadog_span(env) + + tracer.trace( + Ext::SPAN_ROUTE, + service: configuration[:service_name], + span_type: Datadog::Ext::HTTP::TYPE_INBOUND + ) do |span| + span.resource = "#{request.request_method} #{@datadog_route}" + + span.set_tag(Ext::TAG_APP_NAME, settings.name || settings.superclass.name) + 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 + + rack_request_span = env[Datadog::Contrib::Rack::TraceMiddleware::RACK_REQUEST_SPAN] + rack_request_span.resource = span.resource if rack_request_span + + Contrib::Analytics.set_measured(span) + + super + end end end end end end