flu-rails 8.0.2 → 8.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fde08f865924f75af0fc55c572bb6d10c617275772848055f838234cbf64a515
4
- data.tar.gz: f675884b945492c91c6dbb8713156e2559da185d3b585aac2598525f61ae0d3d
3
+ metadata.gz: 5fadf301a677ec0ec47d67f9eb8159aac7dfab535c268030331ce4d54c391e58
4
+ data.tar.gz: 90c8b9dc6c71fa9f01630b1e183114132ec1d63735cdc2cb62c5974750f3ff49
5
5
  SHA512:
6
- metadata.gz: 4084a87a8b3ac4b9b3de65b6fd3e0c0c2cf6cc912afcb0570212dc38baa706fac873033cda2fd38e177659c62d0fe8e5f7fef941feee8419874b9064d865b32b
7
- data.tar.gz: c4648f491c6d74e4f0a48cbcc5cedd164aee43b9e615f6c51108fdb5390869a96332b758a87c9b1203a65d9b96e35f7061e0bee317ed94b76535b26aa00add34
6
+ metadata.gz: 2a8f50d7eb2796ce8408f3a1ca36b68d55020d8858134fc136cef7be6fcb7978c316e99298a2d394ef0f0555602c1a955b3ff497b0a72e9b1f49ddf23ec4e1b8
7
+ data.tar.gz: dbc5b57979a4ebe601b8df0c26822c7dfa127c44f7292b97b654aff0363d04266552a60c2bda0d0ea00b2865421e47677d4f66592bf8c9e36bf6820f1ea290e8
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ### [8.0.4] - 2026-08-01
9
+
10
+ **Security**
11
+
12
+ * Honour the host application's `config.filter_parameters` when publishing a `request` event.
13
+
14
+ **Fixed**
15
+
16
+ * Stop opening two RabbitMQ connections on every boot. The railtie called `Flu.start
17
+ * Close the publisher that `Flu.init` replaces.
18
+
19
+ **Added**
20
+
21
+ * `EventPublisher#disconnect` and `EventPublisher#connected?`.
22
+
23
+ **Changed**
24
+
25
+ * `ignored_request_params` and `default_ignored_request_params` are now matched as strings.
26
+ * A published `request` event may now carry `"[FILTERED]"` in place of a param value.
27
+ * `EventPublisher#connect` is now idempotent: calling it on a connected publisher does nothing
28
+ instead of opening a second connection.
29
+
30
+ **Tests**
31
+
32
+ * Cover the connection lifecycle against a real broker: redundant `connect` calls, `disconnect`,
33
+ `connected?`, and reconnection after a disconnect — including an example asserting that no session
34
+ is left open behind the publisher.
35
+ * Cover `Flu.init` disconnecting the publisher it replaces.
36
+
37
+ ### [8.0.3] - 2026-08-01
38
+
39
+ * Use `uuid_v7` as Event ID (requires Ruby 3.3)
40
+
8
41
  ### [8.0.2] - 2026-08-01
9
42
 
10
43
  **Fixed**
data/README.md CHANGED
@@ -164,7 +164,7 @@ All options have a default value. However, all of them can be changed in your in
164
164
  | `rabbitmq_exchange_durable` | `true` | Boolean | Optional | Make the RabbitMQ's exchange durable or not. From RabbitMQ's [documentation](https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges): _"Durable exchanges survive broker restart whereas transient exchanges do not (they have to be redeclared when broker comes back online)."_ | `false` |
165
165
  | `auto_connect_to_exchange` | `true`| Boolean | Optional | Thanks to `Railties`, `flu-rails` starts automatically when the Rails app boots. However, this can be useful to not connect RabbitMQ at start up. To do so, set `auto_connect_to_exchange` to `false`. | `false` |
166
166
  | `default_ignored_model_changes` | `[:password, :password_confirmation, :created_at, :updated_at]` | Boolean | Optional | By default, all these attributes will be ignored from model changes when creating an event. For instance, this means that timestamp fields (`created_at` and `updated_at`) are not monitored when they change. | `[]` |
167
- | `default_ignored_request_params` | `[:password, :password_confirmation, :controller, :action]` | Boolean | Optional | By default, all these parameters will be ignored from controller request's `params` when creating an event. | `false` |
167
+ | `default_ignored_request_params` | `[:password, :password_confirmation, :controller, :action]` | Boolean | Optional | By default, all these parameters will be ignored from controller request's `params` when creating an event. Independently of this option, any parameter your Rails application already masks through `config.filter_parameters` (passwords, tokens,...) is replaced with `"[FILTERED]"` in the event too, including inside nested params. | `false` |
168
168
  | `application_name` | `Rails.application.class.module_parent_name`, resolved on startup | String | Required | Is used as `emitter` for each event created by `flu-rails`, if not overriden by the `track_met`. | `my_app` |
169
169
  | `bunny_options` | `{}` | Hash of symbols | Optional | Additional options to add when connecting the RabbitMQ broker. This overrides the existing options with the same name. | `{ verify_peer: true }` |
170
170
 
@@ -304,8 +304,8 @@ scoped RubyGems credential.
304
304
  3. Tag the commit and push the tag:
305
305
 
306
306
  ```
307
- $ git tag -a v8.0.2 -m "Version 8.0.2"
308
- $ git push origin v8.0.2
307
+ $ git tag -a v8.0.4 -m "Version 8.0.4"
308
+ $ git push origin v8.0.4
309
309
  ```
310
310
 
311
311
  The workflow then checks that the tag matches `Flu::VERSION`, runs the tests, builds the gem
@@ -1,5 +1,7 @@
1
1
  require "active_support/core_ext/string/inflections"
2
2
  require "active_support/core_ext/time/zones"
3
+ require "random/formatter"
4
+ require "securerandom"
3
5
 
4
6
  module Flu
5
7
  class ActionControllerExtender
@@ -28,7 +30,7 @@ module Flu
28
30
  # landed on ActionController::Base itself, leaking onto every controller of the host
29
31
  # application. Here 'self' is the class calling 'track_requests', which is where they belong.
30
32
  define_method(:flu_define_request_id) do
31
- request_id = SecureRandom.uuid
33
+ request_id = Random.respond_to?(:uuid_v7) ? Random.uuid_v7 : SecureRandom.uuid
32
34
  @flu_request_id = request_id
33
35
  Flu::CoreExt.flu_tracker_request_id = request_id
34
36
  end
@@ -20,7 +20,14 @@ module Flu
20
20
 
21
21
  def connect
22
22
  end
23
-
23
+
24
+ def connected?
25
+ true
26
+ end
27
+
28
+ def disconnect
29
+ end
30
+
24
31
  def events_count
25
32
  @published_events_by_routing_key.map do | key, value |
26
33
  value.size
@@ -11,7 +11,7 @@ module Flu
11
11
  @configuration = configuration
12
12
  @emitter = configuration.application_name
13
13
  @default_ignored_model_changes = configuration.default_ignored_model_changes.map(&:to_s)
14
- @default_ignored_request_params = configuration.default_ignored_request_params
14
+ @default_ignored_request_params = configuration.default_ignored_request_params.map(&:to_s)
15
15
  end
16
16
 
17
17
  def build_request_event(data)
@@ -50,7 +50,8 @@ module Flu
50
50
  final_emitter = overriden_emitter.blank? ? original_emitter : overriden_emitter
51
51
  # 'overriden_emitter' selects the emitter of the event, it is not part of what is tracked:
52
52
  # it is dropped from the payload instead of being published as an 'overridenEmitter' key.
53
- Event.new(SecureRandom.uuid, final_emitter, kind, name, deep_camelize(data.except(:overriden_emitter)))
53
+ uuid = Random.respond_to?(:uuid_v7) ? Random.uuid_v7 : SecureRandom.uuid
54
+ Event.new(uuid, final_emitter, kind, name, deep_camelize(data.except(:overriden_emitter)))
54
55
  end
55
56
 
56
57
  def create_data_from_entity_changes(action_name, entity, request_id, request_entity_metadata, changes, user_metadata_lambda, association_columns, ignored_model_changes, flu_overriden_emitter_lambda)
@@ -76,15 +77,15 @@ module Flu
76
77
  response_code: response.status,
77
78
  user_agent: request.user_agent,
78
79
  duration: Time.zone.now - request_start_time,
79
- params: extract_params(params, ignored_request_params)
80
+ params: extract_params(request, ignored_request_params)
80
81
  }
81
82
  end
82
83
 
83
84
  private
84
85
 
85
- def extract_params(params, ignored_request_params)
86
- remaining = params.except(*ignored_request_params).except(*@default_ignored_request_params)
87
- remaining.respond_to?(:to_unsafe_h) ? remaining.to_unsafe_h.to_h : remaining.to_h
86
+ def extract_params(request, ignored_request_params)
87
+ excluded_params = ignored_request_params.map(&:to_s) + @default_ignored_request_params
88
+ request.filtered_parameters.except(*excluded_params)
88
89
  end
89
90
 
90
91
  def deep_camelize(value)
@@ -17,18 +17,41 @@ module Flu
17
17
  end
18
18
 
19
19
  def connect
20
- connected = false
21
- while !connected
22
- begin
23
- connect_to_exchange
24
- connected = true
25
- rescue Bunny::TCPConnectionFailedForAllHosts
26
- @logger.warn("RabbitMQ connection failed, try again in 1 second.")
27
- sleep 1
20
+ unless connected?
21
+ connected = false
22
+ while !connected
23
+ begin
24
+ connect_to_exchange
25
+ connected = true
26
+ rescue Bunny::TCPConnectionFailedForAllHosts
27
+ @logger.warn("RabbitMQ connection failed, try again in 1 second.")
28
+ sleep 1
29
+ end
28
30
  end
29
31
  end
30
32
  end
31
33
 
34
+ def connected?
35
+ if @connection.nil? || @exchange.nil?
36
+ false
37
+ else
38
+ @connection.open?
39
+ end
40
+ end
41
+
42
+ # Closing the connection closes the channel opened on it, and stops the heartbeat and recovery
43
+ # threads Bunny runs alongside it.
44
+ # The guard is on the connection alone, not on 'connected?': a connection that was opened before
45
+ # the exchange could be declared still has to be closed.
46
+ def disconnect
47
+ if !@connection.nil? && @connection.open?
48
+ @connection.close
49
+ end
50
+ @connection = nil
51
+ @channel = nil
52
+ @exchange = nil
53
+ end
54
+
32
55
  private
33
56
 
34
57
  def connect_to_exchange
@@ -2,13 +2,16 @@ module Flu
2
2
  class Railtie < Rails::Railtie
3
3
  railtie_name :flu
4
4
 
5
+ # 'to_prepare' runs before eager loading, so that 'track_entity_changes' and 'track_requests'
6
+ # exist by the time the application's models and controllers are loaded. It runs again on every
7
+ # code reload, where 'Flu.init' builds a new publisher which has to be connected too.
8
+ #
9
+ # There is deliberately no 'after_initialize' hook here: it also runs at boot, right after this
10
+ # one, and the 'Flu.start' it used to make opened a second connection that replaced — and
11
+ # therefore leaked — the connection opened above.
5
12
  config.to_prepare do
6
13
  Flu.init
7
14
  Flu.start
8
15
  end
9
-
10
- config.after_initialize do
11
- Flu.start
12
- end
13
16
  end
14
17
  end
@@ -1,3 +1,3 @@
1
1
  module Flu
2
- VERSION = "8.0.2"
2
+ VERSION = "8.0.4"
3
3
  end
data/lib/flu-rails.rb CHANGED
@@ -38,6 +38,10 @@ module Flu
38
38
  raise "configuration.application_name must not be nil" if @configuration.application_name.nil?
39
39
  @logger = @configuration.logger
40
40
  @event_factory = Flu::EventFactory.new(@configuration)
41
+ # The railtie re-runs 'init' on every code reload. The publisher being replaced owns an open
42
+ # connection, its channel and Bunny's heartbeat thread: dropping the reference to it without
43
+ # closing it leaks all three for the lifetime of the process.
44
+ @event_publisher&.disconnect
41
45
  @event_publisher = create_event_publisher(@configuration)
42
46
  extend_models_and_controllers
43
47
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flu-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.2
4
+ version: 8.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Loïc Vigneron