waldit 0.0.2 → 0.0.3
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 +116 -0
- data/lib/waldit/postgresql_adapter.rb +40 -0
- data/lib/waldit/version.rb +1 -1
- data/lib/waldit/watcher.rb +1 -1
- data/rbi/waldit.rbi +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb7ae260f6d1af42f4e6169084c7ba79de48cea93f44ff47be032f5764df23a6
|
4
|
+
data.tar.gz: 7cf9d0c022ffbb457bcac4380ed635cd65d2df1db24faae5fbd169b50b5aa07c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b54045c2265401d420a276efddb20525b7b72e39d27c25e10da6a7a2c357805ea1956aed900d3c265f5cc88096dc402cd53ea52d0989eff8e3dd965d47c2ec75
|
7
|
+
data.tar.gz: 8d86ba10a3c94e7ca87bb90a39957663b305f19b6b5a8bfb7062fdae89879b57c82d230989bfd49b5480738f63ca81bf9db7b1093406b730578498bfc001a862
|
data/README.md
CHANGED
@@ -1 +1,117 @@
|
|
1
1
|
# Waldit
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/waldit)
|
4
|
+
|
5
|
+
Waldit is a Ruby gem that provides a simple and extensible way to audit changes to your ActiveRecord models. It leverages PostgreSQL's logical replication capabilities to capture changes directly from your database with 100% consistency.
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
- **Automatic Auditing:** Automatically track `create`, `update`, and `delete` operations on your models.
|
10
|
+
- **Contextual Auditing:** Add custom context to your audit records to understand who made the change and why.
|
11
|
+
- **Flexible Configuration:** Configure which tables and columns to watch, and how to store audit information.
|
12
|
+
- **High Performance:** Built on top of [`wal`](https://github.com/reu/wal), which uses PostgreSQL's logical replication for minimal overhead.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "waldit"
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install waldit
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
1. **Configure your database adapter:**
|
33
|
+
|
34
|
+
First step is to configure in your `config/database.yml` and change your adapter to `postgresqlwaldit`, which is a special adapter that allows injecting `waldit` contextual information on your transactions:
|
35
|
+
|
36
|
+
```yaml
|
37
|
+
default: &default
|
38
|
+
adapter: postgresqlwaldit
|
39
|
+
# ...
|
40
|
+
```
|
41
|
+
|
42
|
+
2. **Create an audit table:**
|
43
|
+
|
44
|
+
Generate a migration to create the `waldit` table:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
rails generate migration create_waldit
|
48
|
+
```
|
49
|
+
|
50
|
+
And then add the following to your migration file:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class CreateWalditTable < ActiveRecord::Migration[7.0]
|
54
|
+
def change
|
55
|
+
create_table :waldit do |t|
|
56
|
+
t.bigint :transaction_id, null: false
|
57
|
+
t.bigint :lsn, null: false
|
58
|
+
t.string :action, null: false
|
59
|
+
t.jsonb :context, default: {}
|
60
|
+
t.string :table_name, null: false
|
61
|
+
t.string :primary_key, null: false
|
62
|
+
t.jsonb :old, default: {}
|
63
|
+
t.jsonb :new, default: {}
|
64
|
+
t.timestamp :commited_at
|
65
|
+
|
66
|
+
t.index [:table_name, :primary_key, :transaction_id], unique: true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
3. **Configure Waldit:**
|
73
|
+
|
74
|
+
Create an initializer file at `config/initializers/waldit.rb`:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Waldit.configure do |config|
|
78
|
+
# A callback that returns true if a table should be watched.
|
79
|
+
config.watched_tables = ->(table) { table != "waldit" }
|
80
|
+
|
81
|
+
# A callback that returns an array of columns to ignore for a given table.
|
82
|
+
config.ignored_columns = ->(table) { %w[created_at updated_at] }
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
4. **Add context to your changes:**
|
87
|
+
|
88
|
+
Use the `with_context` method to add context to your database operations:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
Waldit.with_context(user_id: 1, reason: "User updated their profile") do
|
92
|
+
user.update(name: "New Name")
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
5. **Start the watcher:**
|
97
|
+
|
98
|
+
To process the events, you need to start a WAL watcher. The recommended way is to have a config/waldit.yml
|
99
|
+
|
100
|
+
```yml
|
101
|
+
slots:
|
102
|
+
audit:
|
103
|
+
publications: [waldit_publication]
|
104
|
+
watcher: Waldit::Watcher
|
105
|
+
```
|
106
|
+
|
107
|
+
And then run:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
bundle exec wal start config/waldit.yml
|
111
|
+
```
|
112
|
+
|
113
|
+
## How it Works
|
114
|
+
|
115
|
+
Waldit uses a custom PostgreSQL adapter to set the `waldit_context` session variable before each transaction. This context is then captured by the logical replication slot and stored in the `waldit` table by the `Waldit::Watcher`.
|
116
|
+
|
117
|
+
The `Waldit::Watcher` is a streaming watcher that listens for changes in the logical replication slot and creates audit records in the `waldit` table. It processes events in batches to minimize the number of database transactions.
|
@@ -27,6 +27,46 @@ module Waldit
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def exec_no_cache(sql, ...)
|
31
|
+
return super if READ_QUERY_REGEXP.match? sql
|
32
|
+
return super if @current_waldit_context == Waldit.context.hash
|
33
|
+
|
34
|
+
if transaction_open?
|
35
|
+
set_waldit_context!
|
36
|
+
super
|
37
|
+
|
38
|
+
elsif Waldit.context
|
39
|
+
# We are trying to execute a query with waldit context while not in a transaction, so we start one
|
40
|
+
transaction do
|
41
|
+
set_waldit_context!
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def exec_cache(sql, ...)
|
51
|
+
return super if READ_QUERY_REGEXP.match? sql
|
52
|
+
return super if @current_waldit_context == Waldit.context.hash
|
53
|
+
|
54
|
+
if transaction_open?
|
55
|
+
set_waldit_context!
|
56
|
+
super
|
57
|
+
|
58
|
+
elsif Waldit.context
|
59
|
+
# We are trying to execute a query with waldit context while not in a transaction, so we start one
|
60
|
+
transaction do
|
61
|
+
set_waldit_context!
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
30
70
|
def begin_db_transaction(...)
|
31
71
|
@current_waldit_context = nil.hash
|
32
72
|
super
|
data/lib/waldit/version.rb
CHANGED
data/lib/waldit/watcher.rb
CHANGED
@@ -60,7 +60,7 @@ module Waldit
|
|
60
60
|
else
|
61
61
|
# Finally the most common case: just deleting a record not created or updated on this transaction
|
62
62
|
record.upsert(
|
63
|
-
audit.merge(action: "delete", old:),
|
63
|
+
audit.merge(action: "delete", old: event.old),
|
64
64
|
unique_by:,
|
65
65
|
on_duplicate: :update,
|
66
66
|
)
|
data/rbi/waldit.rbi
CHANGED