class EpsagonRackMiddleware

Constants

EMPTY_HASH

Public Class Methods

allowed_rack_request_headers() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 42
def allowed_rack_request_headers
  @allowed_rack_request_headers ||= Array(config[:allowed_request_headers]).each_with_object({}) do |header, memo|
    memo["HTTP_#{header.to_s.upcase.gsub(/[-\s]/, '_')}"] = build_attribute_name('http.request.headers.', header)
  end
end
allowed_response_headers() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 48
def allowed_response_headers
  @allowed_response_headers ||= Array(config[:allowed_response_headers]).each_with_object({}) do |header, memo|
    memo[header] = build_attribute_name('http.response.headers.', header)
    memo[header.to_s.upcase] = build_attribute_name('http.response.headers.', header)
  end
end
build_attribute_name(prefix, suffix) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 55
def build_attribute_name(prefix, suffix)
  prefix + suffix.to_s.downcase.gsub(/[-\s]/, '_')
end
config() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 59
def config
  EpsagonRailsInstrumentation.instance.config
end
new(app) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 73
def initialize(app)
  @app = app
end

Private Class Methods

clear_cached_config() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 65
def clear_cached_config
  @allowed_rack_request_headers = nil
  @allowed_response_headers = nil
end

Public Instance Methods

call(env) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 77
def call(env)
  original_env = env.dup

  # restore extracted context in this process:
  OpenTelemetry::Context.with_current(OpenTelemetry.propagation.http.extract(env)) do
    request_span_name = create_request_span_name(env['REQUEST_URI'] || original_env['PATH_INFO'])
    tracer.in_span(env['HTTP_HOST'] || 'unknown',
                   attributes: request_span_attributes(env: env),
                   kind: :server) do |http_span|
      RackExtension.with_span(http_span) do
        tracer.in_span(
            env['HTTP_HOST'],
            kind: :server,
            attributes: {type: 'rails'}
          ) do |framework_span|
          @app.call(env).tap do |status, headers, response|
            set_attributes_after_request(http_span, framework_span, status, headers, response)
          end
        end
      end
    end
  end
end

Private Instance Methods

allowed_request_headers(env) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 180
def allowed_request_headers(env)
  return EMPTY_HASH if self.class.allowed_rack_request_headers.empty?

  {}.tap do |result|
    self.class.allowed_rack_request_headers.each do |key, value|
      result[value] = env[key] if env.key?(key)
    end
  end
end
allowed_response_headers(headers) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 190
def allowed_response_headers(headers)
  return EMPTY_HASH if headers.nil?
  return EMPTY_HASH if self.class.allowed_response_headers.empty?

  {}.tap do |result|
    self.class.allowed_response_headers.each do |key, value|
      if headers.key?(key)
        result[value] = headers[key]
      else
        # do case-insensitive match:
        headers.each do |k, v|
          if k.upcase == key
            result[value] = v
            break
          end
        end
      end
    end
  end
end
config() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 211
def config
  EpsagonRailsInstrumentation.instance.config
end
create_request_span_name(request_uri_or_path_info) click to toggle source

github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md#name

recommendation: span.name(s) should be low-cardinality (e.g., strip off query param value, keep param name)

see github.com/open-telemetry/opentelemetry-specification/pull/416/files

# File lib/instrumentation/epsagon_rails_middleware.rb, line 159
def create_request_span_name(request_uri_or_path_info)
  # NOTE: dd-trace-rb has implemented 'quantization' (which lowers url cardinality)
  #       see Datadog::Quantization::HTTP.url

  if (implementation = config[:url_quantization])
    implementation.call(request_uri_or_path_info)
  else
    request_uri_or_path_info
  end
end
finish_span(context) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 103
def finish_span(context)
  OpenTelemetry::Trace.current_span(context).finish if context
end
fullpath(env) click to toggle source

e.g., “/webshop/articles/4?s=1”:

# File lib/instrumentation/epsagon_rails_middleware.rb, line 146
def fullpath(env)
  query_string = env['QUERY_STRING']
  path = env['SCRIPT_NAME'] + env['PATH_INFO']

  query_string.empty? ? path : "#{path}?#{query_string}"
end
request_span_attributes(env:) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 111
def request_span_attributes(env:)
  request = Rack::Request.new(env)
  path, path_params = request.path.split(';')
  request_headers = JSON.generate(Hash[*env.select { |k, _v| k.to_s.start_with? 'HTTP_' }
    .collect { |k, v| [k.sub(/^HTTP_/, ''), v] }
    .collect { |k, v| [k.split('_').collect(&:capitalize).join('-'), v] }
    .sort
    .flatten])

  attributes = {
    'operation' => env['REQUEST_METHOD'],
    'type' => 'http',
    'http.scheme' => env['rack.url_scheme'],
    'http.request.path' => path,
    'http.request.headers' => request_headers
  }

  unless config[:epsagon][:metadata_only]
    request.body.rewind
    request_body = request.body.read
    request.body.rewind

    attributes.merge!(Util.epsagon_query_attributes(request.query_string))

    attributes.merge!({
                        'http.request.body' => request_body,
                        'http.request.path_params' => path_params,
                        'http.request.headers.User-Agent' => env['HTTP_USER_AGENT']
                      })
  end

  attributes
end
set_attributes_after_request(http_span, _framework_span, status, headers, response) click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 170
def set_attributes_after_request(http_span, _framework_span, status, headers, response)
  unless config[:epsagon][:metadata_only]
    http_span.set_attribute('http.response.headers', JSON.generate(headers))
    http_span.set_attribute('http.response.body', response.join) if response.respond_to?(:join)
  end

  http_span.set_attribute('http.status_code', status)
  http_span.status = OpenTelemetry::Trace::Status.http_to_status(status)
end
tracer() click to toggle source
# File lib/instrumentation/epsagon_rails_middleware.rb, line 107
def tracer
  EpsagonRailsInstrumentation.instance.tracer
end