# frozen_string_literal: true require 'rack' module OpenTracing module Instrumentation module Rack # TraceMiddleware observer rack requests. class TraceMiddleware DEFAULT_COMMAND_NAME_BUILDER = StaticCommandNameBuilder.new def initialize( app, logger: nil, command_name_builder: DEFAULT_COMMAND_NAME_BUILDER, http_tagger: HttpTagger.new, tracer: OpenTracing.global_tracer ) @app = app @logger = logger @command_name_builder = command_name_builder @http_tagger = http_tagger @tracer = tracer end def call(env) trace_request(env) do |span| catch_error(span) do status, headers, body = app.call(env) set_response_tags(span, status, headers) [status, headers, body] end end end private attr_reader :app, :http_tagger, :tracer, :logger def trace_request(env) extracted_ctx = tracer.extract(OpenTracing::FORMAT_RACK, env) logger&.info('Tracing context extracted') if extracted_ctx command_name = @command_name_builder.build_command_name(env) tracer.start_active_span( command_name, child_of: extracted_ctx, tags: request_tags(env), ) do |scope| yield(scope.span) end end def catch_error(span) yield rescue StandardError => e logger&.error(e) error_tag(span, e) raise end REQUEST_URI = 'REQUEST_URI' def request_tags(env) { 'http.method' => env[::Rack::REQUEST_METHOD], 'http.url' => env[REQUEST_URI], 'span.kind' => 'server', }.merge(http_tagger.request_tags(env)) end def set_response_tags(span, status, headers) set_status_tag(span, status) set_header_tags(span, headers) end def set_status_tag(span, status) span.set_tag('http.status', status) end def set_header_tags(span, headers) http_tagger .response_tags(headers) .each { |(key, value)| span.set_tag(key, value) } end def error_tag(span, error) span.set_tag('error', true) span.log_kv('error.kind': error.class.to_s) end end end end end