rails_audit_log 1.3.0 → 1.4.0
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 +4 -4
- data/README.md +77 -0
- data/app/concerns/rails_audit_log/auditable.rb +2 -1
- data/app/jobs/rails_audit_log/streaming/publish_entry_job.rb +13 -0
- data/app/jobs/rails_audit_log/write_audit_log_job.rb +2 -1
- data/lib/rails_audit_log/streaming/active_job_adapter.rb +25 -0
- data/lib/rails_audit_log/streaming/notifications_adapter.rb +21 -0
- data/lib/rails_audit_log/version.rb +1 -1
- data/lib/rails_audit_log.rb +25 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e3de1e31a64d33c94042ea45ff4eeb391d48cc65db934bf7b7256656326c480
|
|
4
|
+
data.tar.gz: e34d558b4687f61336af9a7f7e6ae3faebe6ea77533e7da8e360d27dbfc70060
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd8d51146c5b915b5c00cc863f23cffc70066cf4cfaafc5e667e9b064b225168b71f45d5699199ba37de78f0089cebabb8a1707f6f837642c32b0fb73c290a6c
|
|
7
|
+
data.tar.gz: 3abf1d5c1894de5496a76b8827eb23f6f8b430e9a4a192505e1012a3be022cf01290b5ef38cc3d9b43e2e4dc5f46c2e2667fdd77015ce0dfb111fab6141530b5
|
data/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Audit logging for Rails. Tracks `create`, `update`, and `destroy` events as stru
|
|
|
27
27
|
- [Scheduled and manual pruning](#scheduled-and-manual-pruning)
|
|
28
28
|
- [Encrypting audit data](#encrypting-audit-data)
|
|
29
29
|
- [Multi-tenancy](#multi-tenancy)
|
|
30
|
+
- [Event streaming](#event-streaming)
|
|
30
31
|
- [Selective tracking](#selective-tracking)
|
|
31
32
|
- [Disabling auditing](#disabling-auditing)
|
|
32
33
|
- [Object reconstruction](#object-reconstruction)
|
|
@@ -477,6 +478,82 @@ RailsAuditLog.acts_as_tenant!
|
|
|
477
478
|
|
|
478
479
|
This is equivalent to `RailsAuditLog.current_tenant { ActsAsTenant.current_tenant&.id }`.
|
|
479
480
|
|
|
481
|
+
### Event streaming
|
|
482
|
+
|
|
483
|
+
Publish every audit entry to an external consumer as it is written. Set any object implementing `#publish(entry)` as the adapter:
|
|
484
|
+
|
|
485
|
+
```ruby
|
|
486
|
+
# config/initializers/rails_audit_log.rb
|
|
487
|
+
RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### NotificationsAdapter (built-in, zero dependencies)
|
|
491
|
+
|
|
492
|
+
Publishes `rails_audit_log.entry_created` synchronously via `ActiveSupport::Notifications`:
|
|
493
|
+
|
|
494
|
+
```ruby
|
|
495
|
+
ActiveSupport::Notifications.subscribe("rails_audit_log.entry_created") do |*, payload|
|
|
496
|
+
entry = payload[:entry]
|
|
497
|
+
Rails.logger.info "Audit: #{entry.event} #{entry.item_type}##{entry.item_id}"
|
|
498
|
+
end
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### ActiveJobAdapter (async)
|
|
502
|
+
|
|
503
|
+
Enqueues `PublishEntryJob` so publishing does not block the request. The job fires the same `rails_audit_log.entry_created` notification when performed:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new
|
|
507
|
+
# or with a custom queue:
|
|
508
|
+
RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new(queue: :streaming)
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### Custom adapters
|
|
512
|
+
|
|
513
|
+
Any object implementing `#publish(entry)` works — no companion gem needed for Kafka, SQS, or any other transport:
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
# Kafka via WaterDrop
|
|
517
|
+
class KafkaAuditAdapter
|
|
518
|
+
def initialize(producer: WaterDrop::SyncProducer)
|
|
519
|
+
@producer = producer
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def publish(entry)
|
|
523
|
+
@producer.call(entry.attributes.to_json, topic: "audit_log")
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
RailsAuditLog.streaming_adapter = KafkaAuditAdapter.new
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
```ruby
|
|
531
|
+
# SQS via aws-sdk-sqs
|
|
532
|
+
class SqsAuditAdapter
|
|
533
|
+
def initialize(queue_url:, client: Aws::SQS::Client.new)
|
|
534
|
+
@queue_url = queue_url
|
|
535
|
+
@client = client
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def publish(entry)
|
|
539
|
+
@client.send_message(
|
|
540
|
+
queue_url: @queue_url,
|
|
541
|
+
message_body: entry.attributes.to_json
|
|
542
|
+
)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
RailsAuditLog.streaming_adapter = SqsAuditAdapter.new(
|
|
547
|
+
queue_url: ENV.fetch("AUDIT_SQS_URL")
|
|
548
|
+
)
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Wrap the adapter in `ActiveJobAdapter` if you want publishing to be asynchronous and not block the request thread.
|
|
552
|
+
|
|
553
|
+
#### Batch mode
|
|
554
|
+
|
|
555
|
+
`batch_audit` flushes the bulk `INSERT` first, then calls `#publish` for each entry individually — streaming consumers receive every entry even in batch mode.
|
|
556
|
+
|
|
480
557
|
### Selective tracking
|
|
481
558
|
|
|
482
559
|
Track only specific attributes, or exclude noisy ones:
|
|
@@ -234,8 +234,9 @@ module RailsAuditLog
|
|
|
234
234
|
period = self.class._audit_log_retain_for || RailsAuditLog.retention_period
|
|
235
235
|
WriteAuditLogJob.perform_later(entry_attrs.stringify_keys, version_limit: limit, retention_period: period)
|
|
236
236
|
else
|
|
237
|
-
RailsAuditLog::AuditLogEntry.create!(entry_attrs)
|
|
237
|
+
entry = RailsAuditLog::AuditLogEntry.create!(entry_attrs)
|
|
238
238
|
prune_audit_entries
|
|
239
|
+
RailsAuditLog.publish_entry(entry)
|
|
239
240
|
end
|
|
240
241
|
end
|
|
241
242
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module RailsAuditLog
|
|
2
|
+
module Streaming
|
|
3
|
+
class PublishEntryJob < ApplicationJob
|
|
4
|
+
def perform(entry_attrs)
|
|
5
|
+
entry = AuditLogEntry.new(entry_attrs)
|
|
6
|
+
ActiveSupport::Notifications.instrument(
|
|
7
|
+
NotificationsAdapter::EVENT,
|
|
8
|
+
entry: entry
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module RailsAuditLog
|
|
2
2
|
class WriteAuditLogJob < ApplicationJob
|
|
3
3
|
def perform(entry_attrs, version_limit: nil, retention_period: nil)
|
|
4
|
-
AuditLogEntry.create!(entry_attrs)
|
|
4
|
+
entry = AuditLogEntry.create!(entry_attrs)
|
|
5
|
+
RailsAuditLog.publish_entry(entry)
|
|
5
6
|
|
|
6
7
|
item_type = entry_attrs["item_type"]
|
|
7
8
|
item_id = entry_attrs["item_id"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RailsAuditLog
|
|
2
|
+
module Streaming
|
|
3
|
+
# Publishes each audit entry asynchronously by enqueuing
|
|
4
|
+
# {RailsAuditLog::Streaming::PublishEntryJob}. The job fires a
|
|
5
|
+
# +rails_audit_log.entry_created+ notification when performed, so subscribers
|
|
6
|
+
# receive the entry out-of-band without blocking the request.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new
|
|
10
|
+
#
|
|
11
|
+
# # Custom queue:
|
|
12
|
+
# RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new(queue: :streaming)
|
|
13
|
+
class ActiveJobAdapter
|
|
14
|
+
def initialize(queue: nil)
|
|
15
|
+
@queue = queue
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def publish(entry)
|
|
19
|
+
job = RailsAuditLog::Streaming::PublishEntryJob
|
|
20
|
+
job = job.set(queue: @queue) if @queue
|
|
21
|
+
job.perform_later(entry.attributes.compact)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module RailsAuditLog
|
|
2
|
+
module Streaming
|
|
3
|
+
# Publishes each audit entry synchronously via +ActiveSupport::Notifications+.
|
|
4
|
+
# Zero external dependencies — subscribe with +ActiveSupport::Notifications.subscribe+.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
|
|
8
|
+
#
|
|
9
|
+
# ActiveSupport::Notifications.subscribe("rails_audit_log.entry_created") do |*, payload|
|
|
10
|
+
# entry = payload[:entry]
|
|
11
|
+
# Rails.logger.info "Audit: #{entry.event} on #{entry.item_type}##{entry.item_id}"
|
|
12
|
+
# end
|
|
13
|
+
class NotificationsAdapter
|
|
14
|
+
EVENT = "rails_audit_log.entry_created"
|
|
15
|
+
|
|
16
|
+
def publish(entry)
|
|
17
|
+
ActiveSupport::Notifications.instrument(EVENT, entry: entry)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/rails_audit_log.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "rails_audit_log/version"
|
|
2
2
|
require "rails_audit_log/engine"
|
|
3
|
+
require "rails_audit_log/streaming/notifications_adapter"
|
|
4
|
+
require "rails_audit_log/streaming/active_job_adapter"
|
|
3
5
|
|
|
4
6
|
# RailsAuditLog is a Rails engine that tracks ActiveRecord +create+, +update+,
|
|
5
7
|
# and +destroy+ events as {AuditLogEntry} records with JSON-first storage and
|
|
@@ -90,6 +92,15 @@ module RailsAuditLog
|
|
|
90
92
|
# @return [Integer]
|
|
91
93
|
mattr_accessor :page_size, default: 25
|
|
92
94
|
|
|
95
|
+
# The active streaming adapter. Any object implementing +#publish(entry)+.
|
|
96
|
+
# Called after every audit entry is persisted, including batch writes.
|
|
97
|
+
# Set to +nil+ (default) to disable streaming.
|
|
98
|
+
#
|
|
99
|
+
# @return [#publish, nil]
|
|
100
|
+
# @example
|
|
101
|
+
# RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
|
|
102
|
+
mattr_accessor :streaming_adapter, default: nil
|
|
103
|
+
|
|
93
104
|
# Controls how an actor object is serialised into the +whodunnit_snapshot+
|
|
94
105
|
# string column. Defaults to +actor.name+ when available, otherwise +to_s+.
|
|
95
106
|
#
|
|
@@ -143,6 +154,16 @@ module RailsAuditLog
|
|
|
143
154
|
current_tenant { ActsAsTenant.current_tenant&.id }
|
|
144
155
|
end
|
|
145
156
|
|
|
157
|
+
# Passes +entry+ to the configured {.streaming_adapter} if one is set.
|
|
158
|
+
# No-ops when no adapter is configured.
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
# @param entry [AuditLogEntry]
|
|
162
|
+
# @return [void]
|
|
163
|
+
def self.publish_entry(entry)
|
|
164
|
+
streaming_adapter&.publish(entry)
|
|
165
|
+
end
|
|
166
|
+
|
|
146
167
|
# Sets or returns the authentication block used to gate the web dashboard.
|
|
147
168
|
# The block is evaluated in controller context, so controller helpers
|
|
148
169
|
# (e.g. +current_user+) are available directly.
|
|
@@ -273,7 +294,10 @@ module RailsAuditLog
|
|
|
273
294
|
begin
|
|
274
295
|
result = yield
|
|
275
296
|
batch = Thread.current[:rails_audit_log_batch]
|
|
276
|
-
|
|
297
|
+
if batch.any?
|
|
298
|
+
AuditLogEntry.insert_all!(batch)
|
|
299
|
+
batch.each { |attrs| publish_entry(AuditLogEntry.new(attrs)) } if streaming_adapter
|
|
300
|
+
end
|
|
277
301
|
result
|
|
278
302
|
ensure
|
|
279
303
|
Thread.current[:rails_audit_log_batch] = nil
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_audit_log
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- app/javascript/rails_audit_log/search_controller.js
|
|
100
100
|
- app/jobs/rails_audit_log/application_job.rb
|
|
101
101
|
- app/jobs/rails_audit_log/prune_audit_log_job.rb
|
|
102
|
+
- app/jobs/rails_audit_log/streaming/publish_entry_job.rb
|
|
102
103
|
- app/jobs/rails_audit_log/write_audit_log_job.rb
|
|
103
104
|
- app/models/rails_audit_log/application_record.rb
|
|
104
105
|
- app/models/rails_audit_log/audit_log_entry.rb
|
|
@@ -125,6 +126,8 @@ files:
|
|
|
125
126
|
- lib/rails_audit_log/matchers.rb
|
|
126
127
|
- lib/rails_audit_log/minitest_assertions.rb
|
|
127
128
|
- lib/rails_audit_log/paper_trail_compat.rb
|
|
129
|
+
- lib/rails_audit_log/streaming/active_job_adapter.rb
|
|
130
|
+
- lib/rails_audit_log/streaming/notifications_adapter.rb
|
|
128
131
|
- lib/rails_audit_log/test_helpers.rb
|
|
129
132
|
- lib/rails_audit_log/version.rb
|
|
130
133
|
- lib/tasks/rails_audit_log_tasks.rake
|