Class: RightSupport::Rack::RequestTracker

Inherits:
Object
  • Object
show all
Defined in:
lib/right_support/rack/request_tracker.rb

Overview

middleware to detect a request ID header or generate a new ID for each incoming request. the purpose is to track a request lineage throughout a chain of internal API calls and, in some cases, out to external APIs that also support the X-Request-ID header as a de-facto standard (google it).

Constant Summary collapse

Generator =

shorthand

::RightSupport::Data::Token
REQUEST_ID_HEADER =

used by many public services to represent a client-generated request ID that can be tracked and safely logged throughout the lineage of a request. this is also supported by goa middleware.

'X-Request-Id'.freeze
HTTP_REQUEST_ID_HEADER =

incoming header as found in Rack env hash, if any.

'HTTP_X_REQUEST_ID'.freeze
REQUEST_UUID_HEADER =

LEGACY: still supported but new code should use the standard (see above).

"X-Request-Uuid".freeze
HTTP_REQUEST_UUID_HEADER =

LEGACY: incoming header as found in Rack env hash, if any.

'HTTP_X_REQUEST_UUID'.freeze
REQUEST_UUID_ENV_NAME =

LEGACY: refers to the generated or passed-in request ID. the key has been hardcoded in some places so is not easy to change and/or there is not much value to finding and replacing with _id in all cases.

'rack.request_uuid'.freeze
REQUEST_LINEAGE_UUID_HEADER =
Deprecated.

do not send the lineage header as support may go away.

'HTTP_X_REQUEST_LINEAGE_UUID'.freeze
UUID_SEPARATOR =
Deprecated.

do not send the lineage header as support may go away.

' '.freeze
MAX_REQUEST_UUID_LENGTH =

limit request [UU]ID to something reasonable to keep our logs from overflowing on bad user-provided IDs. we do not want to be too restrictive in case people are encoding some useful information, etc. the issue is that the request ID will tend to show up a lot in logs.

128

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ RequestTracker

Returns a new instance of RequestTracker.

Parameters:

  • as next middleware or the rack application



65
66
67
# File 'lib/right_support/rack/request_tracker.rb', line 65

def initialize(app)
  @app = app
end

Class Method Details

.copy_request_uuid(from_env, to_headers) ⇒ Hash

copies the request [UU]ID from the request environment to the given hash, if present. does nothing if request [UU]ID is not set (because middleware was not present, etc.)

Parameters:

  • as source

  • as target

Returns:

  • updated headers



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/right_support/rack/request_tracker.rb', line 128

def self.copy_request_uuid(from_env, to_headers)
  to_headers ||= {}
  if from_env
    if request_uuid = from_env[REQUEST_UUID_ENV_NAME]
      # note we always forward the _ID header as the standard. none of the
      # RS code ever actually accepted the _UUID header so that is a
      # non-issue.
      to_headers[REQUEST_ID_HEADER] = request_uuid
    end
  end
  to_headers
end

.detect_request_uuid(env) ⇒ Array

detects whether the incoming env hash contains a request ID is some form and generates a new ID when missing.

Returns:

  • tuple of detected/generated ID and the name of the header to use to represent the ID in a response.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/right_support/rack/request_tracker.rb', line 89

def self.detect_request_uuid(env)
  request_uuid = ''
  response_header_name = nil
  {
    HTTP_REQUEST_ID_HEADER => REQUEST_ID_HEADER,
    HTTP_REQUEST_UUID_HEADER => REQUEST_UUID_HEADER,
    REQUEST_LINEAGE_UUID_HEADER => REQUEST_UUID_HEADER
  }.each do |in_key, out_key|
    if env.has_key?(in_key)
      request_uuid = env[in_key].to_s.strip
      response_header_name = out_key
      break
    end
  end

  # for legacy reasons we default to the -UUID header in response for newly-
  # generated IDs. we will use the -ID standard if that was passed-in. once
  # all apps are updated you will mostly see -ID in response except for API
  # calls initiated by browser code. the javascript can gradually be changed
  # to send -ID with requests as well.
  if request_uuid.empty?
    request_uuid = generate_request_uuid
    response_header_name = REQUEST_UUID_HEADER
  else
    # truncate, if necessary.
    request_uuid = request_uuid[0, MAX_REQUEST_UUID_LENGTH]
  end

  return request_uuid, response_header_name
end

.generate_request_uuidString

generates a token (nicer than an actual UUID for logging) but we continue to refer to it as the “request UUID” for legacy reasons.

Returns:

  • a new token



145
146
147
# File 'lib/right_support/rack/request_tracker.rb', line 145

def self.generate_request_uuid
  Generator.generate
end

Instance Method Details

#call(env) ⇒ Array

request tracking.

Parameters:

  • from preceding middleware

Returns:

  • tuple of [status, headers, body]



74
75
76
77
78
79
80
81
82
# File 'lib/right_support/rack/request_tracker.rb', line 74

def call(env)
  request_uuid, response_header_name = self.class.detect_request_uuid(env)
  env[REQUEST_UUID_ENV_NAME] = request_uuid

  status, headers, body = @app.call(env)

  headers[response_header_name] = request_uuid
  [status, headers, body]
end