From 72b16d981f8132f88109ac6e8d2acb0667dab318 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Sun, 11 Sep 2022 22:57:20 -0400 Subject: [PATCH] Update to Marlowe 3.0 (#8) --- .github/workflows/ruby.yml | 91 ++++++-------- Appraisals | 10 +- Gemfile | 2 + History.md | 7 ++ Licence.md | 24 ++-- Manifest.txt | 5 + README.rdoc | 94 +++++++++----- Rakefile | 6 +- gemfiles/rack_1.gemfile | 8 ++ gemfiles/rack_2.gemfile | 1 + gemfiles/{rack_1.6.gemfile => rack_3.gemfile} | 2 +- lib/marlowe.rb | 35 +++++- lib/marlowe/config.rb | 116 ++++++++++++++++++ lib/marlowe/faraday.rb | 20 +++ lib/marlowe/middleware.rb | 65 +++------- lib/marlowe/rails.rb | 16 ++- lib/marlowe/simple_formatter.rb | 2 + marlowe.gemspec | 28 ++--- test/test_marlowe.rb | 3 +- test/test_marlowe_config.rb | 109 ++++++++++++++++ 20 files changed, 463 insertions(+), 181 deletions(-) create mode 100644 gemfiles/rack_1.gemfile rename gemfiles/{rack_1.6.gemfile => rack_3.gemfile} (81%) create mode 100644 lib/marlowe/config.rb create mode 100644 lib/marlowe/faraday.rb create mode 100644 test/test_marlowe_config.rb diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index dede0e4..b584bc5 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -1,76 +1,55 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake -# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby - -name: Ruby +name: Ruby CI on: - push: - branches: [master] pull_request: - branches: [master] + push: + branches: + - main + - master + workflow_dispatch: jobs: - test: + ruby-ci: + name: Ruby ${{ matrix.ruby }} - ${{ matrix.os }} + strategy: + fail-fast: true matrix: os: - - ubuntu - - macos - - windows + - ubuntu-20.04 ruby: - - '2.6' - '2.7' - '3.0' + - '3.1' - head - - debug - - mingw - - mswin - jruby - jruby-head - truffleruby - truffleruby-head - exclude: - - os: macos - ruby: mingw - - os: macos - ruby: mswin - - os: ubuntu - ruby: mingw - - os: ubuntu - ruby: mswin - - os: windows - ruby: debug - - os: windows - ruby: jruby - - os: windows - ruby: jruby-head - - os: windows - ruby: truffleruby - - os: windows - ruby: truffleruby-head - runs-on: ${{ matrix.os }}-latest - continue-on-error: ${{ - startsWith(matrix.ruby, 'truffle') || - matrix.ruby == 'debug' || - endsWith(matrix.ruby, 'head') || - (startsWith(matrix.ruby, 'jruby') && matrix.os == 'windows') - }} + - truffleruby+graalvm + - truffleruby+graalvm-head + include: + - ruby: head + continue-on-error: true + - ruby: jruby-head + continue-on-error: true + - os: ubuntu-22.04 + ruby: head + - os: ubuntu-22.04 + ruby: '3.1' + + runs-on: ${{ matrix.os }} + + continue-on-error: ${{ matrix.continue-on-error || false }} + steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Install dependencies - run: bundle install - - name: Install appraisals - run: bundle exec ruby -S appraisal install - - name: Run tests - run: bundle exec ruby -S rake test - - name: Run appraisals - run: bundle exec ruby -S appraisal rake test + + - run: bundle exec ruby -S rake test --trace + - run: bundle exec ruby -S appraisal install + - run: bundle exec ruby -S appraisal rake test + - run: bundle exec standardrb diff --git a/Appraisals b/Appraisals index 30f895e..d27a502 100644 --- a/Appraisals +++ b/Appraisals @@ -1,7 +1,15 @@ -appraise "rack-1.6" do +# frozen_string_literal: true + +appraise "rack-1" do gem "rack", "~> 1.6" + gem "rack-test", "~> 1.0" end appraise "rack-2" do gem "rack", "~> 2.0" + gem "rack-test", "~> 1.0" +end + +appraise "rack-3" do + gem "rack", "~> 3.0" end diff --git a/Gemfile b/Gemfile index 7a8814c..8ceeb9b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # -*- ruby -*- # NOTE: This file is present to keep Travis CI happy. Edits to it will not diff --git a/History.md b/History.md index 0fffe8a..9f37aa2 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,10 @@ +### 3.0 / 2022-09-11 + +- Added a Faraday request middleware. +- Replaced Hurley example with examples for the use of the Faraday + middleware. +- Added global Marlowe configuration. + ### 2.1 / 2021-09-08 - Allow the use of Ruby 3. diff --git a/Licence.md b/Licence.md index 6de90ad..56a8383 100644 --- a/Licence.md +++ b/Licence.md @@ -2,26 +2,24 @@ This software is available under an MIT-style licence. -* Copyright 2015–2016 Kinetic Cafe +- Copyright 2015–2022 Kinetic Cafe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -* The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. +- The names of its contributors may not be used to endorse or promote products + derived from this software without specific prior written permission. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Manifest.txt b/Manifest.txt index 8c4bba7..8ae5037 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -1,3 +1,5 @@ +.github/workflows/ruby.yml +.standard.yml Contributing.md History.md Licence.md @@ -5,9 +7,12 @@ Manifest.txt README.rdoc Rakefile lib/marlowe.rb +lib/marlowe/config.rb +lib/marlowe/faraday.rb lib/marlowe/formatter.rb lib/marlowe/middleware.rb lib/marlowe/rails.rb lib/marlowe/simple_formatter.rb test/minitest_config.rb test/test_marlowe.rb +test/test_marlowe_config.rb diff --git a/README.rdoc b/README.rdoc index 2613725..ff96f17 100644 --- a/README.rdoc +++ b/README.rdoc @@ -14,7 +14,41 @@ correlation across multiple services. When using Rails, Marlowe automatically adds itself to the middleware before Rails::Rack::Logger. -=== Upgrading from Marlowe 1.x +As of Marlowe 3.0, a Faraday middleware is provided (require 'marlowe/faraday'). + +=== Upgrading + +==== from Marlowe 2.0 + +In Marlowe 2.0, configuration was entirely specified in Rails configuration or +in the Rack +use+ clause. With the addition of a Faraday middleware for use with +Marlowe, centralization of the configuration is advisable. + +The existing configuration mechanisms will work (and create instance +configurations if provided), but it is instead recommended to use the global +configuration: + + # Compatibility with Marlowe 1.0 + Marlowe.configure do |config| + config.header = 'Correlation-ID' + config.handler = :simple + config.return = false + end + +Configuration is static for Marlowe::Middleware or Marlowe::Faraday instances +and changes to Marlowe configuration only affect *new* instances. If the +configuration options are provided in the middleware +use+ clause, they will +override the configured values. + + Marlowe.configure do |config| + config.header = 'Correlation-ID' + config.handler = :simple + config.return = false + end + use Marlowe::Middleware, handler: :clean + # The handler will be the :clean handler for the used middleware. + +==== 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 @@ -109,7 +143,7 @@ The correlation id can be accessed throughout the application by accessing the 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. +Formatter are included. # config/environments/development.rb Rails.application.configure do @@ -134,7 +168,7 @@ As {lograge}[https://github.com/roidrage/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 ... @@ -146,47 +180,39 @@ you will need to do something a little different: 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}[https://github.com/lostisland/hurley] client: +=== SemanticLogger - # lib/correlated_client.rb +As {semantic_logger}[https://github.com/rocketjob/semantic_logger] provides its +own formatters this should be added as a tag. The best way that I can see to do +this is to capture the +correlation_id+ in an +on_log+ event: - require 'hurley' - require 'request_store' + # config/initializers/semantic_logger.rb - class Hurley::CorrelatedClient < Hurley::Client - def initialize(*args, &block) - super - header['X-Request-Id'] = ::RequestStore.store[:correlation_id] + SemanticLogger.on_log do |log| + if RequestStore[:correlation_id] + log.named_tags[:correlation_id] = RequestStore[:correlation_id] end end -If you have long-lived Hurley clients, it is also possible to use the Hurley -{callback machanism}[https://github.com/lostisland/hurley#client-callbacks] to -add the outgoing headers: +== Clients - client.before_call do |request| - request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id] - end +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. -or - - class Correlator - def name - :correlator - end +Here's an example with {Faraday}[https://github.com/lostisland/faraday]: - def call(request) - request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id] - end - end + require 'faraday' + require 'faraday_middleware' + require 'marlowe/faraday' + + conn = Faraday.new(url: 'https://example.org/') do |conn| + conn.request :marlowe + conn.request :json - client.before_call(Correlator.new) + conn.response :json + conn.adapter Faraday.default_adapter + end == Install diff --git a/Rakefile b/Rakefile index da1916a..e593612 100644 --- a/Rakefile +++ b/Rakefile @@ -23,7 +23,7 @@ spec = Hoe.spec "marlowe" do require_ruby_version ">= 2.0", "< 4" extra_deps << ["request_store", "~> 1.2"] - extra_deps << ["rack", ">= 0.9", "< 3"] + extra_deps << ["rack", ">= 1", "< 4"] extra_dev_deps << ["appraisal", "~> 2.1"] extra_dev_deps << ["hoe-doofus", "~> 1.0"] @@ -35,7 +35,7 @@ spec = Hoe.spec "marlowe" do extra_dev_deps << ["minitest-bonus-assertions", "~> 3.0"] extra_dev_deps << ["minitest-focus", "~> 1.1"] extra_dev_deps << ["minitest-moar", "~> 0.0"] - extra_dev_deps << ["rack-test", "~> 1.0"] + extra_dev_deps << ["rack-test", "~> 2.0"] extra_dev_deps << ["rake", ">= 10.0", "< 14"] extra_dev_deps << ["rdoc", ">= 4.2"] extra_dev_deps << ["standard", "~> 1.0"] @@ -45,7 +45,7 @@ end ENV["RUBYOPT"] = "-W0" -module Hoe::Publish #:nodoc: +module Hoe::Publish # :nodoc: alias_method :__make_rdoc_cmd__marlowe__, :make_rdoc_cmd def make_rdoc_cmd(*extra_args) # :nodoc: diff --git a/gemfiles/rack_1.gemfile b/gemfiles/rack_1.gemfile new file mode 100644 index 0000000..975d457 --- /dev/null +++ b/gemfiles/rack_1.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org/" + +gem "rack", "~> 1.6" +gem "rack-test", "~> 1.0" + +gemspec path: "../" diff --git a/gemfiles/rack_2.gemfile b/gemfiles/rack_2.gemfile index 7b630a8..76a9151 100644 --- a/gemfiles/rack_2.gemfile +++ b/gemfiles/rack_2.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org/" gem "rack", "~> 2.0" +gem "rack-test", "~> 1.0" gemspec path: "../" diff --git a/gemfiles/rack_1.6.gemfile b/gemfiles/rack_3.gemfile similarity index 81% rename from gemfiles/rack_1.6.gemfile rename to gemfiles/rack_3.gemfile index 68da523..96e73cd 100644 --- a/gemfiles/rack_1.6.gemfile +++ b/gemfiles/rack_3.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org/" -gem "rack", "~> 1.6" +gem "rack", "~> 3.0" gemspec path: "../" diff --git a/lib/marlowe.rb b/lib/marlowe.rb index 75b2bdc..41dbdd6 100644 --- a/lib/marlowe.rb +++ b/lib/marlowe.rb @@ -2,11 +2,44 @@ # Marlowe, a correlation id injector. module Marlowe - VERSION = "2.1" #:nodoc: + VERSION = "3.0" # :nodoc: + require "marlowe/config" require "marlowe/middleware" require "marlowe/rails" if defined? Rails::Railtie autoload :Formatter, "marlowe/formatter" autoload :SimpleFormatter, "marlowe/simple_formatter" + + class << self + # Configure Marlowe + def configure(&block) + Marlowe::Config.configure(&block) + end + + # Make a Marlowe request ID + def make_request_id(request_id, config = Marlowe::Config.global) + if config.handler == :simple + simple(request_id) + elsif config.handler.is_a?(Proc) + simple(config.handler.call(request_id)) + else + clean(request_id) + end + end + + private + + def clean(request_id) + simple(request_id).gsub(/[^\w\-]/, "")[0, 255] + end + + def simple(request_id) + if request_id && !request_id.empty? && request_id !~ /\A[[:space]]*\z/ + request_id + else + SecureRandom.uuid + end + end + end end diff --git a/lib/marlowe/config.rb b/lib/marlowe/config.rb new file mode 100644 index 0000000..d947e22 --- /dev/null +++ b/lib/marlowe/config.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +# Configuration object for Marlowe. +class Marlowe::Config + # The name of the default header to look for and put the correlation id in. + CORRELATION_HEADER = "X-Request-Id" # :nodoc: + + class << self + # The global Marlowe configuration. + def global + @global ||= new + end + + # Override the global Marlowe configuration. + def override(opts) + new(global, opts) + end + + def configure(&block) # :nodoc: + @global = new(global, &block) + end + + private + + def clear_global! + @global = nil + end + end + + # The name of the header to inspect. Defaults to 'X-Request-Id'. + attr_accessor :header + # The HTTP formatted version of the header name to inspect. Defaults to + # 'HTTP_X_REQUEST_ID'. + attr_reader :http_header + # The handler for request correlation IDs. Defaults to sanitizing provided + # request IDs or generating a UUID. If :simple is provided, provided + # request IDs will not be sanitized. A callable (expecting a single input of + # any possible existing request ID) may be provided to introduce more complex + # request ID handling. + attr_accessor :handler + # If +true+ (the default), the request correlation ID will be returned as + # part of the response headers. Only affects Marlowe::Middleware. + attr_accessor :return + # If +true+, Marlowe will add code to behave like + # ActionDispatch::RequestId. Depends on + # ActionDispatch::Request. Only affects Marlowe::Middleware. + attr_accessor :action_dispatch + + # === Option Values + # + # :header:: The name of the header to inspect. Defaults to + # 'X-Request-Id'. Also available as + # :correlation_header. + # :handler:: The handler for request correlation IDs. Defaults to + # sanitizing provided request IDs or generating a UUID. + # If :simple is provided, provided request IDs + # will not be sanitized. A callable (expecting a single + # input of any possible existing request ID) may be + # provided to introduce more complex request ID + # handling. + # :return:: If +true+ (the default), the request correlation ID + # will be returned as part of the response headers. + # :action_dispatch:: If +true+, Marlowe will add code to behave + # like ActionDispatch::RequestId. + # Depends on ActionDispatch::Request. + def initialize(base = nil, opts = nil) # :yields: self + opts = + if base.nil? && opts.nil? + {} + elsif base.nil? && opts.is_a?(Hash) + opts + elsif base.is_a?(Hash) && opts.nil? + base + elsif base.is_a?(self.class) && opts.nil? + base.to_hash + elsif (base.is_a?(Hash) || base.is_a?(self.class)) && opts.is_a?(Hash) + hash = + if base.is_a?(self.class) + base.to_hash + else + base + end + hash.update(opts) + end + + @header, @http_header = format_header_name( + opts[:header] || opts[:correlation_header] || CORRELATION_HEADER + ) + + @handler = opts.fetch(:handler, :clean) + @return = opts.fetch(:return, true) + @action_dispatch = opts.fetch(:action_dispatch, false) + + yield self if block_given? + + freeze + end + + def to_hash + { + header: header, + handler: handler, + return: self.return, + action_dispatch: action_dispatch + } + end + + private + + def format_header_name(header) + [ + header.to_s.tr("_", "-").freeze, + "HTTP_#{header.to_s.tr("-", "_").upcase}" + ] + end +end diff --git a/lib/marlowe/faraday.rb b/lib/marlowe/faraday.rb new file mode 100644 index 0000000..1d4d7c0 --- /dev/null +++ b/lib/marlowe/faraday.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "marlowe" + +# Marlowe correlation ID middleware for Faraday. Including this into your +# request middleware stack will use the captured correlation ID. +class Marlowe::Faraday < Faraday::Middleware + def initialize(app, opts = {}) + super(app) + @config = Marlowe::Config.override(opts) + end + + def call(env) + env[:request_headers][@config.header] = + Marlowe.make_request_id(RequestStore[:correlation_id], @config) + @app.call(env) + end +end + +Faraday::Request.register_middleware marlowe: -> { Marlowe::Faraday } diff --git a/lib/marlowe/middleware.rb b/lib/marlowe/middleware.rb index bb2a03a..ee28a3f 100644 --- a/lib/marlowe/middleware.rb +++ b/lib/marlowe/middleware.rb @@ -6,11 +6,11 @@ module Marlowe # Marlowe correlation id middleware. Including this into your middleware - # stack will add a correlation id header as an incoming request, and save - # that id in a request session variable. + # stack will capture or add a correlation id header on an incoming request, + # and save that id in a request session variable. class Middleware # The name of the default header to look for and put the correlation id in. - CORRELATION_HEADER = "X-Request-Id" #:nodoc: + CORRELATION_HEADER = Marlowe::Config::CORRELATION_HEADER # :nodoc: # Configure the Marlowe middleware to call +app+ with options +opts+. # @@ -31,67 +31,36 @@ class Middleware # :action_dispatch:: If +true+, Marlowe will add code to behave # like ActionDispatch::RequestId. # Depends on ActionDispatch::Request. - def initialize(app, opts = {}) + def initialize(app, opts = nil) @app = app - @header, @http_header = format_header_name( - opts[:header] || opts[:correlation_header] || CORRELATION_HEADER - ) - @handler = opts.fetch(:handler, :clean) - @return = opts.fetch(:return, true) - @action_dispatch = opts.fetch(:action_dispatch, false) + @config = Marlowe::Config.override(opts) end # Stores the incoming correlation id from the +env+ hash. If the correlation # id has not been sent, a new UUID is generated and the +env+ is modified. def call(env) - req_id = make_request_id(env[@http_header]) - RequestStore.store[:correlation_id] = env[@http_header] = req_id + req_id = Marlowe.make_request_id(env[config.http_header], config) + RequestStore.store[:correlation_id] = env[config.http_header] = req_id - if @action_dispatch + if config.action_dispatch req = ActionDispatch::Request.new(env) req.request_id = req_id end - @app.call(env).tap { |_status, headers, _body| - if @return - headers[@header] = if @action_dispatch - req.request_id - else - RequestStore.store[:correlation_id] - end + app.call(env).tap { |_status, headers, _body| + if config.return + headers[config.header] = + if config.action_dispatch + req.request_id + else + RequestStore.store[:correlation_id] + end end } end private - def format_header_name(header) - [ - header.to_s.tr("_", "-").freeze, - "HTTP_#{header.to_s.tr("-", "_").upcase}" - ] - end - - def make_request_id(request_id) - if @handler == :simple - simple(request_id) - elsif @handler.is_a?(Proc) - simple(@handler.call(request_id)) - else - clean(request_id) - end - end - - def clean(request_id) - simple(request_id).gsub(/[^\w\-]/, "")[0, 255] - end - - def simple(request_id) - if request_id && !request_id.empty? && request_id !~ /\A[[:space]]*\z/ - request_id - else - SecureRandom.uuid - end - end + attr_reader :app, :config end end diff --git a/lib/marlowe/rails.rb b/lib/marlowe/rails.rb index a7aa8b8..43a2426 100644 --- a/lib/marlowe/rails.rb +++ b/lib/marlowe/rails.rb @@ -6,23 +6,21 @@ class Railtie < Rails::Railtie # :nodoc: config = app.config opts = { - header: config.try(:marlowe_header) || config.try(:marlowe_correlation_header), - handler: config.try(:marlowe_request_id_handler), - return: config.try(:marlowe_return_request_id), - action_dispatch: config.try(:marlowe_replace_action_dispatch_request_id) + header: config&.marlowe_header || config&.marlowe_correlation_header, + handler: config&.marlowe_request_id_handler, + return: config&.marlowe_return_request_id, + action_dispatch: config&.marlowe_replace_action_dispatch_request_id }.compact if opts[:action_dispatch] - app.middleware.insert_before ActionDispatch::RequestId, - Marlowe::Middleware, opts + app.middleware.insert_before ActionDispatch::RequestId, Marlowe::Middleware, opts app.middleware.delete ActionDispatch::RequestId else - app.middleware.insert_before Rails::Rack::Logger, Marlowe::Middleware, - opts + app.middleware.insert_before Rails::Rack::Logger, Marlowe::Middleware, opts end end - def app #:nodoc: + def app # :nodoc: Rails.application end end diff --git a/lib/marlowe/simple_formatter.rb b/lib/marlowe/simple_formatter.rb index 6819c94..c4c1124 100644 --- a/lib/marlowe/simple_formatter.rb +++ b/lib/marlowe/simple_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "request_store" module Marlowe diff --git a/marlowe.gemspec b/marlowe.gemspec index 0866307..af0177a 100644 --- a/marlowe.gemspec +++ b/marlowe.gemspec @@ -1,24 +1,24 @@ # -*- encoding: utf-8 -*- -# stub: marlowe 2.1 ruby lib +# stub: marlowe 3.0 ruby lib Gem::Specification.new do |s| s.name = "marlowe".freeze - s.version = "2.1" + s.version = "3.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "documentation_uri" => "http://www.rubydoc.info/github/KineticCafe/marlowe/master", "source_code_uri" => "https://github.com/KineticCafe/marlowe/" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Trevor Oke".freeze, "Kinetic Cafe".freeze] - s.date = "2021-09-09" - s.description = "{Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that\nextracts or creates a request ID using a pre-defined header, permitting request\ncorrelation across multiple services.\n\nWhen using Rails, Marlowe automatically adds itself to the middleware before\nRails::Rack::Logger.".freeze + s.date = "2022-09-12" + s.description = "{Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that\nextracts or creates a request ID using a pre-defined header, permitting request\ncorrelation across multiple services.\n\nWhen using Rails, Marlowe automatically adds itself to the middleware before\nRails::Rack::Logger.\n\nAs of Marlowe 3.0, a Faraday middleware is provided (require 'marlowe/faraday').".freeze s.email = ["toke@kineticcafe.com".freeze, "dev@kineticcafe.com".freeze] s.extra_rdoc_files = ["Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze] - s.files = ["Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "lib/marlowe.rb".freeze, "lib/marlowe/formatter.rb".freeze, "lib/marlowe/middleware.rb".freeze, "lib/marlowe/rails.rb".freeze, "lib/marlowe/simple_formatter.rb".freeze, "test/minitest_config.rb".freeze, "test/test_marlowe.rb".freeze] + s.files = [".github/workflows/ruby.yml".freeze, ".standard.yml".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "lib/marlowe.rb".freeze, "lib/marlowe/config.rb".freeze, "lib/marlowe/faraday.rb".freeze, "lib/marlowe/formatter.rb".freeze, "lib/marlowe/middleware.rb".freeze, "lib/marlowe/rails.rb".freeze, "lib/marlowe/simple_formatter.rb".freeze, "test/minitest_config.rb".freeze, "test/test_marlowe.rb".freeze, "test/test_marlowe_config.rb".freeze] s.homepage = "https://github.com/KineticCafe/marlowe/".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] s.required_ruby_version = Gem::Requirement.new([">= 2.0".freeze, "< 4".freeze]) - s.rubygems_version = "3.1.6".freeze + s.rubygems_version = "3.3.7".freeze s.summary = "{Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that extracts or creates a request ID using a pre-defined header, permitting request correlation across multiple services".freeze if s.respond_to? :specification_version then @@ -27,8 +27,8 @@ Gem::Specification.new do |s| if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, ["~> 1.2"]) - s.add_runtime_dependency(%q.freeze, [">= 0.9", "< 3"]) - s.add_development_dependency(%q.freeze, ["~> 5.14"]) + s.add_runtime_dependency(%q.freeze, [">= 1", "< 4"]) + s.add_development_dependency(%q.freeze, ["~> 5.16"]) s.add_development_dependency(%q.freeze, ["~> 2.1"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 1.1"]) @@ -38,17 +38,17 @@ Gem::Specification.new do |s| s.add_development_dependency(%q.freeze, ["~> 3.0"]) s.add_development_dependency(%q.freeze, ["~> 1.1"]) s.add_development_dependency(%q.freeze, ["~> 0.0"]) - s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 2.0"]) s.add_development_dependency(%q.freeze, [">= 10.0", "< 14"]) s.add_development_dependency(%q.freeze, [">= 4.2"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 0.21"]) s.add_development_dependency(%q.freeze, ["~> 3.1"]) - s.add_development_dependency(%q.freeze, ["~> 3.23"]) + s.add_development_dependency(%q.freeze, ["~> 3.25"]) else s.add_dependency(%q.freeze, ["~> 1.2"]) - s.add_dependency(%q.freeze, [">= 0.9", "< 3"]) - s.add_dependency(%q.freeze, ["~> 5.14"]) + s.add_dependency(%q.freeze, [">= 1", "< 4"]) + s.add_dependency(%q.freeze, ["~> 5.16"]) s.add_dependency(%q.freeze, ["~> 2.1"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 1.1"]) @@ -58,12 +58,12 @@ Gem::Specification.new do |s| s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 1.1"]) s.add_dependency(%q.freeze, ["~> 0.0"]) - s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, [">= 10.0", "< 14"]) s.add_dependency(%q.freeze, [">= 4.2"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 0.21"]) s.add_dependency(%q.freeze, ["~> 3.1"]) - s.add_dependency(%q.freeze, ["~> 3.23"]) + s.add_dependency(%q.freeze, ["~> 3.25"]) end end diff --git a/test/test_marlowe.rb b/test/test_marlowe.rb index 141b6d9..1a716f7 100644 --- a/test/test_marlowe.rb +++ b/test/test_marlowe.rb @@ -9,6 +9,7 @@ class TestMarlowe < Minitest::Test def setup @marlowe_options = {} + Marlowe::Config.send(:clear_global!) end def app @@ -85,7 +86,7 @@ def test_handler_config_with_proc_handler end def test_handler_config_with_proc_handler_returning_nil - marlowe_options[:handler] = ->(item) {} + marlowe_options[:handler] = ->(_item) {} get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"} assert last_response.header.key?("X-Request-Id") refute_empty last_response.header["X-Request-Id"] diff --git a/test/test_marlowe_config.rb b/test/test_marlowe_config.rb new file mode 100644 index 0000000..c938a3e --- /dev/null +++ b/test/test_marlowe_config.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "minitest_config" + +class TestMarloweConfig < Minitest::Test + include Rack::Test::Methods + + attr_reader :marlowe_options + + def setup + @marlowe_options = {} + Marlowe::Config.send(:clear_global!) + end + + def app + Marlowe.configure do |config| + marlowe_options.each do |k, v| + config.send(:"#{k}=", v) if config.respond_to?(:"#{k}=") + end + end + + options = marlowe_options + Rack::Builder.new do + use Marlowe::Middleware, options + + run lambda { |_env| + [ + 200, + {"Content-Type" => "text/plain"}, + [RequestStore[:correlation_id]] + ] + } + end + end + + def test_default_config_no_header_value + get "/" + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + end + + def test_default_config_with_header_value + get "/", {}, {"HTTP_X_REQUEST_ID" => "testvalue"} + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + assert_equal "testvalue", last_response.header["X-Request-Id"] + end + + def test_header_config_no_header_value + marlowe_options[:header] = "Correlation-Id" + get "/" + assert last_response.header.key?("Correlation-Id") + refute_empty last_response.header["Correlation-Id"] + assert_equal last_response.header["Correlation-Id"], last_response.body + end + + def test_header_config_no_header_with_header_value + marlowe_options[:header] = "Correlation-Id" + get "/", {}, {"HTTP_CORRELATION_ID" => "testvalue"} + assert last_response.header.key?("Correlation-Id") + refute_empty last_response.header["Correlation-Id"] + assert_equal last_response.header["Correlation-Id"], last_response.body + assert_equal "testvalue", last_response.header["Correlation-Id"] + end + + def test_handler_config_default_handler + get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"} + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + assert_equal "testvalue", last_response.header["X-Request-Id"] + end + + def test_handler_config_with_simple_handler + marlowe_options[:handler] = :simple + get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"} + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + assert_equal "test+value", last_response.header["X-Request-Id"] + end + + def test_handler_config_with_proc_handler + marlowe_options[:handler] = ->(item) { item && item.reverse || SecureRandom.uuid } + get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"} + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + assert_equal "eulav+tset", last_response.header["X-Request-Id"] + end + + def test_handler_config_with_proc_handler_returning_nil + marlowe_options[:handler] = ->(_item) {} + get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"} + assert last_response.header.key?("X-Request-Id") + refute_empty last_response.header["X-Request-Id"] + assert_equal last_response.header["X-Request-Id"], last_response.body + assert_match(/\A[-\w]+\z/, last_response.header["X-Request-Id"]) + end + + def test_return_config_false + marlowe_options[:return] = false + get "/" + refute last_response.header.key?("X-Request-Id") + assert_equal RequestStore[:correlation_id], last_response.body + end +end