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