# frozen_string_literal: true require 'rack' module OpenTracing module Instrumentation module Rack # TraceMiddleware observer rack requests. class TraceMiddleware def initialize( app, logger: nil, command_name: 'rack', http_tagger: HttpTagger.new, tracer: OpenTracing.global_tracer ) @app = app @logger = logger @command_name = command_name @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 attr_reader :http_tagger attr_reader :tracer attr_reader :logger def trace_request(env) extracted_ctx = tracer.extract(OpenTracing::FORMAT_RACK, env) logger&.info('Tracing context extracted') if extracted_ctx 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(&span.method(:set_tag)) 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