rails_error_dashboard 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +123 -49
- data/app/controllers/rails_error_dashboard/errors_controller.rb +3 -0
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +8 -1
- data/app/models/rails_error_dashboard/error_log.rb +1 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +30 -0
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +5 -0
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +8 -0
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +9 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +51 -1
- data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +1 -1
- data/app/views/rails_error_dashboard/errors/index.html.erb +8 -1
- data/app/views/rails_error_dashboard/errors/overview.html.erb +2 -2
- data/app/views/rails_error_dashboard/errors/settings.html.erb +2 -0
- data/config/locales/de.yml +19 -2
- data/config/locales/en.yml +17 -0
- data/config/locales/es.yml +18 -2
- data/config/locales/fr.yml +19 -2
- data/config/locales/it.yml +27 -3
- data/config/locales/ja.yml +17 -3
- data/config/locales/pl.yml +19 -2
- data/config/locales/pt-BR.yml +18 -2
- data/config/locales/ru.yml +18 -2
- data/config/locales/uk.yml +18 -2
- data/config/locales/zh-CN.yml +15 -3
- data/db/migrate/20260826000001_add_environment_to_error_logs.rb +26 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +11 -0
- data/lib/rails_error_dashboard/commands/backfill_environments.rb +58 -0
- data/lib/rails_error_dashboard/commands/find_or_increment_error.rb +76 -29
- data/lib/rails_error_dashboard/commands/flush_storm_counts.rb +40 -9
- data/lib/rails_error_dashboard/commands/log_error.rb +17 -0
- data/lib/rails_error_dashboard/configuration.rb +52 -0
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +9 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
- data/lib/rails_error_dashboard/queries/filter_options.rb +9 -0
- data/lib/rails_error_dashboard/services/discord_payload_builder.rb +8 -0
- data/lib/rails_error_dashboard/services/error_broadcaster.rb +6 -2
- data/lib/rails_error_dashboard/services/issue_body_formatter.rb +1 -0
- data/lib/rails_error_dashboard/services/markdown_error_formatter.rb +1 -0
- data/lib/rails_error_dashboard/services/notification_throttler.rb +23 -0
- data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +1 -0
- data/lib/rails_error_dashboard/services/slack_payload_builder.rb +9 -0
- data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -0
- data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +1 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +1 -0
- data/lib/tasks/rails_error_dashboard_tasks.rake +6 -0
- metadata +24 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4aa6cdee526bf9210f3762861a373dbefbe99b9ed94ecdb5f6464fb781bdddb4
|
|
4
|
+
data.tar.gz: 3a7977ff26332c869b5bf6e4e5316d3470e978ff736184dcc4119f0982822c4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea46f47e2e96da83cf0c83d94596e63d63ab0dafeea5a3f696b2a1021b9eb57026485c9fec4d0827e8f8fcd4852fd587a69d28d3890ec14a5ec295d549826264
|
|
7
|
+
data.tar.gz: 4ea0ff21bbcb9177fd43297a5145e17a6316ec52f2f8a5370b30e36879a32810cd289b22e7c1d66abb2b9f9a66593bc2e013ab3ccfc19af1c0ffc46c42863537
|
data/README.md
CHANGED
|
@@ -7,27 +7,100 @@
|
|
|
7
7
|
[](https://github.com/sponsors/AnjanJ)
|
|
8
8
|
[](https://buymeacoffee.com/anjanj)
|
|
9
9
|
|
|
10
|
-
**Self-hosted
|
|
10
|
+
**Rails-native error tracking for failure investigation — see the Ruby state and Rails runtime health behind every exception. Self-hosted, inside your app, in your own database. The gem is MIT and free forever.**
|
|
11
11
|
|
|
12
12
|
```ruby
|
|
13
|
-
gem
|
|
13
|
+
gem "rails_error_dashboard"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
```bash
|
|
17
|
+
bundle install
|
|
18
|
+
rails generate rails_error_dashboard:install
|
|
19
|
+
rails db:migrate
|
|
20
|
+
```
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
Open `/red` and raise a test exception. No monitoring account or ingestion service is required.
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
[Try the live demo](https://rails-error-dashboard.anjan.dev) (`gandalf` / `youshallnotpass`) · [Read the documentation](https://anjanj.github.io/rails_error_dashboard/) · [View on RubyGems](https://rubygems.org/gems/rails_error_dashboard)
|
|
25
|
+
|
|
26
|
+
> **Beta:** RED is functional and extensively tested, but configuration and APIs may change before 1.0. Supports Rails 7.0–8.1 and Ruby 3.2–4.0 (CI runs Ruby 3.2–3.4 against every supported Rails version; Ruby 4.0 is verified by the maintainer).
|
|
27
|
+
|
|
28
|
+
## See the Ruby state and Rails runtime health behind every exception
|
|
29
|
+
|
|
30
|
+
Rails Error Dashboard (RED) is an open-source, self-hosted Rails engine for investigating production failures. It helps you answer not only **what failed**, but **what was happening inside Ruby and Rails when it failed**.
|
|
31
|
+
|
|
32
|
+
- Inspect local variables and the raising object's instance variables before the stack unwinds.
|
|
33
|
+
- See error-time Active Record, Puma, job queue, GC, memory and process health.
|
|
34
|
+
- Follow the SQL, cache, controller, job, mailer and other Rails events leading to the exception.
|
|
35
|
+
- Stay safe during error floods with progressive, count-preserving storm protection.
|
|
36
|
+
- Keep exception data on infrastructure you control.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
## The questions RED helps you answer
|
|
41
|
+
|
|
42
|
+
A stack trace tells you where execution stopped. RED helps you investigate the state behind it:
|
|
43
|
+
|
|
44
|
+
- What did `params`, local variables and objects such as `@order` contain?
|
|
45
|
+
- Was the Active Record pool exhausted?
|
|
46
|
+
- Was Puma out of thread capacity or building a backlog?
|
|
47
|
+
- Were jobs failing or queues growing?
|
|
48
|
+
- Was the process under GC, memory, descriptor or system pressure?
|
|
49
|
+
- Which SQL queries, cache operations or Rails events preceded the failure?
|
|
50
|
+
- Did a deploy introduce the error?
|
|
51
|
+
- Can the failing request become a cURL reproduction or RSpec regression-test scaffold?
|
|
52
|
+
|
|
53
|
+
## What makes RED different
|
|
54
|
+
|
|
55
|
+
### Failure-time Ruby state
|
|
56
|
+
|
|
57
|
+
Optionally capture local variables and — something no other error tracker does — the raising receiver's instance variables at `TracePoint(:raise)`, with bounded serialization and your Rails `filter_parameters` applied to sensitive values. Binding objects are never retained.
|
|
58
|
+
|
|
59
|
+
### Failure-time Rails health
|
|
60
|
+
|
|
61
|
+
Attach connection-pool, Puma, background-job, GC, memory, file-descriptor, TCP, RubyVM and YJIT state to the error record, refreshed on every captured occurrence — not merely to a separate periodic metrics chart. Every APM has these as time-series; none attaches them to the error. Opt-in; the procfs-backed fields are Linux-only.
|
|
62
|
+
|
|
63
|
+
### Monitoring that degrades safely
|
|
64
|
+
|
|
65
|
+
During an error flood, RED progressively reduces captured context and database work, keeps a fresh exemplar every minute, records the storm in a Storm History ledger and reconciles exact in-process occurrence counts onto the error records. On by default.
|
|
21
66
|
|
|
22
|
-
###
|
|
67
|
+
### Rails-specific investigation
|
|
23
68
|
|
|
24
|
-
|
|
69
|
+
Connect exceptions with SQL, caching, Active Job, Action Cable, Active Storage, Rack::Attack, deprecations and other Rails subsystems from one dashboard.
|
|
25
70
|
|
|
26
|
-
|
|
71
|
+
### Things no other tracker does
|
|
72
|
+
|
|
73
|
+
Verified against Sentry, Honeybadger, AppSignal, Rollbar, Bugsnag, Airbrake, Raygun, New Relic, Datadog, Scout, Skylight and every self-hosted Rails tracker in August 2026 ([the ledger](.shipkit/research/red-unique-features-verified.md)):
|
|
74
|
+
|
|
75
|
+
- **Copy as RSpec** — a runnable request spec generated from the captured request (Sentry offers curl only).
|
|
76
|
+
- **Swallowed-exception aggregate** — raise-vs-rescue ratio per location, no APM span needed (Datadog's paid APM detects rescued exceptions but keeps no aggregate).
|
|
77
|
+
- **Rack::Attack ledger** — throttle, blocklist and track events persisted with per-rule stats and an AI-crawler classifier; rack-attack ships no UI of its own.
|
|
78
|
+
- **Codeberg issue tracking**, alongside GitHub, GitLab and Linear with two-way sync.
|
|
79
|
+
- **The tracker instruments itself** — its capture pipeline exported as OpenTelemetry spans, so you can audit its overhead in your own APM.
|
|
80
|
+
|
|
81
|
+
## How RED compares
|
|
82
|
+
|
|
83
|
+
| Basic embedded tracker | General SaaS monitoring | RED |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Stack trace and context | Cross-language telemetry and managed ingestion | Deep failure-time Ruby/Rails state inside the application boundary |
|
|
86
|
+
| Lightweight and local | Strong distributed and frontend observability | Rails-specific operational investigation and storm-safe local capture |
|
|
87
|
+
|
|
88
|
+
That makes RED a self-hosted Sentry alternative for teams that want Rails-specific depth and need error data to stay inside the application boundary — not a replacement for cross-language telemetry. RED has no mobile SDKs, no merge/split, no MCP server and no hosted operations.
|
|
89
|
+
|
|
90
|
+
## Choose how you run it
|
|
91
|
+
|
|
92
|
+
- Store data in the application's existing PostgreSQL, MySQL/Trilogy or SQLite database.
|
|
93
|
+
- Isolate monitoring writes in a separate error database.
|
|
94
|
+
- Use synchronous writes, or async logging through Sidekiq or Solid Queue (GoodJob is detected for job-health stats but is not an async adapter).
|
|
95
|
+
- Track several Rails applications through a shared database.
|
|
96
|
+
|
|
97
|
+
No RED licence or event-ingestion fee, and no plan limits — your database is the only cap, and storm protection deliberately sheds context during floods.
|
|
98
|
+
|
|
99
|
+
---
|
|
27
100
|
|
|
28
101
|
### Screenshots
|
|
29
102
|
|
|
30
|
-
**Dashboard Overview** —
|
|
103
|
+
**Dashboard Overview** — Live error stats, severity breakdown, and trend charts.
|
|
31
104
|
|
|
32
105
|

|
|
33
106
|
|
|
@@ -49,25 +122,12 @@ gem 'rails_error_dashboard'
|
|
|
49
122
|
|
|
50
123
|
---
|
|
51
124
|
|
|
52
|
-
##
|
|
125
|
+
## Safety, performance and compatibility
|
|
53
126
|
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
58
|
-
- **Side projects** that might become real businesses
|
|
59
|
-
|
|
60
|
-
## What It Replaces
|
|
61
|
-
|
|
62
|
-
| Before | After |
|
|
63
|
-
|--------|-------|
|
|
64
|
-
| $29-99/month for error monitoring | $0/month — runs on your existing Rails server |
|
|
65
|
-
| Sensitive error data sent to third parties | All data stays on your infrastructure |
|
|
66
|
-
| SaaS pricing tiers and usage limits | Unlimited errors, unlimited projects |
|
|
67
|
-
| Vendor lock-in with proprietary APIs | 100% open source, fully portable |
|
|
68
|
-
| Complex SDK setup and external services | 5-minute Rails Engine installation |
|
|
69
|
-
| Pay extra for local variable capture (Sentry) | Local + instance variables included free |
|
|
70
|
-
| No tool detects silently rescued exceptions | Swallowed exception detection built in |
|
|
127
|
+
- **Host-app safety** — nothing in the capture path raises into your app; every subscriber and callback is rescue-wrapped, `Thread.current` is cleaned up in `ensure`, and the original exception is always re-raised. Variables, health and breadcrumbs are opt-in and off by default; storm protection is on by default and fails open.
|
|
128
|
+
- **Performance** — the storm-protection hot path is a digest plus an atomic increment with no I/O; the figures quoted below are a maintainer's single-machine measurements and no benchmark script ships with the gem yet.
|
|
129
|
+
- **Security** — HTTP Basic Auth or your own `authenticate_with` lambda (Devise, Warden, session); your Rails `filter_parameters` are applied to params, variables and breadcrumbs; prompts are never recorded by LLM observability. Vulnerability reports: [SECURITY.md](SECURITY.md).
|
|
130
|
+
- **Compatibility** — Rails 7.0–8.1, Ruby 3.2–4.0, PostgreSQL, MySQL/Trilogy or SQLite; `turbo-rails` plus ActionCable are needed for live updates (no polling fallback); the gem's own CSS/JS is inline but Bootstrap JS, Chart.js, highlight.js and Google Fonts load from CDNs, so it is not air-gap clean.
|
|
71
131
|
|
|
72
132
|
---
|
|
73
133
|
|
|
@@ -75,14 +135,14 @@ gem 'rails_error_dashboard'
|
|
|
75
135
|
|
|
76
136
|
### Core (Always Enabled)
|
|
77
137
|
|
|
78
|
-
Error capture from controllers, jobs, and middleware. Custom-designed dashboard with dark/light mode, search, filtering, and real-time updates. Analytics with trend charts, severity breakdown, and spike detection. Workflow management with assignment, priority, snooze, mute/unmute (notification suppression), comments, and batch operations. Security via HTTP Basic Auth or custom lambda (Devise, Warden, session-based). Exception cause chains, enriched HTTP context, custom fingerprinting, CurrentAttributes integration, auto-reopen on recurrence, and sensitive data filtering — all built in.
|
|
138
|
+
Error capture from controllers, jobs, and middleware. Custom-designed dashboard with dark/light mode, search, filtering, and real-time updates (the latter with `turbo-rails` + ActionCable in the host). Analytics with trend charts, severity breakdown, and spike detection. Workflow management with assignment, priority, snooze, mute/unmute (notification suppression), comments, and batch operations. Security via HTTP Basic Auth or custom lambda (Devise, Warden, session-based). Exception cause chains, enriched HTTP context, custom fingerprinting, CurrentAttributes integration, auto-reopen on recurrence, and sensitive data filtering — all built in.
|
|
79
139
|
|
|
80
140
|
### Optional Features
|
|
81
141
|
|
|
82
142
|
<details>
|
|
83
143
|
<summary><strong>Storm Protection — Circuit Breaker + Adaptive Sampling</strong></summary>
|
|
84
144
|
|
|
85
|
-
When the error rate spikes (a bad deploy throwing thousands of errors a minute), the nightmare scenario for any in-process tracker is amplifying the outage with its own database writes. Storm protection
|
|
145
|
+
When the error rate spikes (a bad deploy throwing thousands of errors a minute), the nightmare scenario for any in-process tracker is amplifying the outage with its own database writes. Storm protection is designed to **shed the gem's own expensive work first** — ON by default. The behaviour is measured (see Overhead below), though a bundled, reproducible benchmark is still to come.
|
|
86
146
|
|
|
87
147
|
- **Per-fingerprint caps:** past N occurrences/minute per error, context is shed, then rows are sampled deterministically (a fresh exemplar is always kept each minute)
|
|
88
148
|
- **Global circuit breaker:** sustained floods flip the gem to count-only mode — zero per-event I/O, exact in-memory counts reconciled onto error records every 30s. Async mode is gated too (a SolidQueue enqueue is itself a DB write)
|
|
@@ -98,7 +158,7 @@ config.storm_open_threshold_per_second = 50 # per process
|
|
|
98
158
|
|
|
99
159
|
All thresholds are per process and individually configurable. Disable with one flag.
|
|
100
160
|
|
|
101
|
-
**
|
|
161
|
+
**Overhead:** the check is a digest plus an atomic increment; there is no I/O on the hot path. The maintainer's single-machine measurement (Apple Silicon, Ruby 4.0) was 2.4µs/error with protection active and calm, 2.95µs in count-only mode and 0.2µs when disabled, against a 5µs budget — a reproducible benchmark script is not yet part of the gem.
|
|
102
162
|
</details>
|
|
103
163
|
|
|
104
164
|
<details>
|
|
@@ -108,7 +168,7 @@ See exactly what happened before the crash — SQL queries, controller actions,
|
|
|
108
168
|
|
|
109
169
|
- Automatic capture — zero config beyond the enable flag
|
|
110
170
|
- N+1 query detection with aggregate patterns page
|
|
111
|
-
- Deprecation warnings with aggregate view
|
|
171
|
+
- Deprecation warnings with aggregate view (needs the host's deprecation behaviour to include `:notify`; only requests that later raised are seen)
|
|
112
172
|
- Custom breadcrumbs via `RailsErrorDashboard.add_breadcrumb("checkout started", { cart_id: 123 })`
|
|
113
173
|
- Safe by design — fixed-size ring buffer, thread-local, every subscriber wrapped in rescue
|
|
114
174
|
|
|
@@ -146,7 +206,7 @@ Cross-error N+1 detection grouped by SQL fingerprint, and aggregate deprecation
|
|
|
146
206
|
|
|
147
207
|

|
|
148
208
|
|
|
149
|
-
Requires breadcrumbs to be enabled.
|
|
209
|
+
Requires breadcrumbs to be enabled. Deprecations are seen only when the host's `ActiveSupport::Deprecation` behaviour includes `:notify` (the production default does not) and only inside requests that later raised.
|
|
150
210
|
|
|
151
211
|
[Complete documentation →](docs/FEATURES.md#n1-query-detection)
|
|
152
212
|
</details>
|
|
@@ -154,7 +214,7 @@ Requires breadcrumbs to be enabled.
|
|
|
154
214
|
<details>
|
|
155
215
|
<summary><strong>Operational Health Panels — Jobs, Database, Cache, ActionCable</strong></summary>
|
|
156
216
|
|
|
157
|
-
**Job Health** —
|
|
217
|
+
**Job Health** — Aggregates the queue stats captured on each error (Sidekiq, SolidQueue or GoodJob auto-detected; needs `enable_system_health`). Not a live queue view — a per-error table with adapter badge, failed count (color-coded), sorted worst-first.
|
|
158
218
|
|
|
159
219
|

|
|
160
220
|
|
|
@@ -184,12 +244,12 @@ config.enable_activestorage_tracking = true # requires enable_breadcrumbs = tru
|
|
|
184
244
|
<details>
|
|
185
245
|
<summary><strong>LLM Observability — Calls, Tokens, Cost, Tool Use</strong></summary>
|
|
186
246
|
|
|
187
|
-
Capture
|
|
247
|
+
Capture your app's LLM calls — through a Faraday middleware, OpenTelemetry GenAI spans or a manual notification; nothing is auto-instrumented — as breadcrumbs on the error that follows, with model, latency, token counts, estimated USD cost and tool-use requests. When a request crashes, you see the chat completion that preceded it: which model was called, how long it took, what it cost, and which tools it asked to invoke.
|
|
188
248
|
|
|
189
249
|
- Three capture paths — pick whichever matches your stack
|
|
190
250
|
- Cost estimated from a built-in pricing table (Claude 4.x, GPT-4o/o1, Gemini 2.5) — override per-model via `config.llm_pricing_overrides`
|
|
191
251
|
- Tool-call requests summarized inline; tool *execution* spans captured separately via the OTel path
|
|
192
|
-
-
|
|
252
|
+
- Prompts and completions are **never recorded** — only token counts and metadata (the `llm_observability_content_capture` flag is reserved and currently a no-op)
|
|
193
253
|
- Same host-app safety guarantees as the rest of the gem — never raises, never blocks the request, every callback rescue-wrapped
|
|
194
254
|
|
|
195
255
|
```ruby
|
|
@@ -343,7 +403,7 @@ config.enable_git_blame = true
|
|
|
343
403
|
<details>
|
|
344
404
|
<summary><strong>Code Path Coverage (Diagnostic Mode)</strong></summary>
|
|
345
405
|
|
|
346
|
-
Enable coverage via a dashboard button to see which production code paths were executed. Source code viewer overlays green checkmarks on executed lines and gray dots on unexecuted lines. Uses Ruby's `Coverage.setup(oneshot_lines: true)` — near-zero overhead, each line fires once. Zero overhead when off.
|
|
406
|
+
Enable coverage via a dashboard button to see which production code paths were executed. Source code viewer overlays green checkmarks on executed lines and gray dots on unexecuted lines. Uses Ruby's `Coverage.setup(oneshot_lines: true)` — near-zero overhead, each line fires once. Zero overhead when off. Diagnostic mode only: coverage is process-global (a multi-threaded Puma blends requests), held in memory and not persisted. No error tracker integrates this; Coverband does it standalone with persistence.
|
|
347
407
|
|
|
348
408
|
```ruby
|
|
349
409
|
config.enable_coverage_tracking = true # shows Enable/Disable buttons on error detail page
|
|
@@ -372,7 +432,7 @@ config.llm_model = "gpt-5"
|
|
|
372
432
|
<details>
|
|
373
433
|
<summary><strong>Notifications — Slack, Discord, PagerDuty, Email, Webhooks</strong></summary>
|
|
374
434
|
|
|
375
|
-
Multi-channel alerting with severity filters, per-error cooldown,
|
|
435
|
+
Multi-channel alerting with severity filters, per-error cooldown, milestone threshold alerts, and a per-environment allowlist (`config.notification_environments = %w[production]`) so a staging deploy never pages anyone.
|
|
376
436
|
|
|
377
437
|
```ruby
|
|
378
438
|
config.enable_slack_notifications = true
|
|
@@ -382,6 +442,19 @@ config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
|
|
|
382
442
|
[Notification setup guide →](docs/guides/NOTIFICATIONS.md)
|
|
383
443
|
</details>
|
|
384
444
|
|
|
445
|
+
<details>
|
|
446
|
+
<summary><strong>Environment Awareness — Filter, Badge, Notify per Environment</strong></summary>
|
|
447
|
+
|
|
448
|
+
Every error records the environment it came from — `production`, `staging`, `uat`, `preprod`, any name your deploys use. The errors index filters by it, rows and the detail page carry a badge, the analytics page breaks errors down by environment, and every notification names it. The same error in staging and production is two rows with independent status, so resolving one never hides the other.
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
config.environment = ENV.fetch("ERROR_DASHBOARD_ENVIRONMENT", Rails.env) # free-form, defaults to Rails.env
|
|
452
|
+
config.notification_environments = %w[production] # nil = notify everywhere
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Errors captured before v0.11.0 show no badge until they recur (the next occurrence claims the row) or you run `rails rails_error_dashboard:backfill_environments`.
|
|
456
|
+
</details>
|
|
457
|
+
|
|
385
458
|
<details>
|
|
386
459
|
<summary><strong>Advanced Analytics</strong></summary>
|
|
387
460
|
|
|
@@ -392,9 +465,9 @@ Seven analysis engines built in:
|
|
|
392
465
|
1. **Baseline Anomaly Alerts** — Statistical spike detection (mean + std dev) with intelligent cooldown
|
|
393
466
|
2. **Fuzzy Error Matching** — Jaccard similarity + Levenshtein distance to find related errors
|
|
394
467
|
3. **Co-occurring Errors** — Detect errors that happen together within configurable time windows
|
|
395
|
-
4. **Error Cascade Detection** — Identify
|
|
468
|
+
4. **Error Cascade Detection** — Identify potential cascades (A is followed by B is followed by C) with probability and delays — temporal association, not proven causation
|
|
396
469
|
5. **Error Correlation Analysis** — Correlate errors with app versions, git commits, and users
|
|
397
|
-
6. **Platform Comparison** — iOS vs Android vs
|
|
470
|
+
6. **Platform Comparison** — iOS vs Android vs API health metrics side-by-side
|
|
398
471
|
7. **Occurrence Pattern Detection** — Cyclical patterns (business hours, weekends) and burst detection
|
|
399
472
|
|
|
400
473
|
[Complete documentation →](docs/FEATURES.md#advanced-analytics-features)
|
|
@@ -424,7 +497,7 @@ config.enable_instance_variables = true
|
|
|
424
497
|
<details>
|
|
425
498
|
<summary><strong>Swallowed Exception Detection</strong></summary>
|
|
426
499
|
|
|
427
|
-
Detect exceptions that are raised but silently rescued — the hardest bugs to find.
|
|
500
|
+
Detect exceptions that are raised but silently rescued — the hardest bugs to find. Only Datadog's paid APM detects rescued exceptions (Ruby 3.3+, and only inside a traced request); RED does it free, without an APM span, and aggregates the raise-vs-rescue ratio per location — no other tracker does that.
|
|
428
501
|
|
|
429
502
|
- Uses TracePoint(`:raise`) + TracePoint(`:rescue`) to track exception lifecycle
|
|
430
503
|
- Identifies code paths where exceptions are caught but never logged or re-raised
|
|
@@ -465,7 +538,8 @@ config.enable_diagnostic_dump = true
|
|
|
465
538
|
Track Rack Attack security events (throttles, blocklists, tracks) as breadcrumbs attached to errors, with a dedicated summary page.
|
|
466
539
|
|
|
467
540
|
- Captures throttle, blocklist, and track events automatically
|
|
468
|
-
- Dashboard page at `/errors/rack_attack_summary` with event breakdown
|
|
541
|
+
- Dashboard page at `/errors/rack_attack_summary` with event breakdown and per-rule stats — rack-attack ships no UI of its own
|
|
542
|
+
- Classifies AI-agent user agents (GPTBot, ClaudeBot, …) on `track` events
|
|
469
543
|
- Requires breadcrumbs to be enabled
|
|
470
544
|
|
|
471
545
|
```ruby
|
|
@@ -483,7 +557,7 @@ Capture unhandled exceptions that crash the Ruby process via an `at_exit` hook
|
|
|
483
557
|
- Disk-based fallback: writes crash data to disk because the database may be unavailable during shutdown
|
|
484
558
|
- Imported automatically on next boot
|
|
485
559
|
- Captures exception details, backtrace, uptime, GC stats, thread count, and cause chain
|
|
486
|
-
-
|
|
560
|
+
- Honeybadger, Bugsnag and AppSignal have `at_exit` reporters too; RED's writes to disk and imports at next boot because the database may already be gone during shutdown
|
|
487
561
|
|
|
488
562
|
```ruby
|
|
489
563
|
config.enable_crash_capture = true
|
|
@@ -604,7 +678,7 @@ end
|
|
|
604
678
|
|
|
605
679
|
## Languages
|
|
606
680
|
|
|
607
|
-
|
|
681
|
+
RED ships in English with machine-translated previews for ten additional languages, covering the dashboard, its emails and its notification payloads. Native-speaking Rails developers are invited to review and improve them; once a locale has been reviewed it will be marked individually as community-reviewed. Eleven locales ship:
|
|
608
682
|
|
|
609
683
|
| Locale | Language | Status |
|
|
610
684
|
|---|---|---|
|
|
@@ -648,13 +722,13 @@ SQLite, PostgreSQL, and MySQL/Trilogy — in either shared or separate-database
|
|
|
648
722
|
Yes. It runs entirely inside your own Rails process — no external services, no SDK calling out, no per-event pricing. Error data never leaves your infrastructure.
|
|
649
723
|
|
|
650
724
|
**Does it capture local variables like Sentry?**
|
|
651
|
-
Yes — local **and** instance variables at the moment the exception is raised, via `TracePoint(:raise)`, with sensitive-data filtering and configurable limits.
|
|
725
|
+
Yes — local **and** instance variables at the moment the exception is raised, via `TracePoint(:raise)`, with sensitive-data filtering and configurable limits. It is opt-in. (Sentry's SDK can also capture locals as an opt-in option; RED adds instance variables and applies your Rails `filter_parameters` automatically.)
|
|
652
726
|
|
|
653
727
|
**Will a flood of errors take down my app?**
|
|
654
|
-
No. Storm protection (a circuit breaker with adaptive sampling, **ON by default**) makes the gem degrade itself first during error floods — occurrence counts stay exact while it sheds the expensive work.
|
|
728
|
+
No. Storm protection (a circuit breaker with adaptive sampling, **ON by default**) makes the gem degrade itself first during error floods — occurrence counts stay exact while it sheds the expensive work, and a Storm History page shows exactly what was shed. There is no I/O on the hot path — the check is a digest and an atomic increment.
|
|
655
729
|
|
|
656
730
|
**Does it work with my background jobs?**
|
|
657
|
-
Yes — it
|
|
731
|
+
Yes — errors raised in jobs are captured, and it can log errors asynchronously through Sidekiq or SolidQueue (or the in-process `:async` adapter). Sidekiq, SolidQueue and GoodJob are all auto-detected for the job-queue stats stored on each error.
|
|
658
732
|
|
|
659
733
|
**Does it work with my authentication?**
|
|
660
734
|
Yes — HTTP Basic Auth out of the box, or a custom `authenticate_with` lambda that integrates with Devise, Warden, or session-based auth.
|
|
@@ -681,7 +755,7 @@ Rails 7.0–8.1 and Ruby 3.2–4.0.
|
|
|
681
755
|
- **[Batch Operations](docs/guides/BATCH_OPERATIONS.md)** — Bulk resolve/delete
|
|
682
756
|
- **[Real-Time Updates](docs/guides/REAL_TIME_UPDATES.md)** — Live dashboard
|
|
683
757
|
- **[Error Trends](docs/guides/ERROR_TREND_VISUALIZATIONS.md)** — Charts and analytics
|
|
684
|
-
- **[Translations](docs/guides/TRANSLATIONS.md)** —
|
|
758
|
+
- **[Translations](docs/guides/TRANSLATIONS.md)** — Eleven shipped locales, correcting a string, adding a language
|
|
685
759
|
|
|
686
760
|
### Advanced
|
|
687
761
|
- **[Multi-App Support](docs/MULTI_APP_PERFORMANCE.md)** — Track multiple applications
|
|
@@ -690,7 +764,7 @@ Rails 7.0–8.1 and Ruby 3.2–4.0.
|
|
|
690
764
|
- **[Customization](docs/CUSTOMIZATION.md)** — Customize everything
|
|
691
765
|
- **[Database Options](docs/guides/DATABASE_OPTIONS.md)** — Separate database setup
|
|
692
766
|
- **[Database Optimization](docs/guides/DATABASE_OPTIMIZATION.md)** — Performance tuning
|
|
693
|
-
- **[Mobile App Integration](docs/guides/MOBILE_APP_INTEGRATION.md)** —
|
|
767
|
+
- **[Mobile App Integration](docs/guides/MOBILE_APP_INTEGRATION.md)** — log mobile-originated errors through your own API endpoint, tagged by platform
|
|
694
768
|
- **[FAQ](docs/FAQ.md)** — Common questions answered
|
|
695
769
|
|
|
696
770
|
[View all documentation →](docs/README.md)
|
|
@@ -709,7 +783,7 @@ Built with **CQRS (Command/Query Responsibility Segregation)**:
|
|
|
709
783
|
|
|
710
784
|
## Testing
|
|
711
785
|
|
|
712
|
-
|
|
786
|
+
An RSpec suite of unit, request and browser-based system specs runs in CI on every supported Rails version (see the Tests badge above); the current count lives in the CI log rather than here, where it would go stale.
|
|
713
787
|
|
|
714
788
|
```bash
|
|
715
789
|
bundle exec rspec # Full suite
|
|
@@ -11,6 +11,7 @@ module RailsErrorDashboard
|
|
|
11
11
|
error_type
|
|
12
12
|
unresolved
|
|
13
13
|
platform
|
|
14
|
+
environment
|
|
14
15
|
application_id
|
|
15
16
|
search
|
|
16
17
|
severity
|
|
@@ -94,6 +95,7 @@ module RailsErrorDashboard
|
|
|
94
95
|
filter_options = Queries::FilterOptions.call(application_id: @current_application_id)
|
|
95
96
|
@error_types = filter_options[:error_types]
|
|
96
97
|
@platforms = filter_options[:platforms]
|
|
98
|
+
@environments = filter_options[:environments]
|
|
97
99
|
@assignees = filter_options[:assignees]
|
|
98
100
|
end
|
|
99
101
|
|
|
@@ -242,6 +244,7 @@ module RailsErrorDashboard
|
|
|
242
244
|
@errors_over_time = analytics[:errors_over_time]
|
|
243
245
|
@errors_by_type = analytics[:errors_by_type]
|
|
244
246
|
@errors_by_platform = analytics[:errors_by_platform]
|
|
247
|
+
@errors_by_environment = analytics[:errors_by_environment]
|
|
245
248
|
@errors_by_hour = analytics[:errors_by_hour]
|
|
246
249
|
@top_users = analytics[:top_users]
|
|
247
250
|
@resolution_rate = analytics[:resolution_rate]
|
|
@@ -20,12 +20,19 @@ module RailsErrorDashboard
|
|
|
20
20
|
# The subject is assembled from a key rather than interpolated inline, so a
|
|
21
21
|
# locale can reorder or drop the emoji. error_type and message are verbatim
|
|
22
22
|
# error content and are never translated.
|
|
23
|
+
#
|
|
24
|
+
# An error that carries an environment uses subject_with_environment; a
|
|
25
|
+
# legacy row keeps the pre-0.11 subject so existing mail filters still match.
|
|
23
26
|
def subject_for(error_log, locale)
|
|
27
|
+
environment = error_log.respond_to?(:environment) ? error_log.environment : nil
|
|
28
|
+
key = environment.present? ? "red.mailers.error_alert.subject_with_environment" : "red.mailers.error_alert.subject"
|
|
29
|
+
|
|
24
30
|
I18nStore.translate(
|
|
25
|
-
|
|
31
|
+
key,
|
|
26
32
|
locale: locale,
|
|
27
33
|
application: error_log.application&.name ||
|
|
28
34
|
I18nStore.translate("red.mailers.shared.unknown_application", locale: locale),
|
|
35
|
+
environment: environment,
|
|
29
36
|
error_type: error_log.error_type,
|
|
30
37
|
message: truncate_subject(error_log.message)
|
|
31
38
|
)
|
|
@@ -61,6 +61,7 @@ module RailsErrorDashboard
|
|
|
61
61
|
scope :by_error_type, ->(type) { where(error_type: type) }
|
|
62
62
|
scope :by_type, ->(type) { where(error_type: type) }
|
|
63
63
|
scope :by_platform, ->(platform) { where(platform: platform) }
|
|
64
|
+
scope :by_environment, ->(environment) { where(environment: environment) }
|
|
64
65
|
scope :last_24_hours, -> { where("occurred_at >= ?", 24.hours.ago) }
|
|
65
66
|
scope :last_week, -> { where("occurred_at >= ?", 1.week.ago) }
|
|
66
67
|
|
|
@@ -660,7 +660,16 @@ dd { color: var(--text-primary); }
|
|
|
660
660
|
height: 48px; flex-shrink: 0;
|
|
661
661
|
background: var(--surface-primary);
|
|
662
662
|
border-bottom: 1px solid var(--border-primary);
|
|
663
|
+
/* A flex item's default min-width is auto, meaning "never shrink below my
|
|
664
|
+
content". Both navbar groups therefore kept their full intrinsic width on a
|
|
665
|
+
narrow screen and pushed the right-hand controls past the viewport — the
|
|
666
|
+
dashboard scrolled sideways at 375px in EVERY locale, English included.
|
|
667
|
+
min-width:0 lets them shrink; overflow:hidden keeps whatever still cannot
|
|
668
|
+
fit from extending the page rather than being clipped inside the bar. */
|
|
669
|
+
min-width: 0;
|
|
670
|
+
overflow: hidden;
|
|
663
671
|
}
|
|
672
|
+
.red-navbar > * { min-width: 0; }
|
|
664
673
|
.red-search-input {
|
|
665
674
|
width: 240px; padding: 5px 10px 5px 30px; font-size: 13px;
|
|
666
675
|
border: 1px solid var(--border-primary); border-radius: var(--radius-sm);
|
|
@@ -720,6 +729,21 @@ dd { color: var(--text-primary); }
|
|
|
720
729
|
.red-sidebar.collapsed { display: none; }
|
|
721
730
|
}
|
|
722
731
|
|
|
732
|
+
/* Stat grids
|
|
733
|
+
These were inline `grid-template-columns: repeat(N, 1fr)`, which cannot carry
|
|
734
|
+
a media query, so three or four cards stayed side by side at 375px and pushed
|
|
735
|
+
the page to 488px — it scrolled sideways on a phone in EVERY locale, English
|
|
736
|
+
worst of all. auto-fit + minmax lets the row reflow to two columns and then
|
|
737
|
+
one, and minmax's 0 floor stops a long word forcing a track wider than the
|
|
738
|
+
screen. */
|
|
739
|
+
.red-stat-grid {
|
|
740
|
+
display: grid;
|
|
741
|
+
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
|
|
742
|
+
gap: var(--space-4);
|
|
743
|
+
margin-bottom: var(--space-6);
|
|
744
|
+
}
|
|
745
|
+
.red-stat-grid > * { min-width: 0; }
|
|
746
|
+
|
|
723
747
|
/* Error detail responsive grid */
|
|
724
748
|
.red-detail-grid { display: grid; grid-template-columns: 1fr 260px; gap: var(--space-4); }
|
|
725
749
|
@media (max-width: 1023.98px) { .red-detail-grid { grid-template-columns: 1fr; } }
|
|
@@ -729,7 +753,13 @@ dd { color: var(--text-primary); }
|
|
|
729
753
|
padding: 3px 10px; font-size: 11px; font-weight: 600;
|
|
730
754
|
border-radius: var(--radius-full);
|
|
731
755
|
text-transform: uppercase; letter-spacing: 0.05em;
|
|
756
|
+
white-space: nowrap;
|
|
732
757
|
}
|
|
758
|
+
/* Below the sidebar breakpoint the navbar has to fit a menu button, search,
|
|
759
|
+
language picker, shortcuts, theme toggle and the env badge. The badge is
|
|
760
|
+
decoration — the environment is also on the overview page — so it is the
|
|
761
|
+
first thing dropped rather than letting it push the page sideways. */
|
|
762
|
+
@media (max-width: 575.98px) { .red-env-badge { display: none; } }
|
|
733
763
|
|
|
734
764
|
/* Kbd shortcut */
|
|
735
765
|
kbd {
|
|
@@ -148,6 +148,11 @@
|
|
|
148
148
|
</div>
|
|
149
149
|
<% end %>
|
|
150
150
|
|
|
151
|
+
<% if @error_log.respond_to?(:environment) && @error_log.environment.present? %>
|
|
152
|
+
<div class="label"><%= red_t("red.mailers.error_alert.labels.environment") %>:</div>
|
|
153
|
+
<div class="value"><strong><%= @error_log.environment %></strong></div>
|
|
154
|
+
<% end %>
|
|
155
|
+
|
|
151
156
|
<div class="label"><%= red_t("red.mailers.error_alert.labels.occurred_at") %>:</div>
|
|
152
157
|
<div class="value"><%= red_mail_time(@error_log.occurred_at) %></div>
|
|
153
158
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<%= red_mail_t("red.mailers.error_alert.labels.application") %>: <%= @error_log.application&.name || red_mail_t("red.mailers.shared.unknown_application") %>
|
|
11
11
|
<%= red_mail_t("red.mailers.error_alert.labels.error_type") %>: <%= @error_log.error_type %>
|
|
12
12
|
<% if @error_log.platform.present? %><%= red_mail_t("red.mailers.error_alert.labels.platform") %>: <%= @error_log.platform %><% end %>
|
|
13
|
+
<% if @error_log.respond_to?(:environment) && @error_log.environment.present? %><%= red_mail_t("red.mailers.error_alert.labels.environment") %>: <%= @error_log.environment %><% end %>
|
|
13
14
|
<%= red_mail_t("red.mailers.error_alert.labels.occurred_at") %>: <%= red_mail_time(@error_log.occurred_at) %>
|
|
14
15
|
<% if @error_log.user_id.present? %><%= red_mail_t("red.mailers.error_alert.labels.user_id") %>: <%= @error_log.user_id %><% end %>
|
|
15
16
|
<% if @error_log.ip_address.present? %><%= red_mail_t("red.mailers.error_alert.labels.ip_address") %>: <%= @error_log.ip_address %><% end %>
|
|
@@ -99,4 +99,12 @@
|
|
|
99
99
|
<% end %>
|
|
100
100
|
</td>
|
|
101
101
|
<% end %>
|
|
102
|
+
<% if local_assigns[:show_environment] %>
|
|
103
|
+
<%# Environment names are free-form data (production, staging, uat...) and are never translated. %>
|
|
104
|
+
<td style="padding: var(--space-3) var(--space-4); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<%= error.environment %>">
|
|
105
|
+
<% if error.environment.present? %>
|
|
106
|
+
<span class="badge badge-api" data-environment-badge="<%= error.environment %>"><i class="bi bi-hdd-stack"></i> <%= error.environment %></span>
|
|
107
|
+
<% end %>
|
|
108
|
+
</td>
|
|
109
|
+
<% end %>
|
|
102
110
|
</tr>
|
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
<% end %>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
|
+
<% if error.respond_to?(:environment) && error.environment.present? %>
|
|
74
|
+
<div class="mb-3">
|
|
75
|
+
<small class="metadata-label d-block mb-1"><%= red_t("red.errors.sidebar.environment") %></small>
|
|
76
|
+
<%= link_to errors_path(environment: error.environment, unresolved: '0'), class: "text-decoration-none", title: red_t("red.errors.sidebar.view_all_environment", environment: error.environment) do %>
|
|
77
|
+
<span class="badge badge-api" data-environment-badge="<%= error.environment %>"><i class="bi bi-hdd-stack"></i> <%= error.environment %></span>
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
73
82
|
<div class="mb-3">
|
|
74
83
|
<small class="metadata-label d-block mb-1"><%= red_t("red.errors.sidebar.user") %></small>
|
|
75
84
|
<% if error.respond_to?(:user) && error.user %>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<!-- Summary Stats -->
|
|
48
|
-
<div
|
|
48
|
+
<div class="red-stat-grid">
|
|
49
49
|
<div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-5) var(--space-6);">
|
|
50
50
|
<div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.analytics.overview.stats.total_errors") %></div>
|
|
51
51
|
<span class="stat-value"><%= @error_stats[:total] %></span>
|
|
@@ -155,6 +155,56 @@
|
|
|
155
155
|
</div>
|
|
156
156
|
<% end %>
|
|
157
157
|
|
|
158
|
+
<%# Errors by environment: same shape as the platform chart, shown only when
|
|
159
|
+
there is something to compare. Environment names are data, not text to
|
|
160
|
+
translate; NULL rows (captured before the column existed) are :unknown. %>
|
|
161
|
+
<% environment_counts = (@errors_by_environment || {}).transform_keys { |env| env == :unknown ? red_t("red.analytics.overview.environment_chart.unknown") : env } %>
|
|
162
|
+
<% if environment_counts.size > 1 %>
|
|
163
|
+
<div class="row g-4 mb-4">
|
|
164
|
+
<div class="col-md-12">
|
|
165
|
+
<div class="card">
|
|
166
|
+
<div class="card-header">
|
|
167
|
+
<h5 class="mb-0"><i class="bi bi-hdd-stack"></i> <%= red_t("red.analytics.overview.environment_chart.title") %></h5>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="card-body">
|
|
170
|
+
<div id="errors-by-environment-chart"></div>
|
|
171
|
+
<script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>
|
|
172
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
173
|
+
const colors = window.getChartColors();
|
|
174
|
+
const environmentColors = ["#EF4444", "#F59E0B", "#2563EB", "#10B981", "#8B5CF6", "#6B7280"];
|
|
175
|
+
|
|
176
|
+
new Chartkick.PieChart("errors-by-environment-chart", <%= raw environment_counts.to_json %>, {
|
|
177
|
+
colors: environmentColors,
|
|
178
|
+
height: "300px",
|
|
179
|
+
legend: "bottom",
|
|
180
|
+
donut: true,
|
|
181
|
+
library: {
|
|
182
|
+
plugins: {
|
|
183
|
+
legend: {
|
|
184
|
+
labels: {
|
|
185
|
+
color: colors.textColor,
|
|
186
|
+
font: { size: 12, weight: 'bold' }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
</script>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="card-footer border-top-0">
|
|
196
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
197
|
+
<small class="text-muted me-2"><%= red_t("red.analytics.overview.environment_chart.quick_links") %></small>
|
|
198
|
+
<% (@errors_by_environment || {}).keys.reject { |env| env == :unknown }.each do |environment| %>
|
|
199
|
+
<%= link_to environment, errors_path(environment: environment, unresolved: '0'), class: "btn btn-sm btn-outline-secondary" %>
|
|
200
|
+
<% end %>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<% end %>
|
|
207
|
+
|
|
158
208
|
<!-- Charts Row 2 -->
|
|
159
209
|
<div class="row g-4 mb-4">
|
|
160
210
|
<div class="col-md-12">
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
<% if @live_health[:postgresql] %>
|
|
92
92
|
<%# Summary cards for PostgreSQL %>
|
|
93
93
|
<% total_db_size = (@host_tables + @gem_tables).sum { |t| t[:total_bytes] } %>
|
|
94
|
-
<div
|
|
94
|
+
<div class="red-stat-grid" style="margin-bottom: var(--space-4);">
|
|
95
95
|
<div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
|
|
96
96
|
<div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.total_db_size") %></div>
|
|
97
97
|
<div class="stat-value"><%= number_to_human_size(total_db_size) %></div>
|
|
@@ -157,6 +157,9 @@
|
|
|
157
157
|
<% if @platforms.size > 1 %>
|
|
158
158
|
<%= select_tag :platform, options_for_select([[red_t("red.errors.index.filters.all_platforms"), '']] + @platforms.map { |p| [p, p] }, params[:platform]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
159
159
|
<% end %>
|
|
160
|
+
<% if @environments.size > 1 %>
|
|
161
|
+
<%= select_tag :environment, options_for_select([[red_t("red.errors.index.filters.all_environments"), '']] + @environments.map { |e| [e, e] }, params[:environment]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
162
|
+
<% end %>
|
|
160
163
|
<%= select_tag :error_type, options_for_select([[red_t("red.errors.index.filters.all_types"), '']] + @error_types.map { |t| [t, t] }, params[:error_type]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
161
164
|
<%
|
|
162
165
|
# Built from the same keys the active chips read, so a label can never
|
|
@@ -217,6 +220,7 @@
|
|
|
217
220
|
active_filters << { label: red_t("red.errors.index.filters.chips.application", value: app_name), param: :application_id } if app_name
|
|
218
221
|
end
|
|
219
222
|
active_filters << { label: red_t("red.errors.index.filters.chips.platform", value: params[:platform]), param: :platform } if params[:platform].present?
|
|
223
|
+
active_filters << { label: red_t("red.errors.index.filters.chips.environment", value: params[:environment]), param: :environment } if params[:environment].present?
|
|
220
224
|
active_filters << { label: red_t("red.errors.index.filters.chips.error_type", value: params[:error_type]), param: :error_type } if params[:error_type].present?
|
|
221
225
|
active_filters << { label: red_t("red.errors.index.filters.chips.severity", value: severity_label.call(params[:severity])), param: :severity } if params[:severity].present?
|
|
222
226
|
active_filters << { label: red_t("red.errors.index.filters.chips.timeframe", value: timeframe_label.call(params[:timeframe])), param: :timeframe } if params[:timeframe].present?
|
|
@@ -287,11 +291,14 @@
|
|
|
287
291
|
<% if @platforms.size > 1 %>
|
|
288
292
|
<th style="padding: var(--space-3) var(--space-4); width: 100px;"><%= red_t("red.errors.index.columns.platform") %></th>
|
|
289
293
|
<% end %>
|
|
294
|
+
<% if @environments.size > 1 %>
|
|
295
|
+
<th style="padding: var(--space-3) var(--space-4); width: 110px;"><%= red_t("red.errors.index.columns.environment") %></th>
|
|
296
|
+
<% end %>
|
|
290
297
|
</tr>
|
|
291
298
|
</thead>
|
|
292
299
|
<tbody id="error_list">
|
|
293
300
|
<% @errors.each do |error| %>
|
|
294
|
-
<%= render "error_row", error: error, show_platform: @platforms.size > 1, show_application: (@applications.size > 1 && params[:application_id].blank?) %>
|
|
301
|
+
<%= render "error_row", error: error, show_platform: @platforms.size > 1, show_environment: @environments.size > 1, show_application: (@applications.size > 1 && params[:application_id].blank?) %>
|
|
295
302
|
<% end %>
|
|
296
303
|
</tbody>
|
|
297
304
|
</table>
|