ez_logs_agent 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.
data/README.md ADDED
@@ -0,0 +1,1021 @@
1
+ # EZLogs Agent
2
+
3
+ **Drop-in activity logging for Rails applications.**
4
+
5
+ EZLogs Agent captures what happens in your Rails app — HTTP requests, background jobs, database changes — and sends them to the EZLogs server, where they're transformed into human-readable stories that anyone on your team can understand.
6
+
7
+ ---
8
+
9
+ ## The Problem
10
+
11
+ When someone clicks "Reset Password" in your app, a cascade of events unfolds:
12
+ - HTTP requests hit your server
13
+ - Database records get updated
14
+ - Background jobs are queued
15
+ - Emails are sent
16
+ - Sessions are invalidated
17
+
18
+ Right now, understanding what actually happened requires:
19
+ - Opening multiple tools (application logs, Sidekiq dashboard, database console)
20
+ - Piecing together timestamps across different systems
21
+ - Reading cryptic stack traces and technical jargon
22
+ - Having an engineer translate everything into plain English
23
+
24
+ **EZLogs solves this.**
25
+
26
+ Instead of scattered technical logs, you see a complete story:
27
+
28
+ ```
29
+ Password Reset — jessica@example.com
30
+ 2:23 PM, January 15, 2025
31
+
32
+ ✓ Reset link created (expires in 1 hour)
33
+ ✓ Email sent to jessica@example.com
34
+ ✓ All active sessions logged out (2 devices)
35
+ ✗ Push notification failed (user has notifications disabled)
36
+
37
+ Status: Completed successfully
38
+ ```
39
+
40
+ Your support team can read that. Your product manager can read that. Your CEO can read that. **No engineer required.**
41
+
42
+ ---
43
+
44
+ ## What EZLogs Is (and Is NOT)
45
+
46
+ ### EZLogs Is:
47
+
48
+ - **An application-level activity log** — Shows what happened in your system in business terms
49
+ - **A bridge between technical systems and business understanding** — Translates technical events into human language
50
+ - **Best-effort and non-blocking** — Never impacts your application's performance or reliability
51
+ - **Safe to run in production** — Designed to fail gracefully if anything goes wrong
52
+
53
+ ### EZLogs Is NOT:
54
+
55
+ - **A monitoring tool** — Use Datadog, New Relic, or CloudWatch for performance monitoring
56
+ - **A metrics platform** — Use your existing APM for request rates, response times, etc.
57
+ - **An audit log** — Use PaperTrail or Audited for compliance and legal requirements
58
+ - **A guaranteed delivery system** — Events may be dropped if the server is unreachable (this is intentional)
59
+ - **A replacement for debugging tools** — Use Sentry, Bugsnag, or your IDE debugger for code-level debugging
60
+
61
+ **EZLogs complements your existing tools by focusing on business understanding, not technical signals.**
62
+
63
+ ---
64
+
65
+ ## Installation
66
+
67
+ ### Step 1: Add the Gem
68
+
69
+ Add to your `Gemfile`:
70
+
71
+ ```ruby
72
+ gem 'ez_logs_agent'
73
+ ```
74
+
75
+ Then install:
76
+
77
+ ```bash
78
+ bundle install
79
+ ```
80
+
81
+ ### Step 2: Run the Generator
82
+
83
+ ```bash
84
+ rails generate ez_logs_agent:install
85
+ ```
86
+
87
+ This creates `config/initializers/ez_logs_agent.rb` with all available configuration options and helpful comments.
88
+
89
+ ### Requirements
90
+
91
+ - **Ruby** >= 3.1.0
92
+ - **Rails** (any recent version)
93
+ - **Sidekiq** (optional, auto-detected if present)
94
+ - **ActiveRecord** (optional, auto-detected if present)
95
+
96
+ ---
97
+
98
+ ## Quick Start
99
+
100
+ ### 1. Get Your API Key
101
+
102
+ Sign up for EZLogs at [app.ezlogs.io](https://app.ezlogs.io) and create an API key from your dashboard settings.
103
+
104
+ ### 2. Configure the Agent
105
+
106
+ Edit `config/initializers/ez_logs_agent.rb`:
107
+
108
+ ```ruby
109
+ EzLogsAgent.configure do |config|
110
+ # Required: Your EZLogs server URL
111
+ config.server_url = "https://app.ezlogs.io"
112
+
113
+ # Required: Your API key from the EZLogs dashboard
114
+ config.project_token = "ezl_your_api_key_here"
115
+ end
116
+ ```
117
+
118
+ **That's it.** The agent handles everything else automatically:
119
+ - No middleware registration needed
120
+ - No Sidekiq configuration required
121
+ - No ActiveRecord setup necessary
122
+
123
+ The agent orchestrates itself via a Rails Railtie. When your app boots, event capture begins automatically.
124
+
125
+ ### 3. Verify It's Working
126
+
127
+ Test your configuration:
128
+
129
+ ```bash
130
+ rails ez_logs_agent:test_connection
131
+ ```
132
+
133
+ **Successful output:**
134
+ ```
135
+ ✅ Configuration is valid
136
+ ✅ Connection successful (HTTP 200)
137
+ ✅ Test event sent successfully
138
+ ✅ All checks passed! EZLogs Agent is configured correctly.
139
+ ```
140
+
141
+ If the test fails, you'll see exactly what's wrong and how to fix it.
142
+
143
+ ### 4. Restart Your Application
144
+
145
+ ```bash
146
+ # Development
147
+ rails server
148
+
149
+ # Production
150
+ # Restart your application using your deployment process
151
+ ```
152
+
153
+ Check your Rails logs for the startup message:
154
+
155
+ ```
156
+ [EzLogsAgent] Agent initialized successfully
157
+ [EzLogsAgent] ✓ HTTP capture enabled
158
+ [EzLogsAgent] ✓ Sidekiq capture enabled
159
+ [EzLogsAgent] ✓ Database capture enabled
160
+ ```
161
+
162
+ ### 5. See Your Activity
163
+
164
+ Visit your EZLogs dashboard and interact with your application. Within seconds, you'll see activity appearing in real-time.
165
+
166
+ ---
167
+
168
+ ## What Gets Captured
169
+
170
+ EZLogs Agent captures three primary event sources. These are the building blocks that EZLogs Server uses to construct complete activity stories.
171
+
172
+ ### 1. HTTP Requests
173
+
174
+ Every incoming HTTP request, with intelligent noise filtering:
175
+
176
+ **What's captured:**
177
+ - HTTP method, path, status code, duration
178
+ - Controller and action name (for Rails apps)
179
+ - GraphQL operation name and type (queries, mutations, subscriptions)
180
+ - Request parameters (sanitized automatically)
181
+ - Correlation ID (generated automatically)
182
+
183
+ **Automatic exclusions (no configuration needed):**
184
+ - `/rails/active_storage*` — File uploads/downloads
185
+ - `/assets*`, `/packs*`, `/vite*` — Static assets (JavaScript, CSS, images)
186
+ - `/health*`, `/up`, `/alive`, `/ready`, `/metrics` — Health checks + ops endpoints
187
+ - `/favicon.ico`, `/.well-known*`, `/robots.txt`, `/sitemap.xml` — Crawler / browser plumbing
188
+ - `/cable*` — ActionCable WebSocket connections
189
+ - `/sidekiq`, `/sidekiq/*` — Sidekiq Web UI (auto-polls every few seconds)
190
+ - `*/sign_in*`, `*/sign_out*`, `*/login*`, `*/logout*`, `/users/password*`, `/session*` — Auth pages (Devise + common patterns)
191
+
192
+ **Note on GraphQL:** All GraphQL operations (queries, mutations, subscriptions) are captured. The server classifies queries as "background" significance, allowing users to toggle their visibility in the UI since they're typically read-only operations.
193
+
194
+ ### 2. Background Jobs
195
+
196
+ Sidekiq and ActiveJob executions:
197
+
198
+ **What's captured:**
199
+ - Job class name, queue name, duration
200
+ - Success or failure status with error message (if failed)
201
+ - Correlation ID (automatically inherited from the request that enqueued the job)
202
+ - Job arguments (sanitized automatically)
203
+
204
+ **Automatic exclusions (no configuration needed):**
205
+ - `SidekiqAlive::Worker` — Sidekiq liveness probe
206
+ - `SolidQueue::CleanupJob` — SolidQueue maintenance job
207
+ - `SolidQueue::RecurringJob` — SolidQueue scheduler internals
208
+
209
+ **Supported job systems:**
210
+ - Sidekiq (fully supported)
211
+ - ActiveJob with any backend (fully supported)
212
+
213
+ ### 3. Database Changes
214
+
215
+ ActiveRecord create, update, and destroy operations:
216
+
217
+ **What's captured:**
218
+ - Model class name, record ID, operation type (create/update/destroy)
219
+ - For creates: initial attribute values
220
+ - For updates: one meaningful attribute change (e.g., `status: pending → shipped`)
221
+ - For destroys: final attribute values before deletion
222
+ - Correlation ID (automatically inherited from the current request or job)
223
+
224
+ **What's NOT captured:**
225
+ - SELECT queries (read operations don't change data)
226
+ - Schema migrations (Rails internal operations)
227
+ - Bulk operations (e.g., `update_all`, `delete_all`)
228
+
229
+ **Automatic exclusions (no configuration needed):**
230
+ - `sessions` — Session store updates
231
+ - `schema_migrations`, `ar_internal_metadata` — Rails schema management
232
+ - `active_storage_*` — ActiveStorage internal tables
233
+ - `solid_queue_*`, `solid_cache_*`, `solid_cable_*` — Solid* gem internals
234
+
235
+ ---
236
+
237
+ ## How Correlation Works
238
+
239
+ Events are automatically linked together using a `correlation_id`, allowing EZLogs to reconstruct the complete chain of events triggered by a single user action.
240
+
241
+ ### Automatic Propagation
242
+
243
+ ```
244
+ HTTP Request
245
+ └─ generates correlation_id: "req_abc123"
246
+ └─ Database Update (inherits: "req_abc123")
247
+ └─ Background Job #1 (inherits: "req_abc123")
248
+ └─ Database Update (inherits: "req_abc123")
249
+ └─ Background Job #2 (inherits: "req_abc123")
250
+ ```
251
+
252
+ **Zero configuration required.** The agent handles correlation propagation automatically through:
253
+ - Rack request headers
254
+ - Sidekiq job metadata
255
+ - Thread-local storage (within a single request)
256
+
257
+ ### Best-Effort Approach
258
+
259
+ **Important:** Correlation is best-effort, not guaranteed. Some events may have missing correlation IDs in edge cases:
260
+ - Jobs triggered by cron or external systems
261
+ - Console operations
262
+ - Database callbacks outside request/job context
263
+
264
+ **This is expected and acceptable.** EZLogs will still capture these events—they'll just appear as separate, uncorrelated activities.
265
+
266
+ **Design principle:** Missing data is acceptable; wrong data is not. The agent never guesses correlation IDs.
267
+
268
+ ---
269
+
270
+ ## Configuration Reference
271
+
272
+ All options have sensible defaults. **Only `server_url` and `project_token` are required.**
273
+
274
+ ### Minimal Configuration
275
+
276
+ ```ruby
277
+ EzLogsAgent.configure do |config|
278
+ config.server_url = "https://app.ezlogs.io"
279
+ config.project_token = "ezl_your_api_key_here"
280
+ end
281
+ ```
282
+
283
+ ### Full Configuration
284
+
285
+ ```ruby
286
+ EzLogsAgent.configure do |config|
287
+ # ==========================================
288
+ # Required Settings
289
+ # ==========================================
290
+
291
+ # Server URL (where to send events)
292
+ config.server_url = "https://app.ezlogs.io"
293
+
294
+ # API Key (get this from your EZLogs dashboard under Settings > API Keys)
295
+ # This is sent as a Bearer token in the Authorization header
296
+ config.project_token = "ezl_your_api_key_here"
297
+
298
+ # ==========================================
299
+ # Event Capture Toggles
300
+ # ==========================================
301
+
302
+ # Capture HTTP requests (default: true)
303
+ config.capture_http = true
304
+
305
+ # Capture background jobs (default: true)
306
+ config.capture_jobs = true
307
+
308
+ # Capture database changes (default: true)
309
+ config.capture_database = true
310
+
311
+ # ==========================================
312
+ # Exclusion Lists
313
+ # ==========================================
314
+
315
+ # Additional HTTP paths to exclude
316
+ # Use * suffix for prefix matching (e.g., "/admin*" matches "/admin/users")
317
+ # These are ADDED to the built-in defaults (health checks, assets, etc.)
318
+ config.excluded_paths = ["/admin*", "/internal*", "/api/internal*"]
319
+
320
+ # Additional database tables to exclude
321
+ # These are ADDED to the built-in defaults (sessions, schema_migrations, etc.)
322
+ config.excluded_tables = ["audit_logs", "versions", "paper_trail_versions"]
323
+
324
+ # Additional job classes to exclude
325
+ # These are ADDED to the built-in defaults (Sidekiq health checks, etc.)
326
+ config.excluded_job_classes = ["MyApp::HealthCheckJob", "MyApp::MetricsJob"]
327
+
328
+ # ==========================================
329
+ # Display Names
330
+ # ==========================================
331
+
332
+ # Configure how to display human-readable names for database records
333
+ # This affects how action titles appear (e.g., "User updated 'john@example.com'")
334
+ # See "Display Names" section below for details
335
+ config.display_name_for = {
336
+ "User" => :email, # Use the email field for User models
337
+ "Product" => :name, # Use the name field for Product models
338
+ "Order" => :number # Use the number field for Order models
339
+ }
340
+
341
+ # ==========================================
342
+ # Actor Context
343
+ # ==========================================
344
+
345
+ # Configure how to extract the "who" (actor) from requests
346
+ # This is opt-in and highly application-specific
347
+ # See "Actor Context" section below for details
348
+ config.actor_from_request = ->(env, controller) {
349
+ return nil unless controller.respond_to?(:current_user)
350
+ user = controller.current_user
351
+ return nil unless user
352
+
353
+ {
354
+ id: user.id.to_s, # Stable identifier
355
+ label: user.email # Human-readable display (optional)
356
+ }
357
+ }
358
+
359
+ # ==========================================
360
+ # Transport Settings
361
+ # ==========================================
362
+
363
+ # Maximum events in memory buffer before oldest are dropped
364
+ # Default: 10000 (approximately 1-2MB of memory)
365
+ # Increased for high-volume workloads with many background jobs
366
+ config.buffer_size = 10000
367
+
368
+ # Number of retry attempts for failed sends (with exponential backoff)
369
+ # Default: 3 (retries at 1s, 2s, 4s)
370
+ config.retry_attempts = 3
371
+
372
+ # Seconds between automatic buffer flushes
373
+ # Default: 3 (events are sent every 3 seconds if buffer has data)
374
+ # More frequent sends improve throughput for high-volume applications
375
+ config.send_interval = 3
376
+
377
+ # ==========================================
378
+ # Logging
379
+ # ==========================================
380
+
381
+ # Agent log level
382
+ # Options: :debug, :info, :warn, :error
383
+ # Default: :warn (quiet by default; agent only logs warnings + errors)
384
+ # Set to :debug for verbose output during troubleshooting
385
+ config.log_level = :warn
386
+ end
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Display Names — Human-Readable Resource Identifiers
392
+
393
+ By default, action titles show database record IDs: `"User updated #123"`. With display name resolution, you can show meaningful identifiers instead: `"User updated 'john@example.com'"`.
394
+
395
+ ### Configuration
396
+
397
+ ```ruby
398
+ EzLogsAgent.configure do |config|
399
+ config.display_name_for = {
400
+ "User" => :email, # "User created 'john@example.com'"
401
+ "Product" => :name, # "Product updated 'Premium Widget'"
402
+ "Order" => :number # "Order deleted '#ORD-1234'"
403
+ }
404
+ end
405
+ ```
406
+
407
+ ### How It Works
408
+
409
+ When a database callback fires (create, update, delete), the agent resolves a display name:
410
+
411
+ 1. **If configured:** Use the specified field (e.g., `User → :email`)
412
+ 2. **Otherwise, try defaults:** `name` → `title` → `number` (in that order)
413
+ 3. **If nothing found:** Fall back to `#id` (e.g., `#123`)
414
+
415
+ ### Examples
416
+
417
+ **Before:**
418
+ ```
419
+ User created
420
+ Product updated #456
421
+ Order deleted #789
422
+ ```
423
+
424
+ **After:**
425
+ ```
426
+ User created 'jessica@example.com'
427
+ Product updated 'Premium Subscription Plan'
428
+ Order deleted '#ORD-2025-0789'
429
+ ```
430
+
431
+ ### Important Constraints
432
+
433
+ **Only use direct attributes, not associations.**
434
+
435
+ ```ruby
436
+ # ✅ GOOD - Direct attribute
437
+ config.display_name_for = { "User" => :email }
438
+
439
+ # ❌ BAD - Association (triggers database query)
440
+ config.display_name_for = { "Order" => :customer_email }
441
+ ```
442
+
443
+ **Why?** Associations would trigger additional database queries during event capture, violating the agent's non-blocking guarantee. The display name is resolved using only data already loaded in memory.
444
+
445
+ ---
446
+
447
+ ## Actor Context — Who Triggered This?
448
+
449
+ EZLogs can track **who** triggered each action. This adds a human face to your activity log.
450
+
451
+ ### Configuration
452
+
453
+ ```ruby
454
+ EzLogsAgent.configure do |config|
455
+ config.actor_from_request = ->(env, controller) {
456
+ # Return nil if controller not available or user not authenticated
457
+ return nil unless controller.respond_to?(:current_user)
458
+ user = controller.current_user
459
+ return nil unless user
460
+
461
+ # Return actor hash
462
+ {
463
+ id: user.id.to_s, # Required: stable identifier
464
+ label: user.email # Optional: human-readable display
465
+ }
466
+ }
467
+ end
468
+ ```
469
+
470
+ ### Hook Parameters
471
+
472
+ The hook receives two arguments:
473
+
474
+ - **`env`** (Hash) — Rack environment hash (always present)
475
+ - **`controller`** (Object or nil) — Rails controller instance (nil if not available, e.g., for API-only requests)
476
+
477
+ ### Return Value
478
+
479
+ Return one of:
480
+ - **`{ id: String, label: String }`** — For identified actors (label is optional)
481
+ - **`nil`** — When actor cannot be determined
482
+
483
+ ### Schema
484
+
485
+ | Field | Type | Required | Description |
486
+ |-------|------|----------|-------------|
487
+ | `id` | String | Yes | Stable identifier (e.g., user ID, never changes) |
488
+ | `label` | String | No | Human-readable display (e.g., email, can change) |
489
+
490
+ ### Design Philosophy
491
+
492
+ **Actor extraction is opt-in, not automatic.** This prevents:
493
+ - Incorrect attribution in impersonation scenarios (admin acting as another user)
494
+ - Wrong actors with service accounts or background jobs
495
+ - Silent failures with custom authentication systems (Devise, Clearance, Authlogic, custom auth)
496
+
497
+ When actor is unknown, events are captured with `actor: null`. **Missing data is acceptable; wrong data is not.**
498
+
499
+ ### Examples
500
+
501
+ **With Devise:**
502
+ ```ruby
503
+ config.actor_from_request = ->(env, controller) {
504
+ return nil unless controller.respond_to?(:current_user)
505
+ user = controller.current_user
506
+ return nil unless user
507
+
508
+ { id: user.id.to_s, label: user.email }
509
+ }
510
+ ```
511
+
512
+ **With Clearance:**
513
+ ```ruby
514
+ config.actor_from_request = ->(env, controller) {
515
+ return nil unless controller.respond_to?(:current_user)
516
+ user = controller.current_user
517
+ return nil unless user
518
+
519
+ { id: user.id.to_s, label: user.email }
520
+ }
521
+ ```
522
+
523
+ **With Custom Auth:**
524
+ ```ruby
525
+ config.actor_from_request = ->(env, controller) {
526
+ # Extract from session
527
+ user_id = env["rack.session"]&.dig("user_id")
528
+ return nil unless user_id
529
+
530
+ # Lookup user (only if you have fast caching)
531
+ user = User.find_by(id: user_id)
532
+ return nil unless user
533
+
534
+ { id: user.id.to_s, label: user.email }
535
+ }
536
+ ```
537
+
538
+ ---
539
+
540
+ ## Safety Guarantees
541
+
542
+ The agent is designed to be **invisible** to your application. It will never be the reason your app fails.
543
+
544
+ ### Non-Blocking Operation
545
+
546
+ - **Never raises exceptions** to the host application
547
+ - **Never blocks** HTTP requests or background jobs
548
+ - **Sends asynchronously** via a background thread
549
+ - **Fails gracefully** if the server is unreachable
550
+
551
+ ### Buffer Overflow Protection
552
+
553
+ - **Drops oldest events** when buffer is full (controlled by `buffer_size`)
554
+ - **Never crashes** from memory pressure
555
+ - **Logs warnings** when buffer approaches capacity
556
+
557
+ ### Network Failure Handling
558
+
559
+ - **Retries with exponential backoff** (controlled by `retry_attempts`)
560
+ - **Gives up gracefully** after max retries
561
+ - **Your application continues normally** if EZLogs Server is down
562
+
563
+ ### Graceful Shutdown
564
+
565
+ - **Flushes remaining events** when Rails shuts down
566
+ - **Waits briefly** for final send (non-blocking)
567
+ - **Never prevents** application shutdown
568
+
569
+ **Design principle:** Your application's reliability is more important than capturing every event. EZLogs is best-effort, not guaranteed delivery.
570
+
571
+ ---
572
+
573
+ ## Testing Your Configuration
574
+
575
+ After installation, verify everything is working:
576
+
577
+ ```bash
578
+ rails ez_logs_agent:test_connection
579
+ ```
580
+
581
+ ### What This Command Does
582
+
583
+ 1. ✅ Validates your configuration (required fields, valid URLs, etc.)
584
+ 2. ✅ Tests connectivity to the EZLogs server
585
+ 3. ✅ Sends a test event
586
+ 4. ✅ Confirms the server accepted it (HTTP 200 response)
587
+
588
+ ### Successful Output
589
+
590
+ ```
591
+ [EzLogsAgent] Testing connection to https://app.ezlogs.io...
592
+ ✅ Configuration is valid
593
+ ✅ Connection successful (HTTP 200)
594
+ ✅ Test event sent successfully
595
+ ✅ All checks passed! EZLogs Agent is configured correctly.
596
+
597
+ Next steps:
598
+ 1. Restart your Rails application
599
+ 2. Visit your EZLogs dashboard
600
+ 3. Interact with your application to generate events
601
+ ```
602
+
603
+ ### Failed Output
604
+
605
+ If the test fails, you'll see exactly what's wrong:
606
+
607
+ ```
608
+ [EzLogsAgent] Testing connection to https://app.ezlogs.io...
609
+ ❌ Connection failed (HTTP 401 Unauthorized)
610
+
611
+ Possible causes:
612
+ - Invalid API key (project_token)
613
+ - API key has been revoked
614
+ - Check your project_token in config/initializers/ez_logs_agent.rb
615
+
616
+ Please fix the error and run this command again.
617
+ ```
618
+
619
+ ---
620
+
621
+ ## Troubleshooting
622
+
623
+ ### Quick Diagnosis
624
+
625
+ **Start here.** This command catches 90% of configuration issues immediately:
626
+
627
+ ```bash
628
+ rails ez_logs_agent:test_connection
629
+ ```
630
+
631
+ ---
632
+
633
+ ### No Events Showing Up
634
+
635
+ **Symptoms:** Your EZLogs dashboard is empty after restarting your application.
636
+
637
+ **Debug steps:**
638
+
639
+ 1. **Run the connection test:**
640
+ ```bash
641
+ rails ez_logs_agent:test_connection
642
+ ```
643
+
644
+ 2. **Check Rails logs** for agent messages:
645
+ ```
646
+ [EzLogsAgent] Agent initialized successfully
647
+ [EzLogsAgent] ✓ HTTP capture enabled
648
+ [EzLogsAgent] Sending batch of 3 events...
649
+ [EzLogsAgent] Batch sent successfully (HTTP 200)
650
+ ```
651
+
652
+ 3. **Enable debug logging** in `config/initializers/ez_logs_agent.rb`:
653
+ ```ruby
654
+ config.log_level = :debug
655
+ ```
656
+ Then restart Rails and check logs for detailed capture information.
657
+
658
+ 4. **Verify network connectivity:**
659
+ ```bash
660
+ curl -I https://app.ezlogs.io
661
+ ```
662
+
663
+ 5. **Check firewall rules:** Ensure your application can reach the EZLogs server on port 443 (HTTPS).
664
+
665
+ ---
666
+
667
+ ### Configuration Validation Errors at Boot
668
+
669
+ **Symptoms:** Rails starts but shows warnings from EZLogs Agent.
670
+
671
+ **Example output:**
672
+ ```
673
+ [Railtie] Configuration validation failed:
674
+ - server_url is required. Set it in config/initializers/ez_logs_agent.rb
675
+ [Railtie] Agent initialization skipped. Please fix configuration errors.
676
+ ```
677
+
678
+ **Solution:** Fix the errors listed in the warning message and restart Rails.
679
+
680
+ Common validation errors:
681
+ - `server_url is required` → Set `config.server_url`
682
+ - `project_token is not set` → Set `config.project_token`
683
+ - `server_url must start with http:// or https://` → Fix URL format
684
+
685
+ ---
686
+
687
+ ### Authentication Errors (HTTP 401)
688
+
689
+ **Symptoms:** Connection test or agent logs show `HTTP 401 Unauthorized`.
690
+
691
+ **Debug steps:**
692
+
693
+ 1. **Verify your API key:**
694
+ - Log into your EZLogs dashboard
695
+ - Go to Settings > API Keys
696
+ - Copy the active API key
697
+ - Paste it into `config.project_token` (include the `ezl_` prefix)
698
+
699
+ 2. **Check for extra characters:**
700
+ ```ruby
701
+ # ❌ BAD - Extra quotes or spaces
702
+ config.project_token = " ezl_abc123 "
703
+
704
+ # ✅ GOOD
705
+ config.project_token = "ezl_abc123"
706
+ ```
707
+
708
+ 3. **Ensure the API key is active:**
709
+ - Check the EZLogs dashboard to confirm the key hasn't been revoked
710
+ - If revoked, create a new key and update your configuration
711
+
712
+ ---
713
+
714
+ ### Sidekiq Jobs Not Captured
715
+
716
+ **Symptoms:** HTTP requests appear in EZLogs but background jobs don't.
717
+
718
+ **Debug steps:**
719
+
720
+ 1. **Verify Sidekiq is running:**
721
+ ```bash
722
+ # Should show running Sidekiq processes
723
+ ps aux | grep sidekiq
724
+ ```
725
+
726
+ 2. **Check agent configuration:**
727
+ ```ruby
728
+ # Ensure jobs capture is enabled (this is the default)
729
+ config.capture_jobs = true
730
+ ```
731
+
732
+ 3. **Look for Sidekiq registration in logs:**
733
+ ```
734
+ [Railtie] Sidekiq server middleware registered
735
+ ```
736
+ **Note:** This message only appears in Sidekiq worker processes, not web processes.
737
+
738
+ 4. **Verify job classes aren't excluded:**
739
+ Check `config.excluded_job_classes` to ensure your jobs aren't being filtered out.
740
+
741
+ 5. **Run a test job:**
742
+ ```ruby
743
+ # In Rails console
744
+ class TestJob < ApplicationJob
745
+ def perform
746
+ Rails.logger.info "Test job executed"
747
+ end
748
+ end
749
+
750
+ TestJob.perform_later
751
+ ```
752
+ Check your EZLogs dashboard for the job execution.
753
+
754
+ ---
755
+
756
+ ### Database Events Missing
757
+
758
+ **Symptoms:** HTTP requests and jobs appear, but database changes don't.
759
+
760
+ **Debug steps:**
761
+
762
+ 1. **Verify ActiveRecord is present:**
763
+ ```bash
764
+ # In Rails console
765
+ defined?(ActiveRecord) # Should return "constant"
766
+ ```
767
+
768
+ 2. **Check agent configuration:**
769
+ ```ruby
770
+ # Ensure database capture is enabled (this is the default)
771
+ config.capture_database = true
772
+ ```
773
+
774
+ 3. **Look for database capture registration in logs:**
775
+ ```
776
+ [Railtie] Database capture installed
777
+ ```
778
+
779
+ 4. **Verify models aren't excluded:**
780
+ Check `config.excluded_tables` to ensure your tables aren't being filtered out.
781
+
782
+ 5. **Remember: Only create/update/destroy are captured:**
783
+ - ✅ `User.create(...)` — Captured
784
+ - ✅ `user.update(...)` — Captured
785
+ - ✅ `user.destroy` — Captured
786
+ - ❌ `User.find(...)` — NOT captured (read-only)
787
+ - ❌ `User.update_all(...)` — NOT captured (bulk operation)
788
+
789
+ ---
790
+
791
+ ### Events Appearing Late
792
+
793
+ **Symptoms:** Events show up in your dashboard with a delay.
794
+
795
+ **This is normal.** Events are sent in batches every 3 seconds by default.
796
+
797
+ **To reduce latency further** (at the cost of more network requests):
798
+ ```ruby
799
+ # Send events every 1-2 seconds instead of 3
800
+ config.send_interval = 1
801
+ ```
802
+
803
+ **Note:** Even with `send_interval = 2`, there's still processing time on the server. Real-time is not guaranteed.
804
+
805
+ ---
806
+
807
+ ### Correlation IDs Missing
808
+
809
+ **Symptoms:** Events appear as separate activities instead of being grouped together.
810
+
811
+ **This is expected in some scenarios:**
812
+ - Jobs triggered by cron or external systems
813
+ - Console operations (`rails console`)
814
+ - Database callbacks outside request/job context
815
+ - Cross-process job chains (e.g., Job A in Process 1 enqueues Job B in Process 2)
816
+
817
+ **This is normal and acceptable.** Correlation is best-effort, not guaranteed.
818
+
819
+ **If correlation is missing when it should be present:**
820
+ 1. Enable debug logging: `config.log_level = :debug`
821
+ 2. Look for correlation_id in logs: `[EzLogsAgent] Captured HTTP event with correlation_id: req_abc123`
822
+ 3. Check that jobs are enqueued within the same request context
823
+
824
+ ---
825
+
826
+ ### Performance Impact
827
+
828
+ **Symptoms:** Concerned about memory usage or application performance.
829
+
830
+ **The agent is designed to be lightweight:**
831
+ - **Memory:** ~1-2MB for default buffer (10,000 events)
832
+ - **CPU:** Negligible (background thread does all work)
833
+ - **Latency:** Zero added to requests (capture is asynchronous)
834
+
835
+ **If you experience issues:**
836
+
837
+ 1. **Reduce buffer size** (for low-volume apps):
838
+ ```ruby
839
+ # Reduce from 10000 to 5000 events
840
+ config.buffer_size = 5000
841
+ ```
842
+
843
+ 2. **Increase send interval** (events sent less frequently):
844
+ ```ruby
845
+ # Send every 5-10 seconds instead of 3
846
+ config.send_interval = 5
847
+ ```
848
+
849
+ 3. **Disable specific capture types:**
850
+ ```ruby
851
+ # Database changes can be noisy in write-heavy apps
852
+ config.capture_database = false
853
+ ```
854
+
855
+ ---
856
+
857
+ ### Understanding Agent Log Messages
858
+
859
+ **Normal operation:**
860
+ ```
861
+ [EzLogsAgent] Agent initialized successfully
862
+ [EzLogsAgent] Sending batch of 12 events...
863
+ [EzLogsAgent] Batch sent successfully (HTTP 200)
864
+ ```
865
+
866
+ **Warnings (usually safe to ignore):**
867
+ ```
868
+ [EzLogsAgent] Buffer full, dropping oldest events
869
+ → Your app is generating more events than can be sent
870
+ → Increase buffer_size or send_interval
871
+ ```
872
+
873
+ **Errors (need attention):**
874
+ ```
875
+ [EzLogsAgent] Failed to send events (HTTP 401)
876
+ → Invalid API key, check config.project_token
877
+
878
+ [EzLogsAgent] Failed to send events (timeout)
879
+ → Network connectivity issue or server is down
880
+ → Events will be retried automatically
881
+
882
+ [EzLogsAgent] Configuration validation failed
883
+ → Fix configuration errors and restart Rails
884
+ ```
885
+
886
+ ---
887
+
888
+ ### Getting Help
889
+
890
+ If you're still stuck after trying the above:
891
+
892
+ 1. **Check GitHub Issues:** [github.com/dezsirazvan/ez_logs_agent/issues](https://github.com/dezsirazvan/ez_logs_agent/issues)
893
+ 2. **Open a new issue** with:
894
+ - Rails version and Ruby version
895
+ - Relevant logs (set `config.log_level = :debug`)
896
+ - Output of `rails ez_logs_agent:test_connection`
897
+ - Steps to reproduce the problem
898
+
899
+ ---
900
+
901
+ ## How It Works
902
+
903
+ A visual overview of the agent's architecture:
904
+
905
+ ```
906
+ Your Rails Application
907
+
908
+ ├─ HTTP Request arrives
909
+ │ └─ Rack middleware captures event
910
+ │ └─ Adds to Buffer
911
+
912
+ ├─ Background Job runs
913
+ │ └─ Sidekiq middleware captures event
914
+ │ └─ Adds to Buffer
915
+
916
+ └─ Database record changes
917
+ └─ ActiveRecord callback captures event
918
+ └─ Adds to Buffer
919
+
920
+
921
+
922
+ ┌───────────────┐
923
+ │ Buffer │ (thread-safe, in-memory, circular)
924
+ │ 10,000 events │
925
+ └───────────────┘
926
+
927
+
928
+ ┌───────────────┐
929
+ │FlushScheduler │ (background thread, every 3s)
930
+ └───────────────┘
931
+
932
+
933
+ ┌───────────────┐
934
+ │ Transport │ (HTTP POST with retry logic)
935
+ │ │ (Authorization: Bearer token)
936
+ └───────────────┘
937
+
938
+
939
+ ┌───────────────┐
940
+ │ EZLogs Server │ (groups events into stories)
941
+ └───────────────┘
942
+ ```
943
+
944
+ **Key points:**
945
+ - Capture happens **synchronously** (microseconds, no blocking)
946
+ - Sending happens **asynchronously** (background thread)
947
+ - Buffer is **circular** (oldest events dropped when full)
948
+ - Transport uses **exponential backoff** (1s, 2s, 4s retries)
949
+
950
+ ---
951
+
952
+ ## Development
953
+
954
+ ### Running Tests
955
+
956
+ ```bash
957
+ cd ez_logs_agent
958
+ bundle install
959
+ bundle exec rspec
960
+ ```
961
+
962
+ ### Test Coverage
963
+
964
+ 786+ tests covering:
965
+ - HTTP request capture
966
+ - GraphQL support
967
+ - Background job capture (Sidekiq + ActiveJob)
968
+ - Database callbacks (create, update, destroy)
969
+ - Correlation propagation
970
+ - Actor context extraction
971
+ - Display name resolution
972
+ - Buffer overflow handling
973
+ - Transport retry logic
974
+ - Configuration validation
975
+
976
+ ---
977
+
978
+ ## License
979
+
980
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
981
+
982
+ ---
983
+
984
+ ## Contributing
985
+
986
+ We welcome bug reports and pull requests!
987
+
988
+ **To report a bug:**
989
+ 1. Check existing [GitHub Issues](https://github.com/dezsirazvan/ez_logs_agent/issues)
990
+ 2. Open a new issue with reproduction steps
991
+
992
+ **To contribute code:**
993
+ 1. Fork the repository
994
+ 2. Create a feature branch
995
+ 3. Write tests for your changes
996
+ 4. Ensure all tests pass: `bundle exec rspec`
997
+ 5. Submit a pull request with a clear description
998
+
999
+ **Code style:**
1000
+ - Follow the existing Ruby style
1001
+ - Write descriptive commit messages
1002
+ - Update documentation for user-facing changes
1003
+
1004
+ ---
1005
+
1006
+ ## Status
1007
+
1008
+ - **786 tests**, all green
1009
+ - **Wire-format parity-tested** against the Next.js agent's fixtures (every event shape byte-for-byte identical)
1010
+ - **Production-shipping** in multiple Rails apps including the Bookhouse demo (Rails 8 + Sidekiq + Devise) which exercises every capture path. Try it: see the live activity log at [ezlogs.io](https://ezlogs.io)
1011
+
1012
+ ---
1013
+
1014
+ ## Support
1015
+
1016
+ - **GitHub Issues:** [Report a bug](https://github.com/dezsirazvan/ez_logs_agent/issues)
1017
+ - **Email:** support@ezlogs.io
1018
+
1019
+ ---
1020
+
1021
+ **Made with clarity in mind. Built for everyone on your team, not just engineers.**