rails_audit_log 1.2.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 +127 -0
- data/app/concerns/rails_audit_log/auditable.rb +16 -3
- data/app/controllers/rails_audit_log/application_controller.rb +5 -0
- data/app/controllers/rails_audit_log/audit_log_entries_controller.rb +1 -1
- data/app/controllers/rails_audit_log/resources_controller.rb +1 -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/app/models/rails_audit_log/audit_log_entry.rb +10 -0
- data/lib/generators/rails_audit_log/tenant/templates/add_tenant_id_to_audit_log_entries.rb +6 -0
- data/lib/generators/rails_audit_log/tenant/tenant_generator.rb +33 -0
- 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 +54 -1
- metadata +6 -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
|
@@ -26,6 +26,8 @@ Audit logging for Rails. Tracks `create`, `update`, and `destroy` events as stru
|
|
|
26
26
|
- [Time-based retention](#time-based-retention)
|
|
27
27
|
- [Scheduled and manual pruning](#scheduled-and-manual-pruning)
|
|
28
28
|
- [Encrypting audit data](#encrypting-audit-data)
|
|
29
|
+
- [Multi-tenancy](#multi-tenancy)
|
|
30
|
+
- [Event streaming](#event-streaming)
|
|
29
31
|
- [Selective tracking](#selective-tracking)
|
|
30
32
|
- [Disabling auditing](#disabling-auditing)
|
|
31
33
|
- [Object reconstruction](#object-reconstruction)
|
|
@@ -427,6 +429,131 @@ The generator creates:
|
|
|
427
429
|
- `config/initializers/rails_audit_log_encryption.rb` — reads the generated keys from credentials and passes them to `ActiveRecord::Encryption`
|
|
428
430
|
- `db/migrate/TIMESTAMP_encrypt_rails_audit_log_entries.rb` — re-encrypts existing plain-text audit entries; edit `ENCRYPTED_MODELS` to list your model class names, then run `bin/rails db:migrate`
|
|
429
431
|
|
|
432
|
+
### Multi-tenancy
|
|
433
|
+
|
|
434
|
+
Store the current tenant on every audit entry so queries are naturally isolated per tenant.
|
|
435
|
+
|
|
436
|
+
Run the generator to add the `tenant_id` column:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
bin/rails generate rails_audit_log:tenant
|
|
440
|
+
bin/rails db:migrate
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Set a global resolver in your initializer — the block is called at write time:
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
# config/initializers/rails_audit_log.rb
|
|
447
|
+
RailsAuditLog.current_tenant { Current.tenant_id }
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Or override per model:
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
class Order < ApplicationRecord
|
|
454
|
+
include RailsAuditLog::Auditable
|
|
455
|
+
audit_log tenant: -> { Current.tenant_id }
|
|
456
|
+
end
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The per-model lambda takes precedence over the global resolver. Both accept zero-argument lambdas and store whatever the block returns in the `tenant_id` string column.
|
|
460
|
+
|
|
461
|
+
Scope queries to a single tenant with `for_tenant`:
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
AuditLogEntry.for_tenant("acme")
|
|
465
|
+
AuditLogEntry.for_tenant(Current.tenant_id).updated_events.since(1.week.ago)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
The web dashboard (`/audit`) automatically applies `for_tenant` when `current_tenant` is configured, so entries from other tenants are never exposed.
|
|
469
|
+
|
|
470
|
+
#### Acts As Tenant integration
|
|
471
|
+
|
|
472
|
+
Wire the resolver to `ActsAsTenant` in one line:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
# config/initializers/rails_audit_log.rb
|
|
476
|
+
RailsAuditLog.acts_as_tenant!
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
This is equivalent to `RailsAuditLog.current_tenant { ActsAsTenant.current_tenant&.id }`.
|
|
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
|
+
|
|
430
557
|
### Selective tracking
|
|
431
558
|
|
|
432
559
|
Track only specific attributes, or exclude noisy ones:
|
|
@@ -31,6 +31,7 @@ module RailsAuditLog
|
|
|
31
31
|
class_attribute :_audit_log_retain_for, default: nil
|
|
32
32
|
class_attribute :_audit_log_async, default: false
|
|
33
33
|
class_attribute :_audit_log_encrypt, default: nil
|
|
34
|
+
class_attribute :_audit_log_tenant, default: nil
|
|
34
35
|
|
|
35
36
|
_warn_if_audit_table_missing
|
|
36
37
|
|
|
@@ -119,18 +120,21 @@ module RailsAuditLog
|
|
|
119
120
|
# host app to configure +config.active_record.encryption+; decryption is
|
|
120
121
|
# transparent — {AuditLogEntry#diff}, {AuditLogEntry#reify}, and
|
|
121
122
|
# {AuditLogEntry.touching} work unchanged for non-SQL access paths
|
|
123
|
+
# @param tenant [Proc, nil] zero-argument lambda evaluated at write time;
|
|
124
|
+
# return value is stored in the +tenant_id+ column; overrides
|
|
125
|
+
# {RailsAuditLog.current_tenant} for this model
|
|
122
126
|
# @return [void]
|
|
123
127
|
# @example
|
|
124
128
|
# class Article < ApplicationRecord
|
|
125
129
|
# include RailsAuditLog::Auditable
|
|
126
130
|
# audit_log only: %i[title body published_at],
|
|
127
|
-
#
|
|
131
|
+
# tenant: -> { Current.tenant_id },
|
|
128
132
|
# associations: %i[tags],
|
|
129
133
|
# version_limit: 100,
|
|
130
134
|
# retain_for: 30.days,
|
|
131
135
|
# encrypt: true
|
|
132
136
|
# end
|
|
133
|
-
def audit_log(only: nil, ignore: nil, meta: nil, associations: nil, version_limit: nil, retain_for: nil, async: nil, encrypt: nil)
|
|
137
|
+
def audit_log(only: nil, ignore: nil, meta: nil, associations: nil, version_limit: nil, retain_for: nil, async: nil, encrypt: nil, tenant: nil)
|
|
134
138
|
self._audit_log_only = only.map(&:to_s) if only
|
|
135
139
|
self._audit_log_ignore = ignore.map(&:to_s) if ignore
|
|
136
140
|
self._audit_log_meta = meta if meta
|
|
@@ -139,6 +143,7 @@ module RailsAuditLog
|
|
|
139
143
|
self._audit_log_retain_for = retain_for unless retain_for.nil?
|
|
140
144
|
self._audit_log_async = async unless async.nil?
|
|
141
145
|
self._audit_log_encrypt = encrypt unless encrypt.nil?
|
|
146
|
+
self._audit_log_tenant = tenant unless tenant.nil?
|
|
142
147
|
end
|
|
143
148
|
end
|
|
144
149
|
|
|
@@ -183,6 +188,7 @@ module RailsAuditLog
|
|
|
183
188
|
object: nil,
|
|
184
189
|
reason: RailsAuditLog.reason,
|
|
185
190
|
metadata: meta.presence,
|
|
191
|
+
tenant_id: resolve_tenant_id,
|
|
186
192
|
whodunnit_snapshot: actor ? RailsAuditLog.whodunnit_display.call(actor) : nil,
|
|
187
193
|
actor_type: actor&.class&.name,
|
|
188
194
|
actor_id: actor.respond_to?(:id) ? actor.id : nil
|
|
@@ -205,6 +211,7 @@ module RailsAuditLog
|
|
|
205
211
|
object: maybe_encrypt(snapshot),
|
|
206
212
|
reason: RailsAuditLog.reason,
|
|
207
213
|
metadata: meta.presence,
|
|
214
|
+
tenant_id: resolve_tenant_id,
|
|
208
215
|
whodunnit_snapshot: actor ? RailsAuditLog.whodunnit_display.call(actor) : nil,
|
|
209
216
|
actor_type: actor&.class&.name,
|
|
210
217
|
actor_id: actor.respond_to?(:id) ? actor.id : nil
|
|
@@ -227,8 +234,9 @@ module RailsAuditLog
|
|
|
227
234
|
period = self.class._audit_log_retain_for || RailsAuditLog.retention_period
|
|
228
235
|
WriteAuditLogJob.perform_later(entry_attrs.stringify_keys, version_limit: limit, retention_period: period)
|
|
229
236
|
else
|
|
230
|
-
RailsAuditLog::AuditLogEntry.create!(entry_attrs)
|
|
237
|
+
entry = RailsAuditLog::AuditLogEntry.create!(entry_attrs)
|
|
231
238
|
prune_audit_entries
|
|
239
|
+
RailsAuditLog.publish_entry(entry)
|
|
232
240
|
end
|
|
233
241
|
end
|
|
234
242
|
|
|
@@ -248,6 +256,11 @@ module RailsAuditLog
|
|
|
248
256
|
end
|
|
249
257
|
end
|
|
250
258
|
|
|
259
|
+
def resolve_tenant_id
|
|
260
|
+
tenant_proc = self.class._audit_log_tenant || RailsAuditLog.current_tenant
|
|
261
|
+
tenant_proc&.call
|
|
262
|
+
end
|
|
263
|
+
|
|
251
264
|
def build_audit_metadata
|
|
252
265
|
meta = {}
|
|
253
266
|
if self.class._audit_log_meta
|
|
@@ -13,5 +13,10 @@ module RailsAuditLog
|
|
|
13
13
|
|
|
14
14
|
instance_exec(self, &auth) || request_http_basic_authentication("Audit Log")
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
def base_audit_scope
|
|
18
|
+
tenant_id = RailsAuditLog.current_tenant&.call
|
|
19
|
+
tenant_id ? AuditLogEntry.for_tenant(tenant_id) : AuditLogEntry.all
|
|
20
|
+
end
|
|
16
21
|
end
|
|
17
22
|
end
|
|
@@ -21,7 +21,7 @@ module RailsAuditLog
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def filtered_scope
|
|
24
|
-
scope =
|
|
24
|
+
scope = base_audit_scope.order(created_at: :desc)
|
|
25
25
|
scope = scope.where(event: @event) if @event
|
|
26
26
|
scope = scope.where(item_type: @item_type) if @item_type
|
|
27
27
|
scope = scope.for_period(@period) if @period
|
|
@@ -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"]
|
|
@@ -91,6 +91,16 @@ module RailsAuditLog
|
|
|
91
91
|
end
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
# Entries belonging to a specific tenant.
|
|
95
|
+
# Composable with all other scopes.
|
|
96
|
+
#
|
|
97
|
+
# @param id [String, Integer] the tenant identifier stored in +tenant_id+
|
|
98
|
+
# @return [ActiveRecord::Relation]
|
|
99
|
+
# @example
|
|
100
|
+
# AuditLogEntry.for_tenant("acme")
|
|
101
|
+
# AuditLogEntry.for_tenant(Current.tenant_id).updated_events
|
|
102
|
+
scope :for_tenant, ->(id) { where(tenant_id: id) }
|
|
103
|
+
|
|
94
104
|
# @!endgroup
|
|
95
105
|
|
|
96
106
|
# @!group Time scopes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module RailsAuditLog
|
|
5
|
+
module Generators
|
|
6
|
+
class TenantGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Creates a migration that adds a tenant_id column and index to audit_log_entries."
|
|
12
|
+
|
|
13
|
+
def create_migration_file
|
|
14
|
+
migration_template(
|
|
15
|
+
"add_tenant_id_to_audit_log_entries.rb",
|
|
16
|
+
"db/migrate/add_tenant_id_to_audit_log_entries.rb"
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def print_next_steps
|
|
21
|
+
say ""
|
|
22
|
+
say "Next steps:", :green
|
|
23
|
+
say " 1. Run `bin/rails db:migrate` to add the tenant_id column."
|
|
24
|
+
say " 2. Set a global resolver in your initializer:"
|
|
25
|
+
say " RailsAuditLog.current_tenant { Current.tenant_id }"
|
|
26
|
+
say " or per-model:"
|
|
27
|
+
say " audit_log tenant: -> { Current.tenant_id }"
|
|
28
|
+
say " 3. Use AuditLogEntry.for_tenant(id) to scope queries to a tenant."
|
|
29
|
+
say ""
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -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
|
#
|
|
@@ -114,6 +125,45 @@ module RailsAuditLog
|
|
|
114
125
|
yield self
|
|
115
126
|
end
|
|
116
127
|
|
|
128
|
+
# Sets or returns the global tenant resolver block. The block is called at
|
|
129
|
+
# write time and its return value is stored in the +tenant_id+ column of each
|
|
130
|
+
# {AuditLogEntry}. Override per-model with <tt>audit_log tenant: -> { ... }</tt>.
|
|
131
|
+
#
|
|
132
|
+
# @yield block called with no arguments at write time; return the tenant id
|
|
133
|
+
# @return [Proc, nil] the stored block, or +nil+ when not configured
|
|
134
|
+
# @example
|
|
135
|
+
# RailsAuditLog.current_tenant { Current.tenant_id }
|
|
136
|
+
def self.current_tenant(&block)
|
|
137
|
+
@current_tenant = block if block_given?
|
|
138
|
+
@current_tenant
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Wires {.current_tenant} to +ActsAsTenant.current_tenant&.id+ so audit
|
|
142
|
+
# entries are automatically scoped to the Acts As Tenant context.
|
|
143
|
+
# Call once in an initializer after the gem is loaded.
|
|
144
|
+
#
|
|
145
|
+
# @raise [RuntimeError] if the +acts_as_tenant+ gem is not loaded
|
|
146
|
+
# @return [void]
|
|
147
|
+
# @example
|
|
148
|
+
# RailsAuditLog.acts_as_tenant!
|
|
149
|
+
def self.acts_as_tenant!
|
|
150
|
+
unless defined?(ActsAsTenant)
|
|
151
|
+
raise "ActsAsTenant is not loaded. Add the `acts_as_tenant` gem to your Gemfile."
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
current_tenant { ActsAsTenant.current_tenant&.id }
|
|
155
|
+
end
|
|
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
|
+
|
|
117
167
|
# Sets or returns the authentication block used to gate the web dashboard.
|
|
118
168
|
# The block is evaluated in controller context, so controller helpers
|
|
119
169
|
# (e.g. +current_user+) are available directly.
|
|
@@ -244,7 +294,10 @@ module RailsAuditLog
|
|
|
244
294
|
begin
|
|
245
295
|
result = yield
|
|
246
296
|
batch = Thread.current[:rails_audit_log_batch]
|
|
247
|
-
|
|
297
|
+
if batch.any?
|
|
298
|
+
AuditLogEntry.insert_all!(batch)
|
|
299
|
+
batch.each { |attrs| publish_entry(AuditLogEntry.new(attrs)) } if streaming_adapter
|
|
300
|
+
end
|
|
248
301
|
result
|
|
249
302
|
ensure
|
|
250
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
|
|
@@ -118,11 +119,15 @@ files:
|
|
|
118
119
|
- lib/generators/rails_audit_log/install/templates/create_audit_log_entries.rb
|
|
119
120
|
- lib/generators/rails_audit_log/migrate_from_paper_trail/migrate_from_paper_trail_generator.rb
|
|
120
121
|
- lib/generators/rails_audit_log/migrate_from_paper_trail/templates/migrate_from_paper_trail.rb
|
|
122
|
+
- lib/generators/rails_audit_log/tenant/templates/add_tenant_id_to_audit_log_entries.rb
|
|
123
|
+
- lib/generators/rails_audit_log/tenant/tenant_generator.rb
|
|
121
124
|
- lib/rails_audit_log.rb
|
|
122
125
|
- lib/rails_audit_log/engine.rb
|
|
123
126
|
- lib/rails_audit_log/matchers.rb
|
|
124
127
|
- lib/rails_audit_log/minitest_assertions.rb
|
|
125
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
|
|
126
131
|
- lib/rails_audit_log/test_helpers.rb
|
|
127
132
|
- lib/rails_audit_log/version.rb
|
|
128
133
|
- lib/tasks/rails_audit_log_tasks.rake
|