marlowe 2.1 → 3.0

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: 5a38beee250c02c545fa9a16e99a7e32702f4e027e2d9b87bfee4911210d6f7e
4
+ data.tar.gz: 3c4b0d3dddec1f5706076a36036fe1b1fcc26a4a662a7ce134fc4e7df7fa5579
5
5
  SHA512:
6
- metadata.gz: 6dfa8bd46ddc990b643db1d01925da2a694fba37466c4dfacaab2dd36c776aa8f6f26e8fa64b5e35f5e52d91095e8ad1cf368a382f5c2188defc62596be1a63d
7
- data.tar.gz: ee94d6e57c9dc02ae7690977281dc9a62051465ef45330474aac3e2284f68d438099409736a32806ecff61da2f9191d8eaf9869300f48e6464c943ee6caf608a
6
+ metadata.gz: 1adb8585c41f04ae645af9080f97ae115d2977ed1e0fa0d2bace7b6b896b6dc3e9c10fca4938a40dcd7727a98a552312be9a514e883d48a0dd9058986b7b85ac
7
+ data.tar.gz: 80bfa345158bcf410251861067b071bbef0362cccac7866c50febbf6b9db557498a6b561aed47b1e62ea0de5988d15a510c896ceca017cc8193c386b1f0a5cdd
@@ -0,0 +1,55 @@
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
+ - head
25
+ - jruby
26
+ - jruby-head
27
+ - truffleruby
28
+ - truffleruby-head
29
+ - truffleruby+graalvm
30
+ - truffleruby+graalvm-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.1'
40
+
41
+ runs-on: ${{ matrix.os }}
42
+
43
+ continue-on-error: ${{ matrix.continue-on-error || false }}
44
+
45
+ steps:
46
+ - uses: actions/checkout@v3
47
+ - uses: ruby/setup-ruby@v1
48
+ with:
49
+ ruby-version: ${{ matrix.ruby }}
50
+ bundler-cache: true
51
+
52
+ - run: bundle exec ruby -S rake test --trace
53
+ - run: bundle exec ruby -S appraisal install
54
+ - run: bundle exec ruby -S appraisal rake test
55
+ - run: bundle exec standardrb
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,3 +1,10 @@
1
+ ### 3.0 / 2022-09-11
2
+
3
+ - Added a Faraday request middleware.
4
+ - Replaced Hurley example with examples for the use of the Faraday
5
+ middleware.
6
+ - Added global Marlowe configuration.
7
+
1
8
  ### 2.1 / 2021-09-08
2
9
 
3
10
  - Allow the use of Ruby 3.
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,7 +23,7 @@ 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"]
@@ -35,7 +35,7 @@ spec = Hoe.spec "marlowe" do
35
35
  extra_dev_deps << ["minitest-bonus-assertions", "~> 3.0"]
36
36
  extra_dev_deps << ["minitest-focus", "~> 1.1"]
37
37
  extra_dev_deps << ["minitest-moar", "~> 0.0"]
38
- extra_dev_deps << ["rack-test", "~> 1.0"]
38
+ extra_dev_deps << ["rack-test", "~> 2.0"]
39
39
  extra_dev_deps << ["rake", ">= 10.0", "< 14"]
40
40
  extra_dev_deps << ["rdoc", ">= 4.2"]
41
41
  extra_dev_deps << ["standard", "~> 1.0"]
@@ -45,7 +45,7 @@ end
45
45
 
46
46
  ENV["RUBYOPT"] = "-W0"
47
47
 
48
- module Hoe::Publish #:nodoc:
48
+ module Hoe::Publish # :nodoc:
49
49
  alias_method :__make_rdoc_cmd__marlowe__, :make_rdoc_cmd
50
50
 
51
51
  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.0" # :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
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
@@ -85,7 +86,7 @@ class TestMarlowe < Minitest::Test
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
91
  assert last_response.header.key?("X-Request-Id")
91
92
  refute_empty last_response.header["X-Request-Id"]
@@ -0,0 +1,109 @@
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
+ 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
+ end
42
+
43
+ def test_default_config_with_header_value
44
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "testvalue"}
45
+ assert last_response.header.key?("X-Request-Id")
46
+ refute_empty last_response.header["X-Request-Id"]
47
+ assert_equal last_response.header["X-Request-Id"], last_response.body
48
+ assert_equal "testvalue", last_response.header["X-Request-Id"]
49
+ end
50
+
51
+ def test_header_config_no_header_value
52
+ marlowe_options[:header] = "Correlation-Id"
53
+ get "/"
54
+ assert last_response.header.key?("Correlation-Id")
55
+ refute_empty last_response.header["Correlation-Id"]
56
+ assert_equal last_response.header["Correlation-Id"], last_response.body
57
+ end
58
+
59
+ def test_header_config_no_header_with_header_value
60
+ marlowe_options[:header] = "Correlation-Id"
61
+ get "/", {}, {"HTTP_CORRELATION_ID" => "testvalue"}
62
+ assert last_response.header.key?("Correlation-Id")
63
+ refute_empty last_response.header["Correlation-Id"]
64
+ assert_equal last_response.header["Correlation-Id"], last_response.body
65
+ assert_equal "testvalue", last_response.header["Correlation-Id"]
66
+ end
67
+
68
+ def test_handler_config_default_handler
69
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
70
+ assert last_response.header.key?("X-Request-Id")
71
+ refute_empty last_response.header["X-Request-Id"]
72
+ assert_equal last_response.header["X-Request-Id"], last_response.body
73
+ assert_equal "testvalue", last_response.header["X-Request-Id"]
74
+ end
75
+
76
+ def test_handler_config_with_simple_handler
77
+ marlowe_options[:handler] = :simple
78
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
79
+ assert last_response.header.key?("X-Request-Id")
80
+ refute_empty last_response.header["X-Request-Id"]
81
+ assert_equal last_response.header["X-Request-Id"], last_response.body
82
+ assert_equal "test+value", last_response.header["X-Request-Id"]
83
+ end
84
+
85
+ def test_handler_config_with_proc_handler
86
+ marlowe_options[:handler] = ->(item) { item && item.reverse || SecureRandom.uuid }
87
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
88
+ assert last_response.header.key?("X-Request-Id")
89
+ refute_empty last_response.header["X-Request-Id"]
90
+ assert_equal last_response.header["X-Request-Id"], last_response.body
91
+ assert_equal "eulav+tset", last_response.header["X-Request-Id"]
92
+ end
93
+
94
+ def test_handler_config_with_proc_handler_returning_nil
95
+ marlowe_options[:handler] = ->(_item) {}
96
+ get "/", {}, {"HTTP_X_REQUEST_ID" => "test+value"}
97
+ assert last_response.header.key?("X-Request-Id")
98
+ refute_empty last_response.header["X-Request-Id"]
99
+ assert_equal last_response.header["X-Request-Id"], last_response.body
100
+ assert_match(/\A[-\w]+\z/, last_response.header["X-Request-Id"])
101
+ end
102
+
103
+ def test_return_config_false
104
+ marlowe_options[:return] = false
105
+ get "/"
106
+ refute last_response.header.key?("X-Request-Id")
107
+ assert_equal RequestStore[:correlation_id], last_response.body
108
+ end
109
+ 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.0'
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: 2022-09-12 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.16'
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.16'
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: appraisal
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -191,14 +191,14 @@ dependencies:
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '1.0'
194
+ version: '2.0'
195
195
  type: :development
196
196
  prerelease: false
197
197
  version_requirements: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: '1.0'
201
+ version: '2.0'
202
202
  - !ruby/object:Gem::Dependency
203
203
  name: rake
204
204
  requirement: !ruby/object:Gem::Requirement
@@ -281,14 +281,14 @@ dependencies:
281
281
  requirements:
282
282
  - - "~>"
283
283
  - !ruby/object:Gem::Version
284
- version: '3.23'
284
+ version: '3.25'
285
285
  type: :development
286
286
  prerelease: false
287
287
  version_requirements: !ruby/object:Gem::Requirement
288
288
  requirements:
289
289
  - - "~>"
290
290
  - !ruby/object:Gem::Version
291
- version: '3.23'
291
+ version: '3.25'
292
292
  description: |-
293
293
  {Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that
294
294
  extracts or creates a request ID using a pre-defined header, permitting request
@@ -296,6 +296,8 @@ description: |-
296
296
 
297
297
  When using Rails, Marlowe automatically adds itself to the middleware before
298
298
  <tt>Rails::Rack::Logger</tt>.
299
+
300
+ As of Marlowe 3.0, a Faraday middleware is provided (<tt>require 'marlowe/faraday'</tt>).
299
301
  email:
300
302
  - toke@kineticcafe.com
301
303
  - dev@kineticcafe.com
@@ -308,6 +310,8 @@ extra_rdoc_files:
308
310
  - Manifest.txt
309
311
  - README.rdoc
310
312
  files:
313
+ - ".github/workflows/ruby.yml"
314
+ - ".standard.yml"
311
315
  - Contributing.md
312
316
  - History.md
313
317
  - Licence.md
@@ -315,12 +319,15 @@ files:
315
319
  - README.rdoc
316
320
  - Rakefile
317
321
  - lib/marlowe.rb
322
+ - lib/marlowe/config.rb
323
+ - lib/marlowe/faraday.rb
318
324
  - lib/marlowe/formatter.rb
319
325
  - lib/marlowe/middleware.rb
320
326
  - lib/marlowe/rails.rb
321
327
  - lib/marlowe/simple_formatter.rb
322
328
  - test/minitest_config.rb
323
329
  - test/test_marlowe.rb
330
+ - test/test_marlowe_config.rb
324
331
  homepage: https://github.com/KineticCafe/marlowe/
325
332
  licenses:
326
333
  - MIT
@@ -347,7 +354,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
347
354
  - !ruby/object:Gem::Version
348
355
  version: '0'
349
356
  requirements: []
350
- rubygems_version: 3.1.6
357
+ rubygems_version: 3.3.7
351
358
  signing_key:
352
359
  specification_version: 4
353
360
  summary: "{Marlowe}[https://github.com/KineticCafe/marlowe] is a Rack middleware that