activerabbit-ai 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +49 -0
- data/IMPLEMENTATION_SUMMARY.md +220 -0
- data/README.md +317 -0
- data/Rakefile +10 -0
- data/TESTING_GUIDE.md +585 -0
- data/examples/rails_app_testing.rb +437 -0
- data/examples/rails_integration.rb +243 -0
- data/examples/standalone_usage.rb +309 -0
- data/lib/active_rabbit/client/configuration.rb +162 -0
- data/lib/active_rabbit/client/event_processor.rb +131 -0
- data/lib/active_rabbit/client/exception_tracker.rb +157 -0
- data/lib/active_rabbit/client/http_client.rb +137 -0
- data/lib/active_rabbit/client/n_plus_one_detector.rb +188 -0
- data/lib/active_rabbit/client/performance_monitor.rb +150 -0
- data/lib/active_rabbit/client/pii_scrubber.rb +169 -0
- data/lib/active_rabbit/client/railtie.rb +328 -0
- data/lib/active_rabbit/client/sidekiq_middleware.rb +130 -0
- data/lib/active_rabbit/client/version.rb +7 -0
- data/lib/active_rabbit/client.rb +119 -0
- data/lib/active_rabbit.rb +3 -0
- data/script/test_production_readiness.rb +403 -0
- data/sig/active_rabbit/client.rbs +6 -0
- metadata +155 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example Rails Application Testing with ActiveRabbit
|
|
4
|
+
# This file shows how to test ActiveRabbit integration in your Rails app
|
|
5
|
+
|
|
6
|
+
# spec/support/active_rabbit_helpers.rb
|
|
7
|
+
module ActiveRabbitHelpers
|
|
8
|
+
def setup_active_rabbit_test
|
|
9
|
+
ActiveRabbit::Client.configure do |config|
|
|
10
|
+
config.api_key = "test-api-key"
|
|
11
|
+
config.project_id = "test-project"
|
|
12
|
+
config.api_url = "https://api.activerabbit.com"
|
|
13
|
+
config.environment = "test"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Stub all API calls
|
|
17
|
+
stub_active_rabbit_api
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def stub_active_rabbit_api
|
|
21
|
+
stub_request(:post, "https://api.activerabbit.com/api/v1/exceptions")
|
|
22
|
+
.to_return(status: 200, body: '{"status":"ok"}')
|
|
23
|
+
|
|
24
|
+
stub_request(:post, "https://api.activerabbit.com/api/v1/events")
|
|
25
|
+
.to_return(status: 200, body: '{"status":"ok"}')
|
|
26
|
+
|
|
27
|
+
stub_request(:post, "https://api.activerabbit.com/api/v1/performance")
|
|
28
|
+
.to_return(status: 200, body: '{"status":"ok"}')
|
|
29
|
+
|
|
30
|
+
stub_request(:post, "https://api.activerabbit.com/api/v1/batch")
|
|
31
|
+
.to_return(status: 200, body: '{"status":"ok"}')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def expect_exception_tracked(exception_type: nil, message: nil, context: nil)
|
|
35
|
+
expect(WebMock).to have_requested(:post, "https://api.activerabbit.com/api/v1/exceptions")
|
|
36
|
+
.with { |request|
|
|
37
|
+
body = JSON.parse(request.body)
|
|
38
|
+
|
|
39
|
+
result = true
|
|
40
|
+
result &&= body["type"] == exception_type if exception_type
|
|
41
|
+
result &&= body["message"].include?(message) if message
|
|
42
|
+
|
|
43
|
+
if context
|
|
44
|
+
context.each do |key, value|
|
|
45
|
+
result &&= body.dig("context", key.to_s) == value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def expect_event_tracked(event_name, properties: nil)
|
|
54
|
+
expect(WebMock).to have_requested(:post, "https://api.activerabbit.com/api/v1/events")
|
|
55
|
+
.with { |request|
|
|
56
|
+
body = JSON.parse(request.body)
|
|
57
|
+
|
|
58
|
+
result = body["name"] == event_name
|
|
59
|
+
|
|
60
|
+
if properties
|
|
61
|
+
properties.each do |key, value|
|
|
62
|
+
result &&= body.dig("properties", key.to_s) == value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
result
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def expect_performance_tracked(operation_name, min_duration: nil)
|
|
71
|
+
expect(WebMock).to have_requested(:post, "https://api.activerabbit.com/api/v1/performance")
|
|
72
|
+
.with { |request|
|
|
73
|
+
body = JSON.parse(request.body)
|
|
74
|
+
|
|
75
|
+
result = body["name"] == operation_name
|
|
76
|
+
result &&= body["duration_ms"] >= min_duration if min_duration
|
|
77
|
+
|
|
78
|
+
result
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Include in RSpec configuration
|
|
84
|
+
# spec/rails_helper.rb
|
|
85
|
+
RSpec.configure do |config|
|
|
86
|
+
config.include ActiveRabbitHelpers
|
|
87
|
+
|
|
88
|
+
config.before(:each) do
|
|
89
|
+
setup_active_rabbit_test
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
config.after(:each) do
|
|
93
|
+
ActiveRabbit::Client.configuration = nil
|
|
94
|
+
Thread.current[:active_rabbit_request_context] = nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Example controller test
|
|
99
|
+
# spec/controllers/users_controller_spec.rb
|
|
100
|
+
RSpec.describe UsersController, type: :controller do
|
|
101
|
+
describe "GET #show" do
|
|
102
|
+
context "when user exists" do
|
|
103
|
+
let(:user) { create(:user) }
|
|
104
|
+
|
|
105
|
+
it "returns the user successfully" do
|
|
106
|
+
get :show, params: { id: user.id }
|
|
107
|
+
|
|
108
|
+
expect(response).to have_http_status(:success)
|
|
109
|
+
|
|
110
|
+
# Should track performance but no exceptions
|
|
111
|
+
expect_performance_tracked("controller.action")
|
|
112
|
+
expect(WebMock).not_to have_requested(:post, /exceptions/)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context "when user does not exist" do
|
|
117
|
+
it "tracks the RecordNotFound exception" do
|
|
118
|
+
expect {
|
|
119
|
+
get :show, params: { id: 999999 }
|
|
120
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
|
121
|
+
|
|
122
|
+
expect_exception_tracked(
|
|
123
|
+
exception_type: "ActiveRecord::RecordNotFound",
|
|
124
|
+
message: "Couldn't find User",
|
|
125
|
+
context: {
|
|
126
|
+
"request" => hash_including(
|
|
127
|
+
"method" => "GET",
|
|
128
|
+
"path" => "/users/999999"
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "POST #create" do
|
|
137
|
+
context "with valid parameters" do
|
|
138
|
+
let(:valid_params) { { user: { name: "John Doe", email: "john@example.com" } } }
|
|
139
|
+
|
|
140
|
+
it "creates user and tracks success event" do
|
|
141
|
+
expect {
|
|
142
|
+
post :create, params: valid_params
|
|
143
|
+
}.to change(User, :count).by(1)
|
|
144
|
+
|
|
145
|
+
expect_event_tracked(
|
|
146
|
+
"user_created",
|
|
147
|
+
properties: {
|
|
148
|
+
"source" => "web"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
context "with invalid parameters" do
|
|
155
|
+
let(:invalid_params) { { user: { name: "" } } }
|
|
156
|
+
|
|
157
|
+
it "tracks validation failure" do
|
|
158
|
+
post :create, params: invalid_params
|
|
159
|
+
|
|
160
|
+
expect_event_tracked(
|
|
161
|
+
"user_creation_failed",
|
|
162
|
+
properties: {
|
|
163
|
+
"errors" => array_including("Name can't be blank")
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Example model test
|
|
172
|
+
# spec/models/user_spec.rb
|
|
173
|
+
RSpec.describe User, type: :model do
|
|
174
|
+
describe "callbacks" do
|
|
175
|
+
it "tracks user creation event" do
|
|
176
|
+
user = create(:user)
|
|
177
|
+
|
|
178
|
+
expect_event_tracked(
|
|
179
|
+
"model_user_created",
|
|
180
|
+
properties: {
|
|
181
|
+
"id" => user.id
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "tracks user status changes" do
|
|
187
|
+
user = create(:user, status: "pending")
|
|
188
|
+
user.update(status: "active")
|
|
189
|
+
|
|
190
|
+
expect_event_tracked(
|
|
191
|
+
"user_status_changed",
|
|
192
|
+
properties: {
|
|
193
|
+
"from_status" => "pending",
|
|
194
|
+
"to_status" => "active"
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe "error handling" do
|
|
201
|
+
it "tracks validation errors" do
|
|
202
|
+
user = build(:user, email: "invalid-email")
|
|
203
|
+
|
|
204
|
+
expect(user.valid?).to be false
|
|
205
|
+
|
|
206
|
+
# If you've added custom tracking for validation errors
|
|
207
|
+
expect_event_tracked(
|
|
208
|
+
"validation_failed",
|
|
209
|
+
properties: {
|
|
210
|
+
"model" => "User",
|
|
211
|
+
"errors" => ["Email is invalid"]
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Example job test
|
|
219
|
+
# spec/jobs/user_notification_job_spec.rb
|
|
220
|
+
RSpec.describe UserNotificationJob, type: :job do
|
|
221
|
+
let(:user) { create(:user) }
|
|
222
|
+
|
|
223
|
+
describe "#perform" do
|
|
224
|
+
context "when job succeeds" do
|
|
225
|
+
it "tracks job completion" do
|
|
226
|
+
perform_enqueued_jobs do
|
|
227
|
+
UserNotificationJob.perform_later(user.id)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
expect_event_tracked("sidekiq_job_completed")
|
|
231
|
+
expect_performance_tracked("sidekiq.job")
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
context "when job fails" do
|
|
236
|
+
before do
|
|
237
|
+
allow_any_instance_of(UserNotificationJob).to receive(:perform)
|
|
238
|
+
.and_raise(StandardError, "Email service unavailable")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "tracks job failure and exception" do
|
|
242
|
+
expect {
|
|
243
|
+
perform_enqueued_jobs do
|
|
244
|
+
UserNotificationJob.perform_later(user.id)
|
|
245
|
+
end
|
|
246
|
+
}.to raise_error(StandardError, "Email service unavailable")
|
|
247
|
+
|
|
248
|
+
expect_exception_tracked(
|
|
249
|
+
exception_type: "StandardError",
|
|
250
|
+
message: "Email service unavailable"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
expect_event_tracked("sidekiq_job_failed")
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Example feature test
|
|
260
|
+
# spec/features/user_registration_spec.rb
|
|
261
|
+
RSpec.describe "User Registration", type: :feature do
|
|
262
|
+
scenario "successful user registration" do
|
|
263
|
+
visit new_user_registration_path
|
|
264
|
+
|
|
265
|
+
fill_in "Name", with: "John Doe"
|
|
266
|
+
fill_in "Email", with: "john@example.com"
|
|
267
|
+
fill_in "Password", with: "password123"
|
|
268
|
+
|
|
269
|
+
click_button "Sign Up"
|
|
270
|
+
|
|
271
|
+
expect(page).to have_content("Welcome, John!")
|
|
272
|
+
|
|
273
|
+
# Verify tracking
|
|
274
|
+
expect_event_tracked(
|
|
275
|
+
"user_signup",
|
|
276
|
+
properties: {
|
|
277
|
+
"source" => "website"
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
scenario "user registration with invalid data" do
|
|
283
|
+
visit new_user_registration_path
|
|
284
|
+
|
|
285
|
+
fill_in "Email", with: "invalid-email"
|
|
286
|
+
click_button "Sign Up"
|
|
287
|
+
|
|
288
|
+
expect(page).to have_content("Email is invalid")
|
|
289
|
+
|
|
290
|
+
# Should track the validation failure
|
|
291
|
+
expect_event_tracked("user_signup_failed")
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
scenario "handles server errors gracefully" do
|
|
295
|
+
# Simulate a server error during registration
|
|
296
|
+
allow_any_instance_of(UsersController).to receive(:create)
|
|
297
|
+
.and_raise(StandardError, "Database connection failed")
|
|
298
|
+
|
|
299
|
+
visit new_user_registration_path
|
|
300
|
+
|
|
301
|
+
fill_in "Name", with: "John Doe"
|
|
302
|
+
fill_in "Email", with: "john@example.com"
|
|
303
|
+
|
|
304
|
+
expect {
|
|
305
|
+
click_button "Sign Up"
|
|
306
|
+
}.to raise_error(StandardError)
|
|
307
|
+
|
|
308
|
+
# Verify exception was tracked
|
|
309
|
+
expect_exception_tracked(
|
|
310
|
+
exception_type: "StandardError",
|
|
311
|
+
message: "Database connection failed"
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Example request test for API endpoints
|
|
317
|
+
# spec/requests/api/users_spec.rb
|
|
318
|
+
RSpec.describe "API::Users", type: :request do
|
|
319
|
+
describe "GET /api/users/:id" do
|
|
320
|
+
let(:user) { create(:user) }
|
|
321
|
+
|
|
322
|
+
context "with valid request" do
|
|
323
|
+
it "returns user data and tracks API usage" do
|
|
324
|
+
get "/api/users/#{user.id}", headers: {
|
|
325
|
+
"Accept" => "application/json",
|
|
326
|
+
"User-Agent" => "MyApp/1.0"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
expect(response).to have_http_status(:success)
|
|
330
|
+
|
|
331
|
+
expect_event_tracked(
|
|
332
|
+
"api_endpoint_accessed",
|
|
333
|
+
properties: {
|
|
334
|
+
"endpoint" => "/api/users/:id",
|
|
335
|
+
"method" => "GET"
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
context "with invalid user ID" do
|
|
342
|
+
it "returns 404 and tracks the error" do
|
|
343
|
+
get "/api/users/999999", headers: { "Accept" => "application/json" }
|
|
344
|
+
|
|
345
|
+
expect(response).to have_http_status(:not_found)
|
|
346
|
+
|
|
347
|
+
expect_exception_tracked(
|
|
348
|
+
exception_type: "ActiveRecord::RecordNotFound"
|
|
349
|
+
)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
describe "POST /api/users" do
|
|
355
|
+
context "with rate limiting" do
|
|
356
|
+
it "tracks rate limit violations" do
|
|
357
|
+
# Simulate rate limiting
|
|
358
|
+
allow_any_instance_of(ApplicationController).to receive(:check_rate_limit)
|
|
359
|
+
.and_raise(RateLimitExceeded, "Too many requests")
|
|
360
|
+
|
|
361
|
+
expect {
|
|
362
|
+
post "/api/users", params: { user: { name: "Test" } }
|
|
363
|
+
}.to raise_error(RateLimitExceeded)
|
|
364
|
+
|
|
365
|
+
expect_exception_tracked(
|
|
366
|
+
exception_type: "RateLimitExceeded",
|
|
367
|
+
message: "Too many requests"
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Example system test
|
|
375
|
+
# spec/system/error_handling_spec.rb
|
|
376
|
+
RSpec.describe "Error Handling", type: :system do
|
|
377
|
+
before do
|
|
378
|
+
driven_by(:selenium_chrome_headless)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
scenario "handles JavaScript errors" do
|
|
382
|
+
# If you have JavaScript error tracking
|
|
383
|
+
visit some_page_with_js_error
|
|
384
|
+
|
|
385
|
+
# Trigger JS error
|
|
386
|
+
click_button "Trigger Error"
|
|
387
|
+
|
|
388
|
+
# Verify JS error was tracked (if implemented)
|
|
389
|
+
expect_event_tracked("javascript_error")
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
scenario "handles network timeouts" do
|
|
393
|
+
# Simulate slow external API
|
|
394
|
+
stub_request(:get, "https://external-api.com/data")
|
|
395
|
+
.to_timeout
|
|
396
|
+
|
|
397
|
+
visit page_that_calls_external_api
|
|
398
|
+
|
|
399
|
+
# Should handle gracefully and track the timeout
|
|
400
|
+
expect_exception_tracked(
|
|
401
|
+
exception_type: "Net::TimeoutError"
|
|
402
|
+
)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Performance test example
|
|
407
|
+
# spec/performance/activerabbit_impact_spec.rb
|
|
408
|
+
RSpec.describe "ActiveRabbit Performance Impact" do
|
|
409
|
+
let(:iterations) { 100 }
|
|
410
|
+
|
|
411
|
+
it "has minimal impact on controller actions" do
|
|
412
|
+
# Baseline without ActiveRabbit
|
|
413
|
+
ActiveRabbit::Client.configuration = nil
|
|
414
|
+
|
|
415
|
+
baseline_time = Benchmark.measure do
|
|
416
|
+
iterations.times do
|
|
417
|
+
get "/users/1"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# With ActiveRabbit enabled
|
|
422
|
+
setup_active_rabbit_test
|
|
423
|
+
|
|
424
|
+
activerabbit_time = Benchmark.measure do
|
|
425
|
+
iterations.times do
|
|
426
|
+
get "/users/1"
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Calculate overhead
|
|
431
|
+
overhead_percent = ((activerabbit_time.real - baseline_time.real) / baseline_time.real) * 100
|
|
432
|
+
|
|
433
|
+
expect(overhead_percent).to be < 5,
|
|
434
|
+
"ActiveRabbit overhead (#{overhead_percent.round(2)}%) exceeds 5% threshold"
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example Rails integration for ActiveRabbit::Client
|
|
4
|
+
|
|
5
|
+
# config/initializers/active_rabbit.rb
|
|
6
|
+
ActiveRabbit::Client.configure do |config|
|
|
7
|
+
# Required configuration
|
|
8
|
+
config.api_key = ENV['active_rabbit_API_KEY']
|
|
9
|
+
config.project_id = ENV['active_rabbit_PROJECT_ID']
|
|
10
|
+
config.environment = Rails.env
|
|
11
|
+
|
|
12
|
+
# Optional configuration
|
|
13
|
+
config.api_url = ENV.fetch('active_rabbit_API_URL', 'https://api.activerabbit.com')
|
|
14
|
+
config.release = ENV['HEROKU_SLUG_COMMIT'] || `git rev-parse HEAD`.chomp
|
|
15
|
+
|
|
16
|
+
# Performance settings
|
|
17
|
+
config.enable_performance_monitoring = Rails.env.production?
|
|
18
|
+
config.enable_n_plus_one_detection = true
|
|
19
|
+
config.batch_size = 50
|
|
20
|
+
config.flush_interval = 15
|
|
21
|
+
|
|
22
|
+
# PII protection
|
|
23
|
+
config.enable_pii_scrubbing = true
|
|
24
|
+
config.pii_fields += %w[
|
|
25
|
+
customer_id
|
|
26
|
+
internal_notes
|
|
27
|
+
admin_comments
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Exception filtering
|
|
31
|
+
config.ignored_exceptions += %w[
|
|
32
|
+
MyApp::BusinessLogicError
|
|
33
|
+
MyApp::ExpectedError
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# User agent filtering (ignore bots in production)
|
|
37
|
+
if Rails.env.production?
|
|
38
|
+
config.ignored_user_agents += [
|
|
39
|
+
/HeadlessChrome/i,
|
|
40
|
+
/PhantomJS/i,
|
|
41
|
+
/SiteAudit/i
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Callbacks for additional processing
|
|
46
|
+
config.before_send_exception = proc do |exception_data|
|
|
47
|
+
# Add deployment information
|
|
48
|
+
exception_data[:deployment] = {
|
|
49
|
+
version: ENV['APP_VERSION'],
|
|
50
|
+
build_number: ENV['BUILD_NUMBER']
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Don't send exceptions in test environment
|
|
54
|
+
return nil if Rails.env.test?
|
|
55
|
+
|
|
56
|
+
exception_data
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
config.before_send_event = proc do |event_data|
|
|
60
|
+
# Add user context if available
|
|
61
|
+
if current_user = Thread.current[:current_user]
|
|
62
|
+
event_data[:user_context] = {
|
|
63
|
+
id: current_user.id,
|
|
64
|
+
plan: current_user.plan,
|
|
65
|
+
created_at: current_user.created_at
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
event_data
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# app/controllers/application_controller.rb
|
|
74
|
+
class ApplicationController < ActionController::Base
|
|
75
|
+
before_action :set_active_rabbit_context
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def set_active_rabbit_context
|
|
80
|
+
# Set current user for ActiveRabbit context
|
|
81
|
+
Thread.current[:current_user] = current_user
|
|
82
|
+
|
|
83
|
+
# Add additional request context
|
|
84
|
+
if Thread.current[:active_rabbit_request_context]
|
|
85
|
+
Thread.current[:active_rabbit_request_context].merge!(
|
|
86
|
+
user_id: current_user&.id,
|
|
87
|
+
user_plan: current_user&.plan,
|
|
88
|
+
tenant_id: current_tenant&.id
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# app/controllers/orders_controller.rb
|
|
95
|
+
class OrdersController < ApplicationController
|
|
96
|
+
def create
|
|
97
|
+
# Manual exception tracking with context
|
|
98
|
+
begin
|
|
99
|
+
@order = Order.create!(order_params)
|
|
100
|
+
|
|
101
|
+
# Track successful order creation
|
|
102
|
+
ActiveRabbit::Client.track_event(
|
|
103
|
+
'order_created',
|
|
104
|
+
{
|
|
105
|
+
order_id: @order.id,
|
|
106
|
+
amount: @order.total_amount,
|
|
107
|
+
items_count: @order.items.count,
|
|
108
|
+
payment_method: @order.payment_method
|
|
109
|
+
},
|
|
110
|
+
user_id: current_user.id
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
redirect_to @order, notice: 'Order was successfully created.'
|
|
114
|
+
rescue PaymentProcessor::Error => e
|
|
115
|
+
# Track payment errors with additional context
|
|
116
|
+
ActiveRabbit::Client.track_exception(
|
|
117
|
+
e,
|
|
118
|
+
context: {
|
|
119
|
+
order_params: order_params.to_h,
|
|
120
|
+
payment_method: params[:payment_method],
|
|
121
|
+
user_id: current_user.id
|
|
122
|
+
},
|
|
123
|
+
tags: {
|
|
124
|
+
component: 'payment_processor',
|
|
125
|
+
severity: 'high'
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
redirect_to new_order_path, alert: 'Payment failed. Please try again.'
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def show
|
|
134
|
+
# Performance monitoring for complex operations
|
|
135
|
+
@order = ActiveRabbit::Client.performance_monitor.measure('order_loading') do
|
|
136
|
+
Order.includes(:items, :customer, :shipping_address).find(params[:id])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def order_params
|
|
143
|
+
params.require(:order).permit(:customer_id, items_attributes: [:product_id, :quantity])
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# app/jobs/order_processing_job.rb
|
|
148
|
+
class OrderProcessingJob < ApplicationJob
|
|
149
|
+
def perform(order_id)
|
|
150
|
+
order = Order.find(order_id)
|
|
151
|
+
|
|
152
|
+
# Sidekiq integration will automatically track this job
|
|
153
|
+
# But you can add custom events for important milestones
|
|
154
|
+
|
|
155
|
+
ActiveRabbit::Client.track_event(
|
|
156
|
+
'order_processing_started',
|
|
157
|
+
{ order_id: order.id },
|
|
158
|
+
user_id: order.customer_id
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Process the order
|
|
162
|
+
process_inventory(order)
|
|
163
|
+
charge_payment(order)
|
|
164
|
+
send_confirmation_email(order)
|
|
165
|
+
|
|
166
|
+
ActiveRabbit::Client.track_event(
|
|
167
|
+
'order_processing_completed',
|
|
168
|
+
{
|
|
169
|
+
order_id: order.id,
|
|
170
|
+
processing_time: Time.current - order.created_at
|
|
171
|
+
},
|
|
172
|
+
user_id: order.customer_id
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def process_inventory(order)
|
|
179
|
+
# Custom performance tracking
|
|
180
|
+
ActiveRabbit::Client.track_performance(
|
|
181
|
+
'inventory_processing',
|
|
182
|
+
measure_time { update_inventory_levels(order) },
|
|
183
|
+
metadata: {
|
|
184
|
+
order_id: order.id,
|
|
185
|
+
items_count: order.items.count
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def measure_time
|
|
191
|
+
start_time = Time.current
|
|
192
|
+
yield
|
|
193
|
+
((Time.current - start_time) * 1000).round(2)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# app/models/order.rb
|
|
198
|
+
class Order < ApplicationRecord
|
|
199
|
+
has_many :items
|
|
200
|
+
belongs_to :customer
|
|
201
|
+
|
|
202
|
+
after_create :track_creation
|
|
203
|
+
after_update :track_status_changes
|
|
204
|
+
|
|
205
|
+
private
|
|
206
|
+
|
|
207
|
+
def track_creation
|
|
208
|
+
ActiveRabbit::Client.track_event(
|
|
209
|
+
'model_order_created',
|
|
210
|
+
{
|
|
211
|
+
id: id,
|
|
212
|
+
customer_id: customer_id,
|
|
213
|
+
total_amount: total_amount
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def track_status_changes
|
|
219
|
+
if saved_change_to_status?
|
|
220
|
+
ActiveRabbit::Client.track_event(
|
|
221
|
+
'order_status_changed',
|
|
222
|
+
{
|
|
223
|
+
order_id: id,
|
|
224
|
+
from_status: status_before_last_save,
|
|
225
|
+
to_status: status,
|
|
226
|
+
customer_id: customer_id
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# config/environments/production.rb
|
|
234
|
+
Rails.application.configure do
|
|
235
|
+
# ... other configuration ...
|
|
236
|
+
|
|
237
|
+
# Ensure ActiveRabbit client shuts down gracefully
|
|
238
|
+
config.after_initialize do
|
|
239
|
+
at_exit do
|
|
240
|
+
ActiveRabbit::Client.shutdown if ActiveRabbit::Client.configured?
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|