action-audit 1.0.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/CHANGELOG.md +29 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/LICENSE +21 -0
- data/README.md +232 -0
- data/Rakefile +12 -0
- data/docs/README.md +25 -0
- data/docs/api-reference.md +434 -0
- data/docs/configuration.md +230 -0
- data/docs/examples.md +609 -0
- data/docs/installation.md +114 -0
- data/docs/migration.md +571 -0
- data/docs/multi-engine.md +413 -0
- data/docs/troubleshooting.md +577 -0
- data/docs/usage.md +347 -0
- data/lib/action-audit.rb +3 -0
- data/lib/action_audit/audit_messages.rb +73 -0
- data/lib/action_audit/engine.rb +19 -0
- data/lib/action_audit/version.rb +5 -0
- data/lib/action_audit.rb +75 -0
- data/lib/generators/action_audit/install_generator.rb +25 -0
- data/lib/generators/action_audit/templates/README +25 -0
- data/lib/generators/action_audit/templates/action_audit.rb +17 -0
- data/lib/generators/action_audit/templates/audit.yml +30 -0
- data/sig/action_audit.rbs +4 -0
- metadata +146 -0
data/docs/usage.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Usage Guide
|
|
2
|
+
|
|
3
|
+
This guide covers how to use ActionAudit in your Rails controllers and common usage patterns.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
### Including ActionAudit
|
|
8
|
+
|
|
9
|
+
Include the `ActionAudit` module in any controller you want to audit:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class UsersController < ApplicationController
|
|
13
|
+
include ActionAudit
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@user = User.create!(user_params)
|
|
17
|
+
# ActionAudit automatically logs this action after completion
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update
|
|
21
|
+
@user = User.find(params[:id])
|
|
22
|
+
@user.update!(user_params)
|
|
23
|
+
# Will log with interpolated parameters
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def user_params
|
|
29
|
+
params.require(:user).permit(:name, :email, :role)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### How It Works
|
|
35
|
+
|
|
36
|
+
1. **Automatic Hook**: When you include `ActionAudit`, it adds an `after_action :audit_request` callback
|
|
37
|
+
2. **Message Lookup**: After each action, it looks up the corresponding message in your `audit.yml` configuration
|
|
38
|
+
3. **Parameter Interpolation**: It interpolates the message with parameters from `params`
|
|
39
|
+
4. **Logging**: It logs the final message using `Rails.logger`
|
|
40
|
+
|
|
41
|
+
## Parameter Interpolation
|
|
42
|
+
|
|
43
|
+
ActionAudit uses Ruby's string interpolation (`%{key}`) to inject parameter values into audit messages.
|
|
44
|
+
|
|
45
|
+
### Basic Interpolation
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
# config/audit.yml
|
|
49
|
+
users:
|
|
50
|
+
create: "Created user %{email}"
|
|
51
|
+
update: "Updated user %{id} with name %{name}"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class UsersController < ApplicationController
|
|
56
|
+
include ActionAudit
|
|
57
|
+
|
|
58
|
+
def create
|
|
59
|
+
# If params = { email: "john@example.com", name: "John Doe" }
|
|
60
|
+
# Will log: "Created user john@example.com"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def update
|
|
64
|
+
# If params = { id: "123", name: "Jane Doe" }
|
|
65
|
+
# Will log: "Updated user 123 with name Jane Doe"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Nested Parameters
|
|
71
|
+
|
|
72
|
+
ActionAudit automatically flattens nested parameters for interpolation:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# If params = { user: { email: "john@example.com" }, id: "123" }
|
|
76
|
+
# You can reference both %{id} and %{email} in your audit message
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Error Handling
|
|
80
|
+
|
|
81
|
+
If a parameter referenced in the audit message is missing, ActionAudit handles it gracefully:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
users:
|
|
85
|
+
create: "Created user %{email} with role %{role}"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If `params[:role]` is missing, the log will show:
|
|
89
|
+
```
|
|
90
|
+
Created user john@example.com with role %{role} (interpolation error: key{role} not found)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Controller Patterns
|
|
94
|
+
|
|
95
|
+
### Application-Wide Auditing
|
|
96
|
+
|
|
97
|
+
Include ActionAudit in your `ApplicationController` to audit all controller actions:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class ApplicationController < ActionController::Base
|
|
101
|
+
include ActionAudit
|
|
102
|
+
|
|
103
|
+
# All controllers inheriting from this will be audited
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Selective Auditing
|
|
108
|
+
|
|
109
|
+
Include ActionAudit only in specific controllers:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
class Admin::UsersController < ApplicationController
|
|
113
|
+
include ActionAudit # Only admin actions are audited
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class PublicController < ApplicationController
|
|
117
|
+
# No auditing for public actions
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Namespaced Controllers
|
|
122
|
+
|
|
123
|
+
ActionAudit automatically handles namespaced controllers:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
class Admin::Users::ProfilesController < ApplicationController
|
|
127
|
+
include ActionAudit
|
|
128
|
+
|
|
129
|
+
def update
|
|
130
|
+
# Will look up: admin/users/profiles/update in audit.yml
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Common Usage Patterns
|
|
136
|
+
|
|
137
|
+
### User Management
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
# config/audit.yml
|
|
141
|
+
admin:
|
|
142
|
+
users:
|
|
143
|
+
create: "Admin created user %{email} with role %{role}"
|
|
144
|
+
update: "Admin updated user %{id}"
|
|
145
|
+
destroy: "Admin deleted user %{id}"
|
|
146
|
+
activate: "Admin activated user %{id}"
|
|
147
|
+
deactivate: "Admin deactivated user %{id}"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class Admin::UsersController < ApplicationController
|
|
152
|
+
include ActionAudit
|
|
153
|
+
|
|
154
|
+
def create
|
|
155
|
+
@user = User.create!(user_params)
|
|
156
|
+
# Logs: "Admin created user john@example.com with role editor"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def activate
|
|
160
|
+
@user = User.find(params[:id])
|
|
161
|
+
@user.update!(active: true)
|
|
162
|
+
# Logs: "Admin activated user 123"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Authentication & Sessions
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
# config/audit.yml
|
|
171
|
+
sessions:
|
|
172
|
+
create: "User logged in with %{email}"
|
|
173
|
+
destroy: "User logged out"
|
|
174
|
+
|
|
175
|
+
passwords:
|
|
176
|
+
create: "Password reset requested for %{email}"
|
|
177
|
+
update: "Password changed for user %{user_id}"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
class SessionsController < ApplicationController
|
|
182
|
+
include ActionAudit
|
|
183
|
+
|
|
184
|
+
def create
|
|
185
|
+
# params[:email] = "user@example.com"
|
|
186
|
+
# Logs: "User logged in with user@example.com"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def destroy
|
|
190
|
+
# Logs: "User logged out"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### API Endpoints
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
# config/audit.yml
|
|
199
|
+
api:
|
|
200
|
+
v1:
|
|
201
|
+
webhooks:
|
|
202
|
+
create: "Webhook received from %{source} with %{event_type}"
|
|
203
|
+
|
|
204
|
+
users:
|
|
205
|
+
create: "API user created via client %{client_id}"
|
|
206
|
+
update: "API user %{id} updated via client %{client_id}"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
class API::V1::WebhooksController < ApplicationController
|
|
211
|
+
include ActionAudit
|
|
212
|
+
|
|
213
|
+
def create
|
|
214
|
+
# params = { source: "stripe", event_type: "payment.succeeded" }
|
|
215
|
+
# Logs: "Webhook received from stripe with payment.succeeded"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Content Management
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
# config/audit.yml
|
|
224
|
+
posts:
|
|
225
|
+
create: "Created post '%{title}'"
|
|
226
|
+
update: "Updated post %{id}"
|
|
227
|
+
destroy: "Deleted post '%{title}'"
|
|
228
|
+
publish: "Published post '%{title}'"
|
|
229
|
+
unpublish: "Unpublished post '%{title}'"
|
|
230
|
+
|
|
231
|
+
categories:
|
|
232
|
+
create: "Created category '%{name}'"
|
|
233
|
+
update: "Updated category %{id} to '%{name}'"
|
|
234
|
+
destroy: "Deleted category '%{name}'"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Advanced Usage
|
|
238
|
+
|
|
239
|
+
### Custom Parameter Extraction
|
|
240
|
+
|
|
241
|
+
Sometimes you need to log information that's not directly in `params`. You can modify parameters before the audit:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
class PostsController < ApplicationController
|
|
245
|
+
include ActionAudit
|
|
246
|
+
|
|
247
|
+
before_action :set_audit_params, only: [:publish, :unpublish]
|
|
248
|
+
|
|
249
|
+
def publish
|
|
250
|
+
@post = Post.find(params[:id])
|
|
251
|
+
@post.update!(published: true)
|
|
252
|
+
# Will use the custom title parameter we set
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
def set_audit_params
|
|
258
|
+
@post = Post.find(params[:id])
|
|
259
|
+
params[:title] = @post.title # Add title to params for auditing
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Conditional Auditing
|
|
265
|
+
|
|
266
|
+
You can conditionally include ActionAudit or skip certain actions:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
class UsersController < ApplicationController
|
|
270
|
+
include ActionAudit
|
|
271
|
+
|
|
272
|
+
# Skip auditing for certain actions
|
|
273
|
+
skip_after_action :audit_request, only: [:show, :index]
|
|
274
|
+
|
|
275
|
+
# Or use conditional logic
|
|
276
|
+
def sensitive_action
|
|
277
|
+
# Custom auditing logic here if needed
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Integration with Current User
|
|
283
|
+
|
|
284
|
+
ActionAudit works well with authentication systems. The custom formatter can access `current_user`:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# config/initializers/action_audit.rb
|
|
288
|
+
ActionAudit.log_formatter = lambda do |controller, action, message|
|
|
289
|
+
if defined?(current_user) && current_user
|
|
290
|
+
"#{message} (by #{current_user.email})"
|
|
291
|
+
else
|
|
292
|
+
"#{message} (by anonymous user)"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Testing
|
|
298
|
+
|
|
299
|
+
### Testing Audit Messages
|
|
300
|
+
|
|
301
|
+
You can test that audit messages are being logged correctly:
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
# spec/controllers/users_controller_spec.rb
|
|
305
|
+
RSpec.describe UsersController, type: :controller do
|
|
306
|
+
describe "#create" do
|
|
307
|
+
it "logs user creation" do
|
|
308
|
+
expect(Rails.logger).to receive(:info).with(/Created user.*john@example\.com/)
|
|
309
|
+
|
|
310
|
+
post :create, params: { user: { email: "john@example.com" } }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Testing Without Auditing
|
|
317
|
+
|
|
318
|
+
In tests where you don't want audit logging, you can stub it:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
before do
|
|
322
|
+
allow(controller).to receive(:audit_request)
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Performance Considerations
|
|
327
|
+
|
|
328
|
+
ActionAudit is designed to be lightweight:
|
|
329
|
+
|
|
330
|
+
- **Minimal Overhead**: Only runs after successful actions
|
|
331
|
+
- **Lazy Loading**: Audit messages are loaded once at startup
|
|
332
|
+
- **No Database Calls**: All auditing happens through Rails logger
|
|
333
|
+
- **Graceful Failures**: Missing messages or parameters won't break your application
|
|
334
|
+
|
|
335
|
+
## Error Handling
|
|
336
|
+
|
|
337
|
+
ActionAudit handles errors gracefully:
|
|
338
|
+
|
|
339
|
+
1. **Missing Messages**: If no audit message is configured for an action, nothing is logged
|
|
340
|
+
2. **Missing Parameters**: If interpolation fails, the error is logged alongside the original message
|
|
341
|
+
3. **Invalid YAML**: Rails will warn about YAML syntax errors during loading
|
|
342
|
+
|
|
343
|
+
## Next Steps
|
|
344
|
+
|
|
345
|
+
- [Learn about multi-engine setup](multi-engine.md)
|
|
346
|
+
- [See real-world examples](examples.md)
|
|
347
|
+
- [Check the API reference](api-reference.md)
|
data/lib/action-audit.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module ActionAudit
|
|
6
|
+
class AuditMessages
|
|
7
|
+
class << self
|
|
8
|
+
def messages
|
|
9
|
+
@messages ||= {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def load_from_file(file_path)
|
|
13
|
+
return unless File.exist?(file_path)
|
|
14
|
+
|
|
15
|
+
content = YAML.load_file(file_path)
|
|
16
|
+
return unless content.is_a?(Hash)
|
|
17
|
+
|
|
18
|
+
messages.deep_merge!(content)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_from_engines
|
|
22
|
+
# Load from all Rails engines and the main app
|
|
23
|
+
if defined?(Rails) && Rails.application
|
|
24
|
+
# Load from main application
|
|
25
|
+
main_audit_file = Rails.root.join("config", "audit.yml")
|
|
26
|
+
load_from_file(main_audit_file) if File.exist?(main_audit_file)
|
|
27
|
+
|
|
28
|
+
# Load from all engines
|
|
29
|
+
Rails.application.railties.each do |railtie|
|
|
30
|
+
next unless railtie.respond_to?(:root)
|
|
31
|
+
|
|
32
|
+
engine_audit_file = railtie.root.join("config", "audit.yml")
|
|
33
|
+
load_from_file(engine_audit_file) if File.exist?(engine_audit_file)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def lookup(controller_path, action_name)
|
|
39
|
+
# Convert controller path to nested hash lookup
|
|
40
|
+
# e.g., "manage/accounts" becomes ["manage", "accounts"]
|
|
41
|
+
path_parts = controller_path.split("/")
|
|
42
|
+
|
|
43
|
+
# Navigate through nested hash structure
|
|
44
|
+
current_level = messages
|
|
45
|
+
path_parts.each do |part|
|
|
46
|
+
current_level = current_level[part]
|
|
47
|
+
return nil unless current_level.is_a?(Hash)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Look up the action
|
|
51
|
+
current_level[action_name]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clear!
|
|
55
|
+
@messages = {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_message(controller_path, action_name, message)
|
|
59
|
+
path_parts = controller_path.split("/")
|
|
60
|
+
|
|
61
|
+
# Navigate/create nested structure
|
|
62
|
+
current_level = messages
|
|
63
|
+
path_parts.each do |part|
|
|
64
|
+
current_level[part] ||= {}
|
|
65
|
+
current_level = current_level[part]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Set the message
|
|
69
|
+
current_level[action_name] = message
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionAudit
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace ActionAudit
|
|
6
|
+
|
|
7
|
+
initializer "action_audit.load_audit_messages", after: :load_config_initializers do
|
|
8
|
+
ActionAudit::AuditMessages.load_from_engines
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Reload audit messages in development when files change
|
|
12
|
+
config.to_prepare do
|
|
13
|
+
if Rails.env.development?
|
|
14
|
+
ActionAudit::AuditMessages.clear!
|
|
15
|
+
ActionAudit::AuditMessages.load_from_engines
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/action_audit.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/concern"
|
|
5
|
+
require "rails"
|
|
6
|
+
|
|
7
|
+
require_relative "action_audit/version"
|
|
8
|
+
require_relative "action_audit/audit_messages"
|
|
9
|
+
require_relative "action_audit/engine" if defined?(Rails)
|
|
10
|
+
|
|
11
|
+
module ActionAudit
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
# Configuration attributes
|
|
15
|
+
mattr_accessor :log_formatter, default: nil
|
|
16
|
+
mattr_accessor :log_tag, default: nil
|
|
17
|
+
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
included do
|
|
21
|
+
after_action :audit_request
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def audit_request
|
|
27
|
+
controller_path = self.class.name.underscore.gsub("_controller", "")
|
|
28
|
+
action_name = action_name()
|
|
29
|
+
|
|
30
|
+
# Look up the audit message
|
|
31
|
+
message = ActionAudit::AuditMessages.lookup(controller_path, action_name)
|
|
32
|
+
return unless message
|
|
33
|
+
|
|
34
|
+
# Interpolate the message with params
|
|
35
|
+
interpolated_message = interpolate_message(message, params)
|
|
36
|
+
|
|
37
|
+
# Format the log entry
|
|
38
|
+
formatted_message = if ActionAudit.log_formatter
|
|
39
|
+
ActionAudit.log_formatter.call(controller_path, action_name, interpolated_message, params)
|
|
40
|
+
else
|
|
41
|
+
"#{controller_path}/#{action_name} - #{interpolated_message}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Log with optional tag
|
|
45
|
+
if ActionAudit.log_tag
|
|
46
|
+
Rails.logger.tagged(ActionAudit.log_tag) do
|
|
47
|
+
Rails.logger.info(formatted_message)
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
Rails.logger.info(formatted_message)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def interpolate_message(message, interpolation_params)
|
|
55
|
+
return "" if message.nil?
|
|
56
|
+
return message.to_s unless message.respond_to?(:%)
|
|
57
|
+
|
|
58
|
+
# Convert params to hash with symbol keys for interpolation
|
|
59
|
+
unsafe_hash = interpolation_params.to_unsafe_h
|
|
60
|
+
string_params = unsafe_hash.respond_to?(:deep_stringify_keys) ? unsafe_hash.deep_stringify_keys : unsafe_hash
|
|
61
|
+
|
|
62
|
+
# Convert to symbol keys for string interpolation
|
|
63
|
+
symbol_params = {}
|
|
64
|
+
string_params.each { |k, v| symbol_params[k.to_sym] = v }
|
|
65
|
+
|
|
66
|
+
# Perform string interpolation
|
|
67
|
+
message % symbol_params
|
|
68
|
+
rescue KeyError => e
|
|
69
|
+
# If interpolation fails, log the original message with error info
|
|
70
|
+
"#{message} (interpolation error: #{e.message})"
|
|
71
|
+
rescue TypeError
|
|
72
|
+
# If message is not a string or doesn't support %, return as-is
|
|
73
|
+
message.to_s
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module ActionAudit
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
desc "Install ActionAudit configuration"
|
|
8
|
+
|
|
9
|
+
def self.source_root
|
|
10
|
+
@source_root ||= File.expand_path("templates", __dir__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def copy_audit_config
|
|
14
|
+
template "audit.yml", "config/audit.yml"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_initializer
|
|
18
|
+
template "action_audit.rb", "config/initializers/action_audit.rb"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def show_readme
|
|
22
|
+
readme "README"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
ActionAudit has been installed!
|
|
4
|
+
|
|
5
|
+
Next steps:
|
|
6
|
+
|
|
7
|
+
1. Include ActionAudit in your controllers:
|
|
8
|
+
|
|
9
|
+
class ApplicationController < ActionController::Base
|
|
10
|
+
include ActionAudit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Or include it in specific controllers:
|
|
14
|
+
|
|
15
|
+
class Admin::UsersController < ApplicationController
|
|
16
|
+
include ActionAudit
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
2. Edit config/audit.yml to define your audit messages
|
|
20
|
+
|
|
21
|
+
3. Customize logging in config/initializers/action_audit.rb (optional)
|
|
22
|
+
|
|
23
|
+
4. Test your setup by triggering a controller action and checking your logs
|
|
24
|
+
|
|
25
|
+
===============================================================================
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# ActionAudit configuration
|
|
2
|
+
# Customize how audit logs are formatted and tagged
|
|
3
|
+
|
|
4
|
+
# Add a tag to all audit log entries
|
|
5
|
+
# ActionAudit.log_tag = "AUDIT"
|
|
6
|
+
|
|
7
|
+
# Customize the log message format
|
|
8
|
+
# The formatter receives (controller_path, action_name, interpolated_message)
|
|
9
|
+
# ActionAudit.log_formatter = lambda do |controller, action, message|
|
|
10
|
+
# user_info = defined?(current_user) && current_user ? "User: #{current_user.email}" : "User: anonymous"
|
|
11
|
+
# "[#{Time.current.iso8601}] #{controller}/#{action} | #{message} | #{user_info}"
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Simple formatter example:
|
|
15
|
+
# ActionAudit.log_formatter = ->(controller, action, msg) do
|
|
16
|
+
# "AUDIT: #{controller}/#{action} - #{msg}"
|
|
17
|
+
# end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ActionAudit configuration file
|
|
2
|
+
# This file defines audit messages for your controllers
|
|
3
|
+
# Organize by controller namespace and action
|
|
4
|
+
|
|
5
|
+
# Example structure:
|
|
6
|
+
# namespace:
|
|
7
|
+
# controller:
|
|
8
|
+
# action: "Message with %{param} interpolation"
|
|
9
|
+
|
|
10
|
+
# Example audit messages
|
|
11
|
+
# Uncomment and customize as needed
|
|
12
|
+
|
|
13
|
+
# admin:
|
|
14
|
+
# users:
|
|
15
|
+
# create: "Created user %{email}"
|
|
16
|
+
# update: "Updated user %{id}"
|
|
17
|
+
# destroy: "Deleted user %{id}"
|
|
18
|
+
# accounts:
|
|
19
|
+
# create: "Created account %{name}"
|
|
20
|
+
# update: "Updated account %{id}"
|
|
21
|
+
|
|
22
|
+
# sessions:
|
|
23
|
+
# create: "User logged in with %{email}"
|
|
24
|
+
# destroy: "User logged out"
|
|
25
|
+
|
|
26
|
+
# posts:
|
|
27
|
+
# create: "Created post '%{title}'"
|
|
28
|
+
# update: "Updated post %{id}"
|
|
29
|
+
# publish: "Published post %{id}"
|
|
30
|
+
# unpublish: "Unpublished post %{id}"
|