affiliation_id 0.1.0 → 0.2.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: 22c3498448dc238453b78b0ee487f46530e2f1f5f4df6795ddf84575c8688da5
4
- data.tar.gz: 35fe8f9468138f049958da4d45bd831197f232ed80c4a3757ab18a630456ff62
3
+ metadata.gz: 35a5a46ef033ee61169a80dce4812d80ed06c0e53806dfe11f9758629b8e6e75
4
+ data.tar.gz: ec88467895ef1cd544ed846067d235dc3ddac81bed797a77f3f6530d40746fe3
5
5
  SHA512:
6
- metadata.gz: 392593ddf433ed21d096440c3519b2e146cdab79d8e33616dd5e60544e850b24cc1bd4e57747bec2f6231406d3f018679a060272575f2e01c65cf37f8dca5024
7
- data.tar.gz: da684bd4265d1907b6ccd7b42dcaf2c3aa8359854542399b8301e400942b19f1b6236dde0c7eb48acea7d994ddbb63c764ce4f229c528da8f645760b3d38fd73
6
+ metadata.gz: 0ed858f2409de3db4083879c2422d836b15d19b51aa4c365b17cf64d5cf51f414a2cb1e0cf0b063d01f50ad648a93cbfcb61859a783aa8b018ac98f44a23b7db
7
+ data.tar.gz: '09830569408ffc91ba83cfd7e35fb9d0847d6f5c60db0bbe3833a866652c2e32d1c927f4b5f80c2f1494a6dc3a7cf6b8a23367751bd34ed5f9af7c9c94b6afe2'
data/.overcommit.yml ADDED
@@ -0,0 +1,31 @@
1
+ # Use this file to configure the Overcommit hooks you wish to use. This will
2
+ # extend the default configuration defined in:
3
+ # https://github.com/sds/overcommit/blob/master/config/default.yml
4
+ #
5
+ # At the topmost level of this YAML file is a key representing type of hook
6
+ # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
7
+ # customize each hook, such as whether to only run it on certain files (via
8
+ # `include`), whether to only display output if it fails (via `quiet`), etc.
9
+ #
10
+ # For a complete list of hooks, see:
11
+ # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
12
+ #
13
+ # For a complete list of options that you can use to customize hooks, see:
14
+ # https://github.com/sds/overcommit#configuration
15
+ #
16
+ # Uncomment the following lines to make the configuration take effect.
17
+
18
+ PreCommit:
19
+ RuboCop:
20
+ enabled: true
21
+ on_warn: fail # Treat all warnings as failures
22
+
23
+ TrailingWhitespace:
24
+ enabled: true
25
+
26
+ #PostCheckout:
27
+ # ALL: # Special hook name that customizes all hooks of this type
28
+ # quiet: true # Change all post-checkout hooks to only display output on failure
29
+ #
30
+ # IndexTags:
31
+ # enabled: true # Generate a tags file with `ctags` each time HEAD changes
data/.rubocop.yml CHANGED
@@ -1,5 +1,15 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.7
3
+ NewCops: enable
3
4
 
4
5
  Layout/LineLength:
5
6
  Max: 120
7
+
8
+ # ====================================================
9
+ # Metrics Cops
10
+ # ====================================================
11
+ Metrics/BlockLength:
12
+ Enabled: true
13
+ Exclude:
14
+ - spec/**/*
15
+ - Guardfile
data/Gemfile CHANGED
@@ -7,6 +7,12 @@ gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
9
 
10
- gem 'rspec', '~> 3.0'
10
+ group :test do
11
+ gem 'rspec', '~> 3.0'
12
+ end
11
13
 
12
- gem 'rubocop', '~> 1.21'
14
+ group :development, :test do
15
+ gem 'guard-rspec', '~> 4.7', '>= 4.7.3', require: false
16
+ gem 'overcommit', '~> 0.59.1', require: false
17
+ gem 'rubocop', '~> 1.21'
18
+ end
data/Gemfile.lock CHANGED
@@ -1,19 +1,65 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- affiliation_id (0.1.0)
4
+ affiliation_id (0.2.0)
5
+ faraday (>= 0.17.0, < 3)
6
+ rack (>= 1.4)
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
8
10
  specs:
9
11
  ast (2.4.2)
12
+ byebug (11.1.3)
13
+ childprocess (4.1.0)
14
+ coderay (1.1.3)
10
15
  diff-lcs (1.5.0)
16
+ faraday (2.5.2)
17
+ faraday-net_http (>= 2.0, < 3.1)
18
+ ruby2_keywords (>= 0.0.4)
19
+ faraday-net_http (3.0.0)
20
+ ffi (1.15.5)
21
+ formatador (1.1.0)
22
+ guard (2.18.0)
23
+ formatador (>= 0.2.4)
24
+ listen (>= 2.7, < 4.0)
25
+ lumberjack (>= 1.0.12, < 2.0)
26
+ nenv (~> 0.1)
27
+ notiffany (~> 0.0)
28
+ pry (>= 0.13.0)
29
+ shellany (~> 0.0)
30
+ thor (>= 0.18.1)
31
+ guard-compat (1.2.1)
32
+ guard-rspec (4.7.3)
33
+ guard (~> 2.1)
34
+ guard-compat (~> 1.1)
35
+ rspec (>= 2.99.0, < 4.0)
36
+ iniparse (1.5.0)
11
37
  json (2.6.2)
38
+ listen (3.7.1)
39
+ rb-fsevent (~> 0.10, >= 0.10.3)
40
+ rb-inotify (~> 0.9, >= 0.9.10)
41
+ lumberjack (1.2.8)
42
+ method_source (1.0.0)
43
+ nenv (0.3.0)
44
+ notiffany (0.1.3)
45
+ nenv (~> 0.1)
46
+ shellany (~> 0.0)
47
+ overcommit (0.59.1)
48
+ childprocess (>= 0.6.3, < 5)
49
+ iniparse (~> 1.4)
50
+ rexml (~> 3.2)
12
51
  parallel (1.22.1)
13
52
  parser (3.1.2.0)
14
53
  ast (~> 2.4.1)
54
+ pry (0.14.1)
55
+ coderay (~> 1.1)
56
+ method_source (~> 1.0)
57
+ rack (2.2.4)
15
58
  rainbow (3.1.1)
16
59
  rake (13.0.6)
60
+ rb-fsevent (0.11.1)
61
+ rb-inotify (0.10.1)
62
+ ffi (~> 1.0)
17
63
  regexp_parser (2.5.0)
18
64
  rexml (3.2.5)
19
65
  rspec (3.11.0)
@@ -42,6 +88,9 @@ GEM
42
88
  rubocop-ast (1.19.1)
43
89
  parser (>= 3.1.1.0)
44
90
  ruby-progressbar (1.11.0)
91
+ ruby2_keywords (0.0.5)
92
+ shellany (0.0.1)
93
+ thor (1.2.1)
45
94
  unicode-display_width (2.2.0)
46
95
 
47
96
  PLATFORMS
@@ -49,6 +98,9 @@ PLATFORMS
49
98
 
50
99
  DEPENDENCIES
51
100
  affiliation_id!
101
+ byebug (~> 11.1, >= 11.1.3)
102
+ guard-rspec (~> 4.7, >= 4.7.3)
103
+ overcommit (~> 0.59.1)
52
104
  rake (~> 13.0)
53
105
  rspec (~> 3.0)
54
106
  rubocop (~> 1.21)
data/Guardfile ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ # directories %w(app lib config test spec features) \
8
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
+
10
+ ## NOTE: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ # NOTE: The cmd option is now required due to the increasing number of ways
21
+ # rspec may be run, below are examples of the most common uses.
22
+ # * bundler: 'bundle exec rspec'
23
+ # * bundler binstubs: 'bin/rspec'
24
+ # * spring: 'bin/rspec' (This will use spring if running and you have
25
+ # installed the spring binstubs per the docs)
26
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
27
+ # * 'just' rspec: 'rspec'
28
+
29
+ guard :rspec, cmd: 'bundle exec rspec' do
30
+ require 'guard/rspec/dsl'
31
+ dsl = Guard::RSpec::Dsl.new(self)
32
+
33
+ # Feel free to open issues for suggestions and improvements
34
+
35
+ # RSpec files
36
+ rspec = dsl.rspec
37
+ watch(rspec.spec_helper) { rspec.spec_dir }
38
+ watch(rspec.spec_support) { rspec.spec_dir }
39
+ watch(rspec.spec_files)
40
+
41
+ # Ruby files
42
+ ruby = dsl.ruby
43
+ dsl.watch_spec_files_for(ruby.lib_files)
44
+ end
data/README.md CHANGED
@@ -1,8 +1,21 @@
1
1
  # AffiliationId
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/affiliation_id`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ AffiliationId is a middleware collection for different frameworks and gems with the purpose of making end-to-end request tracing as easy as possible.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ The concept is really simple and it's meant to work like this:
6
+
7
+ 1. A request that reaches a web app or API it's the entry point and should have a unique ID.
8
+ 2. That ID is propagated throughout the app in all parts that are a consequence of the initial request, things like: requests to third-party APIs, Sidekiq jobs, error trackers, etc.
9
+ 3. The ID is included in all the logs statements so they can be traced based on the ID
10
+
11
+ Although there are tools like [OpenTelemetry](https://opentelemetry.io/), which is great, that is a much more complex tool and brings a lot of overhead that maybe is not worth it for some applications or teams.
12
+
13
+ Currently implemented middleware:
14
+
15
+ - Rack
16
+ - Rails
17
+ - Faraday
18
+ - Sidekiq
6
19
 
7
20
  ## Installation
8
21
 
@@ -22,7 +35,125 @@ Or install it yourself as:
22
35
 
23
36
  ## Usage
24
37
 
25
- TODO: Write usage instructions here
38
+ Under the hood AffiliationId uses `SecureRandom.uuid` to generate unique ID's when needed.
39
+
40
+ ```ruby
41
+ # Get current ID
42
+ AffiliationId.current_id
43
+ => '93f971bb-b889-4223-ac57-5d39f34051a4'
44
+ ```
45
+
46
+ `.current_id` is memoized and subsequent calls will return the same value
47
+
48
+ ```ruby
49
+ # Set ID
50
+ AffiliationId.current_id = 'myID'
51
+
52
+ AffiliationId.current_id
53
+ => 'myID'
54
+ ```
55
+
56
+ ```ruby
57
+ # Overwrite current_id with a new generated value
58
+ AffiliationId.current_id
59
+ => '93f971bb-b889-4223-ac57-5d39f34051a4'
60
+
61
+ AffiliationId.renew_current_id!
62
+ =>'55e94f67-5fce-4226-98d5-4c149684debe'
63
+
64
+ AffiliationId.current_id
65
+ => '55e94f67-5fce-4226-98d5-4c149684debe'
66
+ ```
67
+
68
+ ```ruby
69
+ # Reset/Clear current_id
70
+
71
+ AffiliationId.current_id
72
+ => '55e94f67-5fce-4226-98d5-4c149684debe'
73
+
74
+ AffiliationId.reset!
75
+ => nil
76
+ ```
77
+
78
+ ### Rack
79
+
80
+ The middleware for rack based applications is inspired from the Rails version of this middleware [ActionDispatch::RequestId](https://api.rubyonrails.org/classes/ActionDispatch/RequestId.html).
81
+
82
+ It looks at the `X-Affiliation-ID`, if the header is found and a value is present, this will be set as the `Affiliation.current_id` throughout the request, otherwise an random ID is generated.
83
+
84
+ To use the middleware, it needs to be added to the app middleware stack.
85
+
86
+ ```ruby
87
+ # config.ru
88
+ require 'affiliation_id/middleware/rack'
89
+
90
+ use AffiliationId::Middleware::Rack
91
+
92
+ run MyApp.new
93
+ ```
94
+
95
+ ### Rails
96
+
97
+ In true Rails fashion, this works out of the box after installing the gem. Although it still depends on [ActionDispatch::RequestId](https://api.rubyonrails.org/classes/ActionDispatch/RequestId.html) which is included by default in all new Rails apps.
98
+
99
+ ### Faraday
100
+
101
+ By using the Faraday middleware the, all requests will include a header `X-Affiliation-ID` with the value of `AffiliationID.current_id`.
102
+
103
+ ```ruby
104
+ require 'affiliation_id/middleware/faraday'
105
+
106
+ # ...
107
+
108
+ conn = Faraday.new do |f|
109
+ f.request :affiliation_id # include AffiliationID.current_id in the request headers
110
+ f.adapter :net_http # Use the Net::HTTP adapter
111
+ end
112
+ ```
113
+
114
+ ### Sidekiq
115
+
116
+ For more information on how [Sidekiq Middleware](https://github.com/mperham/sidekiq/wiki/Middleware) works, I suggest reading id directly from the Sidekiq documentation.
117
+
118
+ Configuration example:
119
+
120
+ ```ruby
121
+ # sidekiq_initializer.rb
122
+
123
+ require 'affiliation_id/middleware/sidekiq_client'
124
+ require 'affiliation_id/middleware/sidekiq_server'
125
+
126
+ Sidekiq.configure_client do |config|
127
+ config.client_middleware do |chain|
128
+ chain.add AffiliationId::Middleware::SidekiqClient
129
+ end
130
+ end
131
+
132
+ Sidekiq.configure_server do |config|
133
+ config.client_middleware do |chain|
134
+ chain.add AffiliationId::Middleware::SidekiqClient
135
+ end
136
+ config.server_middleware do |chain|
137
+ chain.add AffiliationId::Middleware::SidekiqServer
138
+ end
139
+ end
140
+ ```
141
+
142
+ ## Configuration
143
+
144
+ ```ruby
145
+ # affiliation_id_initializer.rb
146
+
147
+ AffiliationId.configure do |config|
148
+ # By default AffiliationId.current_id will raise an AffiliationId::MissingCurrentId exception if the value was not previously set.
149
+ # To opt in to the behavior of generating the id automatically config the following setting to false.
150
+ # config.enforce_explicit_current_id = false (Default: true)
151
+
152
+ # AffiliationId uses 'X-Request-ID' as the default header name.
153
+ # This can be changed by the following config.
154
+ # config.header_name = 'My-Custom-Header' (Default: 'X-Request-ID')
155
+ end
156
+ ```
26
157
 
27
158
  ## Development
28
159
 
@@ -32,7 +163,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
163
 
33
164
  ## Contributing
34
165
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/affiliation_id. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/affiliation_id/blob/main/CODE_OF_CONDUCT.md).
166
+ Bug reports and pull requests are welcome on GitHub at https://github.com/siklodi-mariusz/affiliation_id. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/siklodi-mariusz/affiliation_id/blob/main/CODE_OF_CONDUCT.md).
36
167
 
37
168
  ## License
38
169
 
@@ -40,4 +171,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
171
 
41
172
  ## Code of Conduct
42
173
 
43
- Everyone interacting in the AffiliationId project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/affiliation_id/blob/main/CODE_OF_CONDUCT.md).
174
+ Everyone interacting in the AffiliationId project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/siklodi-mariusz/affiliation_id/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/affiliation_id/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'affiliation_id'
7
+ spec.version = AffiliationId::VERSION
8
+
9
+ spec.authors = ['Mariusz Siklodi']
10
+ spec.summary = 'Track requests by affiliating them with an uniq id.'
11
+ spec.homepage = 'https://github.com/siklodi-mariusz/affiliation_id'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = '>= 2.7.0'
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['rubygems_mfa_required'] = 'true'
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.require_paths = ['lib']
27
+
28
+ # Uncomment to register a new dependency of your gem
29
+ spec.add_dependency 'faraday', '>= 0.17.0', '< 3'
30
+ spec.add_dependency 'rack', '>= 1.4'
31
+
32
+ spec.add_development_dependency 'byebug', '~> 11.1', '>= 11.1.3'
33
+
34
+ # For more information and examples about making a new gem, checkout our
35
+ # guide at: https://bundler.io/guides/creating_gem.html
36
+ end
data/bin/setup CHANGED
@@ -4,5 +4,6 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
+ overcommit --install
7
8
 
8
9
  # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AffiliationId
4
+ class Configuration # :nodoc:
5
+ HEADER_NAME = 'X-Request-ID'
6
+ attr_accessor :enforce_explicit_current_id, :header_name
7
+
8
+ def initialize
9
+ @enforce_explicit_current_id = true
10
+ @header_name = HEADER_NAME
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ require_relative '../../affiliation_id'
6
+
7
+ module AffiliationId
8
+ module Middleware
9
+ #
10
+ # Faraday::Middleware for handling requests made with Faraday with affiliation_id
11
+ #
12
+ # Usage:
13
+ # conn = Faraday.new do |f|
14
+ # f.request :affiliation_id # include AffiliationID.current_id in the request headers
15
+ # f.adapter :net_http # Use the Net::HTTP adapter
16
+ # end
17
+ #
18
+ class Faraday < ::Faraday::Middleware
19
+ def call(env)
20
+ env[:request_headers][config.header_name] ||= AffiliationId.current_id
21
+
22
+ @app.call(env)
23
+ end
24
+
25
+ private
26
+
27
+ def config
28
+ AffiliationId.configuration
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ if Faraday::Request.respond_to? :register_middleware
35
+ Faraday::Request.register_middleware affiliation_id: AffiliationId::Middleware::Faraday
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../affiliation_id'
4
+
5
+ module AffiliationId
6
+ module Middleware
7
+ #
8
+ # Rack middleware to add Affiliation ID HTTP header to rack based web frameworks
9
+ #
10
+ class Rack
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ AffiliationId.current_id = header_from_env(env) || AffiliationId.renew_current_id!
17
+
18
+ @app.call(env).tap { |_status, headers, _body| headers[config.header_name] = AffiliationId.current_id }
19
+ ensure
20
+ AffiliationId.reset!
21
+ end
22
+
23
+ private
24
+
25
+ def header_from_env(env)
26
+ value = env["HTTP_#{config.header_name.upcase.tr('-', '_')}"]
27
+
28
+ value&.gsub(/[^\w\-@.]/, '')&.[](0...255)
29
+ end
30
+
31
+ def config
32
+ AffiliationId.configuration
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../affiliation_id'
4
+
5
+ module AffiliationId
6
+ module Middleware
7
+ class Rails # :nodoc:
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ req = ActionDispatch::Request.new env
14
+ AffiliationId.current_id = req.request_id
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../affiliation_id'
4
+
5
+ module AffiliationId
6
+ module Middleware
7
+ #
8
+ # Sidekiq client middleware to inject the AffiliationId.current_id in the Job
9
+ #
10
+ # When Sidekiq client pushes a job in queue, at minimum, the job contains the following attributes:
11
+ #
12
+ # {
13
+ # "class": "SomeWorker",
14
+ # "jid": "b4a577edbccf1d805744efa9", // 12-byte random number as 24 char hex string
15
+ # "args": [1, "arg", true],
16
+ # "created_at": 1234567890,
17
+ # "enqueued_at": 1234567890
18
+ # }
19
+ #
20
+ # This middleware adds to this list of attributes the current value of AffiliationId.current_id.
21
+ # So, if AffiliationId.current_id returns a value of "93f971bb-b889-4223-ac57-5d39f34051a4",
22
+ # the end result will look something like this:
23
+ #
24
+ # {
25
+ # "class": "SomeWorker",
26
+ # "jid": "b4a577edbccf1d805744efa9", // 12-byte random number as 24 char hex string
27
+ # "args": [1, "arg", true],
28
+ # "created_at": 1234567890,
29
+ # "enqueued_at": 1234567890,
30
+ # "affiliation_id": "93f971bb-b889-4223-ac57-5d39f34051a4"
31
+ # }
32
+ #
33
+ #
34
+ class SidekiqClient
35
+ def call(_, job, _, _)
36
+ job[::AffiliationId::SIDEKIQ_JOB_KEY] = ::AffiliationId.current_id
37
+ yield
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../affiliation_id'
4
+
5
+ module AffiliationId
6
+ module Middleware
7
+ #
8
+ # Sidekiq server middleware to set AffiliationId.current_id from the job hash
9
+ #
10
+ # Assuming AffiliationId::Middleware::SidekiqClient is used.
11
+ # All jobs pushed in the queue will include an affiliation_id
12
+ #
13
+ # {
14
+ # "class": "SomeWorker",
15
+ # "jid": "b4a577edbccf1d805744efa9", // 12-byte random number as 24 char hex string
16
+ # "args": [1, "arg", true],
17
+ # "created_at": 1234567890,
18
+ # "enqueued_at": 1234567890,
19
+ # "affiliation_id": "93f971bb-b889-4223-ac57-5d39f34051a4"
20
+ # }
21
+ #
22
+ # This middleware will take that value and set AffiliationId.current_id= to it.
23
+ #
24
+ # If there is no affiliation_id attribute in the job Hash, there is a fallback to AffiliationId.current_id,
25
+ # which depending on the configuration of :enforce_explicit_current_id raises an error or generates a new id.
26
+ #
27
+ class SidekiqServer
28
+ def call(_, job, _)
29
+ ::AffiliationId.current_id = job[::AffiliationId::SIDEKIQ_JOB_KEY] || ::AffiliationId.current_id
30
+ yield
31
+ ensure
32
+ AffiliationId.reset!
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'middleware/rails'
4
+
5
+ module AffiliationId
6
+ class Railtie < ::Rails::Railtie # :nodoc:
7
+ initializer 'affiliation_id.initializer' do |app|
8
+ app.middleware.insert_after ActionDispatch::RequestId, AffiliationId::Middleware::Rails
9
+ end
10
+
11
+ config.to_prepare do
12
+ AffiliationId.reset!
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AffiliationId
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -1,8 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  require_relative 'affiliation_id/version'
6
+ require_relative 'affiliation_id/configuration'
7
+ require_relative 'affiliation_id/middleware/faraday'
8
+ require_relative 'affiliation_id/middleware/sidekiq_client'
9
+ require_relative 'affiliation_id/middleware/sidekiq_server'
10
+ require_relative 'affiliation_id/railtie' if defined?(Rails::Railtie)
11
+
12
+ module AffiliationId # :nodoc:
13
+ THREAD_KEY = 'AFFILIATION_ID'
14
+ SIDEKIQ_JOB_KEY = 'affiliation_id'
15
+
16
+ class << self
17
+ attr_writer :configuration
18
+
19
+ #
20
+ # Returns the current Affiliation ID
21
+ #
22
+ # @return [String] Uniq Affiliation ID
23
+ #
24
+ def current_id
25
+ raise MissingCurrentId if Thread.current[THREAD_KEY].nil? && configuration.enforce_explicit_current_id
26
+
27
+ Thread.current[THREAD_KEY] ||= SecureRandom.uuid
28
+ end
29
+
30
+ #
31
+ # Sets a new ID to be used as Affiliation ID
32
+ #
33
+ # @param [String] value of Affilication ID
34
+ #
35
+ # @return [String] Affiliation ID
36
+ #
37
+ def current_id=(value)
38
+ Thread.current[THREAD_KEY] = value
39
+ end
40
+
41
+ #
42
+ # Renew the current Affiliation ID with a new one
43
+ #
44
+ # @return [String] Affiliation ID
45
+ #
46
+ def renew_current_id!
47
+ Thread.current[THREAD_KEY] = SecureRandom.uuid
48
+ end
49
+
50
+ def reset!
51
+ Thread.current[THREAD_KEY] = nil
52
+ end
53
+
54
+ def configuration
55
+ @configuration ||= Configuration.new
56
+ end
57
+
58
+ def configure
59
+ yield configuration
60
+ end
61
+ end
4
62
 
5
- module AffiliationId
6
- class Error < StandardError; end
7
- # Your code goes here...
63
+ class MissingCurrentId < StandardError # :nodoc:
64
+ def to_s
65
+ 'Affiliation ID must be set explicitly'
66
+ end
67
+ end
8
68
  end
metadata CHANGED
@@ -1,32 +1,96 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: affiliation_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mariusz Siklodi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-04 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-08-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.17.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.17.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.4'
47
+ - !ruby/object:Gem::Dependency
48
+ name: byebug
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '11.1'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 11.1.3
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '11.1'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 11.1.3
13
67
  description:
14
68
  email:
15
69
  executables: []
16
70
  extensions: []
17
71
  extra_rdoc_files: []
18
72
  files:
73
+ - ".overcommit.yml"
19
74
  - ".rspec"
20
75
  - ".rubocop.yml"
21
76
  - CODE_OF_CONDUCT.md
22
77
  - Gemfile
23
78
  - Gemfile.lock
79
+ - Guardfile
24
80
  - LICENSE.txt
25
81
  - README.md
26
82
  - Rakefile
83
+ - affiliation_id.gemspec
27
84
  - bin/console
28
85
  - bin/setup
29
86
  - lib/affiliation_id.rb
87
+ - lib/affiliation_id/configuration.rb
88
+ - lib/affiliation_id/middleware/faraday.rb
89
+ - lib/affiliation_id/middleware/rack.rb
90
+ - lib/affiliation_id/middleware/rails.rb
91
+ - lib/affiliation_id/middleware/sidekiq_client.rb
92
+ - lib/affiliation_id/middleware/sidekiq_server.rb
93
+ - lib/affiliation_id/railtie.rb
30
94
  - lib/affiliation_id/version.rb
31
95
  homepage: https://github.com/siklodi-mariusz/affiliation_id
32
96
  licenses:
@@ -34,6 +98,7 @@ licenses:
34
98
  metadata:
35
99
  homepage_uri: https://github.com/siklodi-mariusz/affiliation_id
36
100
  source_code_uri: https://github.com/siklodi-mariusz/affiliation_id
101
+ rubygems_mfa_required: 'true'
37
102
  post_install_message:
38
103
  rdoc_options: []
39
104
  require_paths: