Marlowe, Your Request Sleuth¶ ↑
- code
- issues
- docs
- continuous integration
Description¶ ↑
Marlowe is a Rack middleware that extracts or creates a request ID using a pre-defined header, permitting request correlation across multiple services.
When using Rails, Marlowe automatically adds itself to the middleware before Rails::Rack::Logger
.
Upgrading from Marlowe 1.x¶ ↑
In Marlowe 1.0, the correlation header was called Correlation-Id
; since then, Rails 5 and other tools and frameworks (such as Phoenix) have standardized on the header X-Request-Id
. Marlowe 2.0 changes to this header by default.
To keep complete compatibility with Marlowe 1.0, the following should be used:
# Rails config.marlowe_header = 'Correlation-Id' config.marlowe_handler = :simple config.marlowe_return = false # Rack use Marlowe::Middleware, header: 'Correlation-Id', handler: :simple, return: false
Configuration¶ ↑
Marlowe has three main configuration options: the request ID header, the request ID handler, and the request ID return. The options may be provided to the Rack use
command as a keyword option, or set in a corresponding marlowe_option
configuration variable in Rails.
Request ID Header¶ ↑
Specifies the header to be used for the request correlation ID. Defaults to X-Request-Id
.
# Rails config.marlowe_header = 'Correlation-Id' # OR: config.marlowe_correlation_header = 'Correlation-Id' # Rack use Marlowe::Middleware, header: 'Correlation-Id' # OR: use Marlowe::Middleware, correlation_header: 'Correlation-Id'
Marlowe will convert this to an appropriate HTTP header (in the Rack env
parameter, the above header would be represented as env['HTTP_CORRELATION_ID']
).
Request ID Handler¶ ↑
Specifies the method for sanitizing or generating the request correlation ID. Values can be :clean
(the default, which limits incoming correlation IDs to 255 alphanumeric-or-dash characters), :simple
(does not limit incoming correlation IDs), or a proc to transform or generate a correlation ID.
In all cases, if a correlation request ID is not handled, a UUID will be generated.
# Rails config.marlowe_handler = :simple config.marlowe_handler = ->(req_id) { req_id.try(:reverse) || SecureRandom.uuid } # Rack use Marlowe::Middleware, handler: :simple use Marlowe::Middleware, handler: ->(req_id) { req_id.try(:reverse) || SecureRandom.uuid }
Request ID Return¶ ↑
If true
(the default), the request correlation ID will be returned to the client in the same header that it was provided in.
# Rails config.marlowe_return = false # Rack use Marlowe::Middleware, return: false
Using Marlowe with Rails 5¶ ↑
Rails 5 includes the ActionDispatch::RequestId
middleware, reducing the need for Marlowe. Marlowe is more configurable than the Rails 5 default, so set marlowe_replace_action_dispatch_request_id
to true to have Marlowe::Middleware
will replace ActionDispatch::RequestId
:
# Rails only config.marlowe_replace_action_dispatch_request_id = true
Accessing the Correlation ID¶ ↑
The correlation id can be accessed throughout the application by accessing the RequestStore storage.
RequestStore[:correlation_id]
Logging¶ ↑
For a Rails application, you simply need to change the log formatter to one of the provided ones. Correlated versions of both the SimpleFormatter and Formatter are included.
# config/environments/development.rb Rails.application.configure do config.log_formatter = Marlowe::SimpleFormatter.new end
To create your own formatter, you'll need to access the RequestStore storage. You can use this pattern if you've rolled your own logger/formatter:
# lib/correlated_formatter.rb require 'request_store' class CorrelatedSimpleFormatter < ActiveSupport::Logger::SimpleFormatter def call(severity, timestamp, progname, msg) "[#{RequestStore.store[:correlation_id]}] #{super}" end end
Lograge¶ ↑
As lograge supplies its own formatter, you will need to do something a little different:
# config/application.rb class Application < Rails::Application config.before_initialize do ... # use lograge for all request logs config.lograge.enabled = true config.lograge.custom_options = lambda do |event| { correlation_id: RequestStore[:correlation_id] } end end end
Clients¶ ↑
Catching and creating the correlation ID is a great all on its own, but to really take advantage of the correlation in a service based architecture you'll need to pass the request ID to the next service in the change.
Here's an example of a Hurley client:
# lib/correlated_client.rb require 'hurley' require 'request_store' class Hurley::CorrelatedClient < Hurley::Client def initialize(*args, &block) super header['X-Request-Id'] = ::RequestStore.store[:correlation_id] end end
If you have long-lived Hurley clients, it is also possible to use the Hurley callback machanism to add the outgoing headers:
client.before_call do |request| request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id] end
or
class Correlator def name :correlator end def call(request) request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id] end end client.before_call(Correlator.new)
Install¶ ↑
Add Marlowe to your Gemfile:
gem 'marlowe', '~> 2.0'
Or manually install:
$ gem install marlowe
Marlowe Semantic Versioning¶ ↑
Marlowe uses a Semantic Versioning scheme with one significant change:
-
When PATCH is zero (
0
), it will be omitted from version references.
Additionally, the major version will generally be reserved for plug-in infrastructure changes.
Community and Contributing¶ ↑
Marlowe welcomes your contributions as described in Contributing.md. This project, like all Kinetic Cafe open source projects, is under the Kinetic Cafe Open Source Code of Conduct.