marlowe 2.1 → 3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 942139256c91a7b04a4549271e176b8dab519dd566df088dc818198e8d285065
4
- data.tar.gz: 00a699c0d9ce21ada8c453ea638a0f972f64307e7572be8aa3dca7629922f598
3
+ metadata.gz: ab7ab600bebd938d83401763fdb4da4e46448cce69f2421e0e8e68dec967c289
4
+ data.tar.gz: 5b5a7f77d71cabe799fba50b54e5639c0f9e3e0fc75600b0c5cd486dab850d1f
5
5
  SHA512:
6
- metadata.gz: 6dfa8bd46ddc990b643db1d01925da2a694fba37466c4dfacaab2dd36c776aa8f6f26e8fa64b5e35f5e52d91095e8ad1cf368a382f5c2188defc62596be1a63d
7
- data.tar.gz: ee94d6e57c9dc02ae7690977281dc9a62051465ef45330474aac3e2284f68d438099409736a32806ecff61da2f9191d8eaf9869300f48e6464c943ee6caf608a
6
+ metadata.gz: 630dea03e6bb4aed6e325baba7152a18c129c76d3ef2a029bdb7079432ff9b5bc8477742cb7d64c2e78718554eaf95bf8f9ce43aaaab66110c95e32616532f93
7
+ data.tar.gz: 6a6a17555432e901b74abbefb5f65385e79cb22c942e6605d5885e04366f246012ba8681ff74b30583bb808835782b9b8a380971bb7ca0d7b4b13ed324b37eb5
@@ -0,0 +1,59 @@
1
+ name: Ruby CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+ - master
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ ruby-ci:
13
+ name: Ruby ${{ matrix.ruby }} - ${{ matrix.os }}
14
+
15
+ strategy:
16
+ fail-fast: true
17
+ matrix:
18
+ os:
19
+ - ubuntu-20.04
20
+ ruby:
21
+ - '2.7'
22
+ - '3.0'
23
+ - '3.1'
24
+ - '3.2'
25
+ - '3.3'
26
+ - head
27
+ - jruby
28
+ - jruby-head
29
+ - truffleruby
30
+ - truffleruby-head
31
+ include:
32
+ - ruby: head
33
+ continue-on-error: true
34
+ - ruby: jruby-head
35
+ continue-on-error: true
36
+ - os: ubuntu-22.04
37
+ ruby: head
38
+ - os: ubuntu-22.04
39
+ ruby: '3.2'
40
+ - os: ubuntu-22.04
41
+ ruby: '3.3'
42
+
43
+ runs-on: ${{ matrix.os }}
44
+
45
+ continue-on-error: ${{ matrix.continue-on-error || false }}
46
+
47
+ steps:
48
+ - uses: actions/checkout@v3
49
+ - uses: ruby/setup-ruby@v1
50
+ with:
51
+ ruby-version: ${{ matrix.ruby }}
52
+ bundler-cache: true
53
+
54
+ - run: bundle exec ruby -S rake test --trace
55
+ - run: bundle exec ruby -S appraisal install
56
+ - run: bundle exec ruby -S appraisal rake test
57
+
58
+ - run: bundle exec standardrb
59
+ if: ${{ matrix.ruby == '3.3' && matrix.os == 'ubuntu-22.04' }}
data/.standard.yml ADDED
@@ -0,0 +1,4 @@
1
+ parallel: true
2
+ ruby_version: 2.7
3
+ ignore:
4
+ - 'marlowe.gemspec'
data/History.md CHANGED
@@ -1,10 +1,24 @@
1
- ### 2.1 / 2021-09-08
1
+ # History
2
+
3
+ ## 3.1 / 2024-02-29
4
+
5
+ - Fixed some noise in the tests as preparation for Rack 3.1.
6
+ - Updated dependencies.
7
+
8
+ ## 3.0 / 2022-09-11
9
+
10
+ - Added a Faraday request middleware.
11
+ - Replaced Hurley example with examples for the use of the Faraday
12
+ middleware.
13
+ - Added global Marlowe configuration.
14
+
15
+ ## 2.1 / 2021-09-08
2
16
 
3
17
  - Allow the use of Ruby 3.
4
18
  - Switch to standardruby instead of rubocop.
5
19
  - Switch from Travis to Github Actions.
6
20
 
7
- ### 2.0 / 2016-11-16
21
+ ## 2.0 / 2016-11-16
8
22
 
9
23
  - Breaking change: the correlation header defaults to `X-Request-Id` instead of
10
24
  `Correlation-Id`.
@@ -15,21 +29,21 @@
15
29
  part of the response.
16
30
  - Marlowe is more configurable now.
17
31
 
18
- ### 1.0.3 / 2016-01-15
32
+ ## 1.0.3 / 2016-01-15
19
33
 
20
34
  - Update Readme example of using available formatted subclass.
21
35
  - Make the correlation header name configurable
22
36
 
23
- ### 1.0.2 / 2015-11-24
37
+ ## 1.0.2 / 2015-11-24
24
38
 
25
39
  - Add documentation for using Marlowe with [lograge][].
26
40
 
27
- ### 1.0.1 / 2015-10-20
41
+ ## 1.0.1 / 2015-10-20
28
42
 
29
43
  - Update gemspec with homepage
30
44
  - Update Rakefile
31
45
 
32
- ### 1.0.0 / 2015-10-16
46
+ ## 1.0.0 / 2015-10-16
33
47
 
34
48
  - Initial Commit
35
49
 
data/Licence.md CHANGED
@@ -2,26 +2,24 @@
2
2
 
3
3
  This software is available under an MIT-style licence.
4
4
 
5
- * Copyright 2015–2016 Kinetic Cafe
5
+ - Copyright 2015–2022 Kinetic Cafe
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining a copy of
8
8
  this software and associated documentation files (the "Software"), to deal in
9
9
  the Software without restriction, including without limitation the rights to
10
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
- of the Software, and to permit persons to whom the Software is furnished to do
12
- so, subject to the following conditions:
10
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
+ the Software, and to permit persons to whom the Software is furnished to do so,
12
+ subject to the following conditions:
13
13
 
14
- * The names of its contributors may not be used to endorse or promote
15
- products derived from this software without specific prior written
16
- permission.
14
+ - The names of its contributors may not be used to endorse or promote products
15
+ derived from this software without specific prior written permission.
17
16
 
18
17
  The above copyright notice and this permission notice shall be included in all
19
18
  copies or substantial portions of the Software.
20
19
 
21
20
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
- SOFTWARE.
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt CHANGED
@@ -1,3 +1,5 @@
1
+ .github/workflows/ruby.yml
2
+ .standard.yml
1
3
  Contributing.md
2
4
  History.md
3
5
  Licence.md
@@ -5,9 +7,12 @@ Manifest.txt
5
7
  README.rdoc
6
8
  Rakefile
7
9
  lib/marlowe.rb
10
+ lib/marlowe/config.rb
11
+ lib/marlowe/faraday.rb
8
12
  lib/marlowe/formatter.rb
9
13
  lib/marlowe/middleware.rb
10
14
  lib/marlowe/rails.rb
11
15
  lib/marlowe/simple_formatter.rb
12
16
  test/minitest_config.rb
13
17
  test/test_marlowe.rb
18
+ test/test_marlowe_config.rb
data/README.rdoc CHANGED
@@ -14,7 +14,41 @@ correlation across multiple services.
14
14
  When using Rails, Marlowe automatically adds itself to the middleware before
15
15
  <tt>Rails::Rack::Logger</tt>.
16
16
 
17
- === Upgrading from Marlowe 1.x
17
+ As of Marlowe 3.0, a Faraday middleware is provided (<tt>require 'marlowe/faraday'</tt>).
18
+
19
+ === Upgrading
20
+
21
+ ==== from Marlowe 2.0
22
+
23
+ In Marlowe 2.0, configuration was entirely specified in Rails configuration or
24
+ in the Rack +use+ clause. With the addition of a Faraday middleware for use with
25
+ Marlowe, centralization of the configuration is advisable.
26
+
27
+ The existing configuration mechanisms will work (and create instance
28
+ configurations if provided), but it is instead recommended to use the global
29
+ configuration:
30
+
31
+ # Compatibility with Marlowe 1.0
32
+ Marlowe.configure do |config|
33
+ config.header = 'Correlation-ID'
34
+ config.handler = :simple
35
+ config.return = false
36
+ end
37
+
38
+ Configuration is static for Marlowe::Middleware or Marlowe::Faraday instances
39
+ and changes to Marlowe configuration only affect *new* instances. If the
40
+ configuration options are provided in the middleware +use+ clause, they will
41
+ override the configured values.
42
+
43
+ Marlowe.configure do |config|
44
+ config.header = 'Correlation-ID'
45
+ config.handler = :simple
46
+ config.return = false
47
+ end
48
+ use Marlowe::Middleware, handler: :clean
49
+ # The handler will be the :clean handler for the used middleware.
50
+
51
+ ==== from Marlowe 1.x
18
52
 
19
53
  In Marlowe 1.0, the correlation header was called <tt>Correlation-Id</tt>;
20
54
  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
109
143
 
110
144
  For a Rails application, you simply need to change the log formatter to one of
111
145
  the provided ones. Correlated versions of both the SimpleFormatter and
112
- Formatter are included.
146
+ Formatter are included.
113
147
 
114
148
  # config/environments/development.rb
115
149
  Rails.application.configure do
@@ -134,7 +168,7 @@ As {lograge}[https://github.com/roidrage/lograge] supplies its own formatter,
134
168
  you will need to do something a little different:
135
169
 
136
170
  # config/application.rb
137
-
171
+
138
172
  class Application < Rails::Application
139
173
  config.before_initialize do
140
174
  ...
@@ -146,47 +180,39 @@ you will need to do something a little different:
146
180
  end
147
181
  end
148
182
 
149
- == Clients
150
-
151
- Catching and creating the correlation ID is a great all on its own, but to
152
- really take advantage of the correlation in a service based architecture you'll
153
- need to pass the request ID to the next service in the change.
154
-
155
- Here's an example of a {Hurley}[https://github.com/lostisland/hurley] client:
183
+ === SemanticLogger
156
184
 
157
- # lib/correlated_client.rb
185
+ As {semantic_logger}[https://github.com/rocketjob/semantic_logger] provides its
186
+ own formatters this should be added as a tag. The best way that I can see to do
187
+ this is to capture the +correlation_id+ in an +on_log+ event:
158
188
 
159
- require 'hurley'
160
- require 'request_store'
189
+ # config/initializers/semantic_logger.rb
161
190
 
162
- class Hurley::CorrelatedClient < Hurley::Client
163
- def initialize(*args, &block)
164
- super
165
- header['X-Request-Id'] = ::RequestStore.store[:correlation_id]
191
+ SemanticLogger.on_log do |log|
192
+ if RequestStore[:correlation_id]
193
+ log.named_tags[:correlation_id] = RequestStore[:correlation_id]
166
194
  end
167
195
  end
168
196
 
169
- If you have long-lived Hurley clients, it is also possible to use the Hurley
170
- {callback machanism}[https://github.com/lostisland/hurley#client-callbacks] to
171
- add the outgoing headers:
197
+ == Clients
172
198
 
173
- client.before_call do |request|
174
- request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id]
175
- end
199
+ Catching and creating the correlation ID is a great all on its own, but to
200
+ really take advantage of the correlation in a service based architecture you'll
201
+ need to pass the request ID to the next service in the change.
176
202
 
177
- or
178
-
179
- class Correlator
180
- def name
181
- :correlator
182
- end
203
+ Here's an example with {Faraday}[https://github.com/lostisland/faraday]:
183
204
 
184
- def call(request)
185
- request.header['X-Request-Id'] = ::RequestStore.store[:correlation_id]
186
- end
187
- end
205
+ require 'faraday'
206
+ require 'faraday_middleware'
207
+ require 'marlowe/faraday'
208
+
209
+ conn = Faraday.new(url: 'https://example.org/') do |conn|
210
+ conn.request :marlowe
211
+ conn.request :json
188
212
 
189
- client.before_call(Correlator.new)
213
+ conn.response :json
214
+ conn.adapter Faraday.default_adapter
215
+ end
190
216
 
191
217
  == Install
192
218
 
data/Rakefile CHANGED
@@ -23,29 +23,26 @@ spec = Hoe.spec "marlowe" do
23
23
  require_ruby_version ">= 2.0", "< 4"
24
24
 
25
25
  extra_deps << ["request_store", "~> 1.2"]
26
- extra_deps << ["rack", ">= 0.9", "< 3"]
26
+ extra_deps << ["rack", ">= 1", "< 4"]
27
27
 
28
28
  extra_dev_deps << ["appraisal", "~> 2.1"]
29
29
  extra_dev_deps << ["hoe-doofus", "~> 1.0"]
30
30
  extra_dev_deps << ["hoe-gemspec2", "~> 1.1"]
31
- extra_dev_deps << ["hoe-git", "~> 1.6"]
31
+ extra_dev_deps << ["hoe-git2", "~> 1.7"]
32
32
  extra_dev_deps << ["hoe-rubygems", "~> 1.0"]
33
33
  extra_dev_deps << ["minitest", "~> 5.4"]
34
34
  extra_dev_deps << ["minitest-autotest", "~> 1.0"]
35
- extra_dev_deps << ["minitest-bonus-assertions", "~> 3.0"]
36
35
  extra_dev_deps << ["minitest-focus", "~> 1.1"]
37
36
  extra_dev_deps << ["minitest-moar", "~> 0.0"]
38
- extra_dev_deps << ["rack-test", "~> 1.0"]
37
+ extra_dev_deps << ["rack-test", "~> 2.0"]
39
38
  extra_dev_deps << ["rake", ">= 10.0", "< 14"]
40
- extra_dev_deps << ["rdoc", ">= 4.2"]
41
39
  extra_dev_deps << ["standard", "~> 1.0"]
42
40
  extra_dev_deps << ["simplecov", "~> 0.21"]
43
- extra_dev_deps << ["psych", "~> 3.1"]
44
41
  end
45
42
 
46
43
  ENV["RUBYOPT"] = "-W0"
47
44
 
48
- module Hoe::Publish #:nodoc:
45
+ module Hoe::Publish # :nodoc:
49
46
  alias_method :__make_rdoc_cmd__marlowe__, :make_rdoc_cmd
50
47
 
51
48
  def make_rdoc_cmd(*extra_args) # :nodoc:
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configuration object for Marlowe.
4
+ class Marlowe::Config
5
+ # The name of the default header to look for and put the correlation id in.
6
+ CORRELATION_HEADER = "X-Request-Id" # :nodoc:
7
+
8
+ class << self
9
+ # The global Marlowe configuration.
10
+ def global
11
+ @global ||= new
12
+ end
13
+
14
+ # Override the global Marlowe configuration.
15
+ def override(opts)
16
+ new(global, opts)
17
+ end
18
+
19
+ def configure(&block) # :nodoc:
20
+ @global = new(global, &block)
21
+ end
22
+
23
+ private
24
+
25
+ def clear_global!
26
+ @global = nil
27
+ end
28
+ end
29
+
30
+ # The name of the header to inspect. Defaults to 'X-Request-Id'.
31
+ attr_accessor :header
32
+ # The HTTP formatted version of the header name to inspect. Defaults to
33
+ # 'HTTP_X_REQUEST_ID'.
34
+ attr_reader :http_header
35
+ # The handler for request correlation IDs. Defaults to sanitizing provided
36
+ # request IDs or generating a UUID. If <tt>:simple</tt> is provided, provided
37
+ # request IDs will not be sanitized. A callable (expecting a single input of
38
+ # any possible existing request ID) may be provided to introduce more complex
39
+ # request ID handling.
40
+ attr_accessor :handler
41
+ # If +true+ (the default), the request correlation ID will be returned as
42
+ # part of the response headers. Only affects Marlowe::Middleware.
43
+ attr_accessor :return
44
+ # If +true+, Marlowe will add code to behave like
45
+ # <tt>ActionDispatch::RequestId</tt>. Depends on
46
+ # <tt>ActionDispatch::Request</tt>. Only affects Marlowe::Middleware.
47
+ attr_accessor :action_dispatch
48
+
49
+ # === Option Values
50
+ #
51
+ # <tt>:header</tt>:: The name of the header to inspect. Defaults to
52
+ # 'X-Request-Id'. Also available as
53
+ # <tt>:correlation_header</tt>.
54
+ # <tt>:handler</tt>:: The handler for request correlation IDs. Defaults to
55
+ # sanitizing provided request IDs or generating a UUID.
56
+ # If <tt>:simple</tt> is provided, provided request IDs
57
+ # will not be sanitized. A callable (expecting a single
58
+ # input of any possible existing request ID) may be
59
+ # provided to introduce more complex request ID
60
+ # handling.
61
+ # <tt>:return</tt>:: If +true+ (the default), the request correlation ID
62
+ # will be returned as part of the response headers.
63
+ # <tt>:action_dispatch</tt>:: If +true+, Marlowe will add code to behave
64
+ # like <tt>ActionDispatch::RequestId</tt>.
65
+ # Depends on <tt>ActionDispatch::Request</tt>.
66
+ def initialize(base = nil, opts = nil) # :yields: self
67
+ opts =
68
+ if base.nil? && opts.nil?
69
+ {}
70
+ elsif base.nil? && opts.is_a?(Hash)
71
+ opts
72
+ elsif base.is_a?(Hash) && opts.nil?
73
+ base
74
+ elsif base.is_a?(self.class) && opts.nil?
75
+ base.to_hash
76
+ elsif (base.is_a?(Hash) || base.is_a?(self.class)) && opts.is_a?(Hash)
77
+ hash =
78
+ if base.is_a?(self.class)
79
+ base.to_hash
80
+ else
81
+ base
82
+ end
83
+ hash.update(opts)
84
+ end
85
+
86
+ @header, @http_header = format_header_name(
87
+ opts[:header] || opts[:correlation_header] || CORRELATION_HEADER
88
+ )
89
+
90
+ @handler = opts.fetch(:handler, :clean)
91
+ @return = opts.fetch(:return, true)
92
+ @action_dispatch = opts.fetch(:action_dispatch, false)
93
+
94
+ yield self if block_given?
95
+
96
+ freeze
97
+ end
98
+
99
+ def to_hash
100
+ {
101
+ header: header,
102
+ handler: handler,
103
+ return: self.return,
104
+ action_dispatch: action_dispatch
105
+ }
106
+ end
107
+
108
+ private
109
+
110
+ def format_header_name(header)
111
+ [
112
+ header.to_s.tr("_", "-").freeze,
113
+ "HTTP_#{header.to_s.tr("-", "_").upcase}"
114
+ ]
115
+ end
116
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "marlowe"
4
+
5
+ # Marlowe correlation ID middleware for Faraday. Including this into your
6
+ # request middleware stack will use the captured correlation ID.
7
+ class Marlowe::Faraday < Faraday::Middleware
8
+ def initialize(app, opts = {})
9
+ super(app)
10
+ @config = Marlowe::Config.override(opts)
11
+ end
12
+
13
+ def call(env)
14
+ env[:request_headers][@config.header] =
15
+ Marlowe.make_request_id(RequestStore[:correlation_id], @config)
16
+ @app.call(env)
17
+ end
18
+ end
19
+
20
+ Faraday::Request.register_middleware marlowe: -> { Marlowe::Faraday }
@@ -6,11 +6,11 @@ require "securerandom"
6
6
 
7
7
  module Marlowe
8
8
  # Marlowe correlation id middleware. Including this into your middleware
9
- # stack will add a correlation id header as an incoming request, and save
10
- # that id in a request session variable.
9
+ # stack will capture or add a correlation id header on an incoming request,
10
+ # and save that id in a request session variable.
11
11
  class Middleware
12
12
  # The name of the default header to look for and put the correlation id in.
13
- CORRELATION_HEADER = "X-Request-Id" #:nodoc:
13
+ CORRELATION_HEADER = Marlowe::Config::CORRELATION_HEADER # :nodoc:
14
14
 
15
15
  # Configure the Marlowe middleware to call +app+ with options +opts+.
16
16
  #
@@ -31,67 +31,36 @@ module Marlowe
31
31
  # <tt>:action_dispatch</tt>:: If +true+, Marlowe will add code to behave
32
32
  # like <tt>ActionDispatch::RequestId</tt>.
33
33
  # Depends on <tt>ActionDispatch::Request</tt>.
34
- def initialize(app, opts = {})
34
+ def initialize(app, opts = nil)
35
35
  @app = app
36
- @header, @http_header = format_header_name(
37
- opts[:header] || opts[:correlation_header] || CORRELATION_HEADER
38
- )
39
- @handler = opts.fetch(:handler, :clean)
40
- @return = opts.fetch(:return, true)
41
- @action_dispatch = opts.fetch(:action_dispatch, false)
36
+ @config = Marlowe::Config.override(opts)
42
37
  end
43
38
 
44
39
  # Stores the incoming correlation id from the +env+ hash. If the correlation
45
40
  # id has not been sent, a new UUID is generated and the +env+ is modified.
46
41
  def call(env)
47
- req_id = make_request_id(env[@http_header])
48
- RequestStore.store[:correlation_id] = env[@http_header] = req_id
42
+ req_id = Marlowe.make_request_id(env[config.http_header], config)
43
+ RequestStore.store[:correlation_id] = env[config.http_header] = req_id
49
44
 
50
- if @action_dispatch
45
+ if config.action_dispatch
51
46
  req = ActionDispatch::Request.new(env)
52
47
  req.request_id = req_id
53
48
  end
54
49
 
55
- @app.call(env).tap { |_status, headers, _body|
56
- if @return
57
- headers[@header] = if @action_dispatch
58
- req.request_id
59
- else
60
- RequestStore.store[:correlation_id]
61
- end
50
+ app.call(env).tap { |_status, headers, _body|
51
+ if config.return
52
+ headers[config.header] =
53
+ if config.action_dispatch
54
+ req.request_id
55
+ else
56
+ RequestStore.store[:correlation_id]
57
+ end
62
58
  end
63
59
  }
64
60
  end
65
61
 
66
62
  private
67
63
 
68
- def format_header_name(header)
69
- [
70
- header.to_s.tr("_", "-").freeze,
71
- "HTTP_#{header.to_s.tr("-", "_").upcase}"
72
- ]
73
- end
74
-
75
- def make_request_id(request_id)
76
- if @handler == :simple
77
- simple(request_id)
78
- elsif @handler.is_a?(Proc)
79
- simple(@handler.call(request_id))
80
- else
81
- clean(request_id)
82
- end
83
- end
84
-
85
- def clean(request_id)
86
- simple(request_id).gsub(/[^\w\-]/, "")[0, 255]
87
- end
88
-
89
- def simple(request_id)
90
- if request_id && !request_id.empty? && request_id !~ /\A[[:space]]*\z/
91
- request_id
92
- else
93
- SecureRandom.uuid
94
- end
95
- end
64
+ attr_reader :app, :config
96
65
  end
97
66
  end
data/lib/marlowe/rails.rb CHANGED
@@ -6,23 +6,21 @@ module Marlowe
6
6
  config = app.config
7
7
 
8
8
  opts = {
9
- header: config.try(:marlowe_header) || config.try(:marlowe_correlation_header),
10
- handler: config.try(:marlowe_request_id_handler),
11
- return: config.try(:marlowe_return_request_id),
12
- action_dispatch: config.try(:marlowe_replace_action_dispatch_request_id)
9
+ header: config&.marlowe_header || config&.marlowe_correlation_header,
10
+ handler: config&.marlowe_request_id_handler,
11
+ return: config&.marlowe_return_request_id,
12
+ action_dispatch: config&.marlowe_replace_action_dispatch_request_id
13
13
  }.compact
14
14
 
15
15
  if opts[:action_dispatch]
16
- app.middleware.insert_before ActionDispatch::RequestId,
17
- Marlowe::Middleware, opts
16
+ app.middleware.insert_before ActionDispatch::RequestId, Marlowe::Middleware, opts
18
17
  app.middleware.delete ActionDispatch::RequestId
19
18
  else
20
- app.middleware.insert_before Rails::Rack::Logger, Marlowe::Middleware,
21
- opts
19
+ app.middleware.insert_before Rails::Rack::Logger, Marlowe::Middleware, opts
22
20
  end
23
21
  end
24
22
 
25
- def app #:nodoc:
23
+ def app # :nodoc:
26
24
  Rails.application
27
25
  end
28
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "request_store"
2
4
 
3
5
  module Marlowe
data/lib/marlowe.rb CHANGED
@@ -2,11 +2,44 @@
2
2
 
3
3
  # Marlowe, a correlation id injector.
4
4
  module Marlowe
5
- VERSION = "2.1" #:nodoc:
5
+ VERSION = "3.1" # :nodoc:
6
6
 
7
+ require "marlowe/config"
7
8
  require "marlowe/middleware"
8
9
  require "marlowe/rails" if defined? Rails::Railtie
9
10
 
10
11
  autoload :Formatter, "marlowe/formatter"
11
12
  autoload :SimpleFormatter, "marlowe/simple_formatter"
13
+
14
+ class << self
15
+ # Configure Marlowe
16
+ def configure(&block)
17
+ Marlowe::Config.configure(&block)
18
+ end
19
+
20
+ # Make a Marlowe request ID
21
+ def make_request_id(request_id, config = Marlowe::Config.global)
22
+ if config.handler == :simple
23
+ simple(request_id)
24
+ elsif config.handler.is_a?(Proc)
25
+ simple(config.handler.call(request_id))
26
+ else
27
+ clean(request_id)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def clean(request_id)
34
+ simple(request_id).gsub(/[^\w\-]/, "")[0, 255]
35
+ end
36
+
37
+ def simple(request_id)
38
+ if request_id && !request_id.empty? && request_id !~ /\A[[:space]]*\z/
39
+ request_id
40
+ else
41
+ SecureRandom.uuid
42
+ end
43
+ end
44
+ end
12
45
  end
@@ -9,3 +9,29 @@ require "minitest/focus"
9
9
  require "minitest/moar"
10
10
 
11
11
  require "marlowe"
12
+
13
+ RackV1 = Rack.release.start_with?("1.")
14
+
15
+ module NormalizeRackResponseHeaders
16
+ private
17
+
18
+ def has_header?(key)
19
+ if RackV1
20
+ last_response.header.key?(key)
21
+ else
22
+ last_response.has_header?(key)
23
+ end
24
+ end
25
+
26
+ def get_header(key)
27
+ if RackV1
28
+ last_response.header[key]
29
+ else
30
+ last_response.get_header(key)
31
+ end
32
+ end
33
+ end
34
+
35
+ class Minitest::Test
36
+ include NormalizeRackResponseHeaders
37
+ end
data/test/test_marlowe.rb CHANGED
@@ -9,6 +9,7 @@ class TestMarlowe < Minitest::Test
9
9
 
10
10
  def setup
11
11
  @marlowe_options = {}
12
+ Marlowe::Config.send(:clear_global!)
12
13
  end
13
14
 
14
15
  def app
@@ -28,75 +29,75 @@ class TestMarlowe < Minitest::Test
28
29
 
29
30
  def test_default_config_no_header_value
30
31
  get "/"
31
- assert last_response.header.key?("X-Request-Id")
32
- refute_empty last_response.header["X-Request-Id"]
33
- assert_equal last_response.header["X-Request-Id"], last_response.body
32
+ assert has_header?("X-Request-Id")
33
+ refute_empty get_header("X-Request-Id")
34
+ assert_equal get_header("X-Request-Id"), last_response.body
34
35
  end
35
36
 
36
37
  def test_default_config_with_header_value
37
38
  get "/", {}, {"HTTP_X_REQUEST_ID" => "testvalue"}
38
- assert last_response.header.key?("X-Request-Id")
39
- refute_empty last_response.header["X-Request-Id"]
40
- assert_equal last_response.header["X-Request-Id"], last_response.body
41
- assert_equal "testvalue", last_response.header["X-Request-Id"]
39
+ assert has_header?("X-Request-Id")
40
+ refute_empty get_header("X-Request-Id")
41
+ assert_equal get_header("X-Request-Id"), last_response.body
42
+ assert_equal "testvalue", get_header("X-Request-Id")
42
43
  end
43
44
 
44
45
  def test_header_config_no_header_value
45
46
  marlowe_options[:header] = "Correlation-Id"
46
47
  get "/"
47
- assert last_response.header.key?("Correlation-Id")
48
- refute_empty last_response.header["Correlation-Id"]
49
- assert_equal last_response.header["Correlation-Id"], last_response.body
48
+ assert has_header?("Correlation-Id")
49
+ refute_empty get_header("Correlation-Id")
50
+ assert_equal get_header("Correlation-Id"), last_response.body
50
51
  end
51
52
 
52
53
  def test_header_config_no_header_with_header_value
53
54
  marlowe_options[:header] = "Correlation-Id"
54
55
  get "/", {}, {"HTTP_CORRELATION_ID" => "testvalue"}
55
- assert last_response.header.key?("Correlation-Id")
56
- refute_empty last_response.header["Correlation-Id"]
57
- assert_equal last_response.header["Correlation-Id"], last_response.body
58
- assert_equal "testvalue", last_response.header["Correlation-Id"]
56
+ assert has_header?("Correlation-Id")
57
+ refute_empty get_header("Correlation-Id")
58
+ assert_equal get_header("Correlation-Id"), last_response.body
59
+ assert_equal "testvalue", get_header("Correlation-Id")
59
60
  end
60
61
 
61
62
  def test_handler_config_default_handler
62
63
  get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
63
- assert last_response.header.key?("X-Request-Id")
64
- refute_empty last_response.header["X-Request-Id"]
65
- assert_equal last_response.header["X-Request-Id"], last_response.body
66
- assert_equal "testvalue", last_response.header["X-Request-Id"]
64
+ assert has_header?("X-Request-Id")
65
+ refute_empty get_header("X-Request-Id")
66
+ assert_equal get_header("X-Request-Id"), last_response.body
67
+ assert_equal "testvalue", get_header("X-Request-Id")
67
68
  end
68
69
 
69
70
  def test_handler_config_with_simple_handler
70
71
  marlowe_options[:handler] = :simple
71
72
  get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
72
- assert last_response.header.key?("X-Request-Id")
73
- refute_empty last_response.header["X-Request-Id"]
74
- assert_equal last_response.header["X-Request-Id"], last_response.body
75
- assert_equal "test+value", last_response.header["X-Request-Id"]
73
+ assert has_header?("X-Request-Id")
74
+ refute_empty get_header("X-Request-Id")
75
+ assert_equal get_header("X-Request-Id"), last_response.body
76
+ assert_equal "test+value", get_header("X-Request-Id")
76
77
  end
77
78
 
78
79
  def test_handler_config_with_proc_handler
79
80
  marlowe_options[:handler] = ->(item) { item && item.reverse || SecureRandom.uuid }
80
81
  get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
81
- assert last_response.header.key?("X-Request-Id")
82
- refute_empty last_response.header["X-Request-Id"]
83
- assert_equal last_response.header["X-Request-Id"], last_response.body
84
- assert_equal "eulav+tset", last_response.header["X-Request-Id"]
82
+ assert has_header?("X-Request-Id")
83
+ refute_empty get_header("X-Request-Id")
84
+ assert_equal get_header("X-Request-Id"), last_response.body
85
+ assert_equal "eulav+tset", get_header("X-Request-Id")
85
86
  end
86
87
 
87
88
  def test_handler_config_with_proc_handler_returning_nil
88
- marlowe_options[:handler] = ->(item) {}
89
+ marlowe_options[:handler] = ->(_item) {}
89
90
  get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
90
- assert last_response.header.key?("X-Request-Id")
91
- refute_empty last_response.header["X-Request-Id"]
92
- assert_equal last_response.header["X-Request-Id"], last_response.body
93
- assert_match(/\A[-\w]+\z/, last_response.header["X-Request-Id"])
91
+ assert has_header?("X-Request-Id")
92
+ refute_empty get_header("X-Request-Id")
93
+ assert_equal get_header("X-Request-Id"), last_response.body
94
+ assert_match(/\A[-\w]+\z/, get_header("X-Request-Id"))
94
95
  end
95
96
 
96
97
  def test_return_config_false
97
98
  marlowe_options[:return] = false
98
99
  get "/"
99
- refute last_response.header.key?("X-Request-Id")
100
+ refute has_header?("X-Request-Id")
100
101
  assert_equal RequestStore[:correlation_id], last_response.body
101
102
  end
102
103
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest_config"
4
+
5
+ class TestMarloweConfig < Minitest::Test
6
+ include Rack::Test::Methods
7
+
8
+ attr_reader :marlowe_options
9
+
10
+ def setup
11
+ @marlowe_options = {}
12
+ Marlowe::Config.send(:clear_global!)
13
+ end
14
+
15
+ def app
16
+ Marlowe.configure do |config|
17
+ marlowe_options.each do |k, v|
18
+ config.send(:"#{k}=", v) if config.respond_to?(:"#{k}=")
19
+ end
20
+ end
21
+
22
+ options = marlowe_options
23
+ Rack::Builder.new do
24
+ use Marlowe::Middleware, options
25
+
26
+ run lambda { |_env|
27
+ [
28
+ 200,
29
+ {"Content-Type" => "text/plain"},
30
+ [RequestStore[:correlation_id]]
31
+ ]
32
+ }
33
+ end
34
+ end
35
+
36
+ def test_default_config_no_header_value
37
+ get "/"
38
+
39
+ assert has_header?("X-Request-Id")
40
+ refute_empty get_header("X-Request-Id")
41
+ assert_equal get_header("X-Request-Id"), last_response.body
42
+ end
43
+
44
+ def test_default_config_with_header_value
45
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "testvalue"}
46
+ assert has_header?("X-Request-Id")
47
+ refute_empty get_header("X-Request-Id")
48
+ assert_equal get_header("X-Request-Id"), last_response.body
49
+ assert_equal "testvalue", get_header("X-Request-Id")
50
+ end
51
+
52
+ def test_header_config_no_header_value
53
+ marlowe_options[:header] = "Correlation-Id"
54
+ get "/"
55
+ assert has_header?("Correlation-Id")
56
+ refute_empty get_header("Correlation-Id")
57
+ assert_equal get_header("Correlation-Id"), last_response.body
58
+ end
59
+
60
+ def test_header_config_no_header_with_header_value
61
+ marlowe_options[:header] = "Correlation-Id"
62
+ get "/", {}, {"HTTP_CORRELATION_ID" => "testvalue"}
63
+ assert has_header?("Correlation-Id")
64
+ refute_empty get_header("Correlation-Id")
65
+ assert_equal get_header("Correlation-Id"), last_response.body
66
+ assert_equal "testvalue", get_header("Correlation-Id")
67
+ end
68
+
69
+ def test_handler_config_default_handler
70
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
71
+ assert has_header?("X-Request-Id")
72
+ refute_empty get_header("X-Request-Id")
73
+ assert_equal get_header("X-Request-Id"), last_response.body
74
+ assert_equal "testvalue", get_header("X-Request-Id")
75
+ end
76
+
77
+ def test_handler_config_with_simple_handler
78
+ marlowe_options[:handler] = :simple
79
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
80
+ assert has_header?("X-Request-Id")
81
+ refute_empty get_header("X-Request-Id")
82
+ assert_equal get_header("X-Request-Id"), last_response.body
83
+ assert_equal "test+value", get_header("X-Request-Id")
84
+ end
85
+
86
+ def test_handler_config_with_proc_handler
87
+ marlowe_options[:handler] = ->(item) { item && item.reverse || SecureRandom.uuid }
88
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
89
+ assert has_header?("X-Request-Id")
90
+ refute_empty get_header("X-Request-Id")
91
+ assert_equal get_header("X-Request-Id"), last_response.body
92
+ assert_equal "eulav+tset", get_header("X-Request-Id")
93
+ end
94
+
95
+ def test_handler_config_with_proc_handler_returning_nil
96
+ marlowe_options[:handler] = ->(_item) {}
97
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
98
+ assert has_header?("X-Request-Id")
99
+ refute_empty get_header("X-Request-Id")
100
+ assert_equal get_header("X-Request-Id"), last_response.body
101
+ assert_match(/\A[-\w]+\z/, get_header("X-Request-Id"))
102
+ end
103
+
104
+ def test_return_config_false
105
+ marlowe_options[:return] = false
106
+ get "/"
107
+ refute has_header?("X-Request-Id")
108
+ assert_equal RequestStore[:correlation_id], last_response.body
109
+ end
110
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marlowe
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.1'
4
+ version: '3.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trevor Oke
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-09-09 00:00:00.000000000 Z
12
+ date: 2024-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: request_store
@@ -31,34 +31,34 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '0.9'
34
+ version: '1'
35
35
  - - "<"
36
36
  - !ruby/object:Gem::Version
37
- version: '3'
37
+ version: '4'
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
- version: '0.9'
44
+ version: '1'
45
45
  - - "<"
46
46
  - !ruby/object:Gem::Version
47
- version: '3'
47
+ version: '4'
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: minitest
50
50
  requirement: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.14'
54
+ version: '5.22'
55
55
  type: :development
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '5.14'
61
+ version: '5.22'
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: appraisal
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -102,19 +102,19 @@ dependencies:
102
102
  - !ruby/object:Gem::Version
103
103
  version: '1.1'
104
104
  - !ruby/object:Gem::Dependency
105
- name: hoe-git
105
+ name: hoe-git2
106
106
  requirement: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '1.6'
110
+ version: '1.7'
111
111
  type: :development
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '1.6'
117
+ version: '1.7'
118
118
  - !ruby/object:Gem::Dependency
119
119
  name: hoe-rubygems
120
120
  requirement: !ruby/object:Gem::Requirement
@@ -143,20 +143,6 @@ dependencies:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
145
  version: '1.0'
146
- - !ruby/object:Gem::Dependency
147
- name: minitest-bonus-assertions
148
- requirement: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '3.0'
153
- type: :development
154
- prerelease: false
155
- version_requirements: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '3.0'
160
146
  - !ruby/object:Gem::Dependency
161
147
  name: minitest-focus
162
148
  requirement: !ruby/object:Gem::Requirement
@@ -191,14 +177,14 @@ dependencies:
191
177
  requirements:
192
178
  - - "~>"
193
179
  - !ruby/object:Gem::Version
194
- version: '1.0'
180
+ version: '2.0'
195
181
  type: :development
196
182
  prerelease: false
197
183
  version_requirements: !ruby/object:Gem::Requirement
198
184
  requirements:
199
185
  - - "~>"
200
186
  - !ruby/object:Gem::Version
201
- version: '1.0'
187
+ version: '2.0'
202
188
  - !ruby/object:Gem::Dependency
203
189
  name: rake
204
190
  requirement: !ruby/object:Gem::Requirement
@@ -219,20 +205,6 @@ dependencies:
219
205
  - - "<"
220
206
  - !ruby/object:Gem::Version
221
207
  version: '14'
222
- - !ruby/object:Gem::Dependency
223
- name: rdoc
224
- requirement: !ruby/object:Gem::Requirement
225
- requirements:
226
- - - ">="
227
- - !ruby/object:Gem::Version
228
- version: '4.2'
229
- type: :development
230
- prerelease: false
231
- version_requirements: !ruby/object:Gem::Requirement
232
- requirements:
233
- - - ">="
234
- - !ruby/object:Gem::Version
235
- version: '4.2'
236
208
  - !ruby/object:Gem::Dependency
237
209
  name: standard
238
210
  requirement: !ruby/object:Gem::Requirement
@@ -262,33 +234,39 @@ dependencies:
262
234
  - !ruby/object:Gem::Version
263
235
  version: '0.21'
264
236
  - !ruby/object:Gem::Dependency
265
- name: psych
237
+ name: rdoc
266
238
  requirement: !ruby/object:Gem::Requirement
267
239
  requirements:
268
- - - "~>"
240
+ - - ">="
269
241
  - !ruby/object:Gem::Version
270
- version: '3.1'
242
+ version: '4.0'
243
+ - - "<"
244
+ - !ruby/object:Gem::Version
245
+ version: '7'
271
246
  type: :development
272
247
  prerelease: false
273
248
  version_requirements: !ruby/object:Gem::Requirement
274
249
  requirements:
275
- - - "~>"
250
+ - - ">="
276
251
  - !ruby/object:Gem::Version
277
- version: '3.1'
252
+ version: '4.0'
253
+ - - "<"
254
+ - !ruby/object:Gem::Version
255
+ version: '7'
278
256
  - !ruby/object:Gem::Dependency
279
257
  name: hoe
280
258
  requirement: !ruby/object:Gem::Requirement
281
259
  requirements:
282
260
  - - "~>"
283
261
  - !ruby/object:Gem::Version
284
- version: '3.23'
262
+ version: '4.2'
285
263
  type: :development
286
264
  prerelease: false
287
265
  version_requirements: !ruby/object:Gem::Requirement
288
266
  requirements:
289
267
  - - "~>"
290
268
  - !ruby/object:Gem::Version
291
- version: '3.23'
269
+ version: '4.2'
292
270
  description: |-
293
271
  {Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that
294
272
  extracts or creates a request ID using a pre-defined header, permitting request
@@ -296,6 +274,8 @@ description: |-
296
274
 
297
275
  When using Rails, Marlowe automatically adds itself to the middleware before
298
276
  <tt>Rails::Rack::Logger</tt>.
277
+
278
+ As of Marlowe 3.0, a Faraday middleware is provided (<tt>require 'marlowe/faraday'</tt>).
299
279
  email:
300
280
  - toke@kineticcafe.com
301
281
  - dev@kineticcafe.com
@@ -308,6 +288,8 @@ extra_rdoc_files:
308
288
  - Manifest.txt
309
289
  - README.rdoc
310
290
  files:
291
+ - ".github/workflows/ruby.yml"
292
+ - ".standard.yml"
311
293
  - Contributing.md
312
294
  - History.md
313
295
  - Licence.md
@@ -315,12 +297,15 @@ files:
315
297
  - README.rdoc
316
298
  - Rakefile
317
299
  - lib/marlowe.rb
300
+ - lib/marlowe/config.rb
301
+ - lib/marlowe/faraday.rb
318
302
  - lib/marlowe/formatter.rb
319
303
  - lib/marlowe/middleware.rb
320
304
  - lib/marlowe/rails.rb
321
305
  - lib/marlowe/simple_formatter.rb
322
306
  - test/minitest_config.rb
323
307
  - test/test_marlowe.rb
308
+ - test/test_marlowe_config.rb
324
309
  homepage: https://github.com/KineticCafe/marlowe/
325
310
  licenses:
326
311
  - MIT
@@ -347,7 +332,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
347
332
  - !ruby/object:Gem::Version
348
333
  version: '0'
349
334
  requirements: []
350
- rubygems_version: 3.1.6
335
+ rubygems_version: 3.4.10
351
336
  signing_key:
352
337
  specification_version: 4
353
338
  summary: "{Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that