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
data/config/locales/pt-BR.yml
CHANGED
|
@@ -147,6 +147,7 @@ pt-BR:
|
|
|
147
147
|
any: Qualquer gravidade
|
|
148
148
|
all_apps: Todas as aplicações
|
|
149
149
|
all_platforms: Todas as plataformas
|
|
150
|
+
all_environments: Todos os ambientes
|
|
150
151
|
all_types: Todos os tipos
|
|
151
152
|
all_timeframes: Todo o período
|
|
152
153
|
all_frequencies: Todas as frequências
|
|
@@ -175,6 +176,7 @@ pt-BR:
|
|
|
175
176
|
search: 'Busca: %{value}'
|
|
176
177
|
application: 'Aplicação: %{value}'
|
|
177
178
|
platform: 'Plataforma: %{value}'
|
|
179
|
+
environment: 'Ambiente: %{value}'
|
|
178
180
|
error_type: 'Tipo: %{value}'
|
|
179
181
|
severity: 'Gravidade: %{value}'
|
|
180
182
|
timeframe: 'Período: %{value}'
|
|
@@ -190,6 +192,7 @@ pt-BR:
|
|
|
190
192
|
last_seen: Última ocorrência
|
|
191
193
|
application: Aplicação
|
|
192
194
|
platform: Plataforma
|
|
195
|
+
environment: Ambiente
|
|
193
196
|
select_all: Selecionar todos os erros
|
|
194
197
|
batch:
|
|
195
198
|
selected:
|
|
@@ -650,6 +653,7 @@ pt-BR:
|
|
|
650
653
|
low: BAIXA
|
|
651
654
|
platform: Plataforma
|
|
652
655
|
view_all_platform: Ver todos os erros de %{platform}
|
|
656
|
+
view_all_environment: Ver todos os erros de %{environment}
|
|
653
657
|
user: Usuário
|
|
654
658
|
view_user_errors: Ver todos os erros deste usuário
|
|
655
659
|
user_id: 'ID: %{id}'
|
|
@@ -867,8 +871,9 @@ pt-BR:
|
|
|
867
871
|
column_ips: IPs
|
|
868
872
|
column_top_path: Caminho principal
|
|
869
873
|
column_top_agent: Agente principal
|
|
870
|
-
overflow_notice_html:
|
|
871
|
-
|
|
874
|
+
overflow_notice_html: Não foi possível atribuir %{count} eventos a nenhuma
|
|
875
|
+
regra porque o buffer do rastreador estava cheio. Aumente %{option} para
|
|
876
|
+
capturar mais.
|
|
872
877
|
match_type:
|
|
873
878
|
blocklist: blocklist
|
|
874
879
|
throttle: throttle
|
|
@@ -1107,6 +1112,9 @@ pt-BR:
|
|
|
1107
1112
|
sampling_rate: Porcentagem de erros capturados
|
|
1108
1113
|
application_name: Nome desta aplicação (detectado automaticamente se não for
|
|
1109
1114
|
definido)
|
|
1115
|
+
environment: Ambiente ao qual os erros são atribuídos (Rails.env se não definido)
|
|
1116
|
+
notification_environments: Notificar apenas para estes ambientes (todos se
|
|
1117
|
+
não definido)
|
|
1110
1118
|
database: Banco de dados ativo para os logs de erros
|
|
1111
1119
|
use_separate_database: Usar um banco de dados dedicado para os logs de erros
|
|
1112
1120
|
user_model: Nome do modelo para as associações de usuário (detectado automaticamente
|
|
@@ -1277,11 +1285,14 @@ pt-BR:
|
|
|
1277
1285
|
unknown_application: Desconhecida
|
|
1278
1286
|
error_alert:
|
|
1279
1287
|
subject: "\U0001F6A8 [%{application}] %{error_type}: %{message}"
|
|
1288
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:
|
|
1289
|
+
%{message}"
|
|
1280
1290
|
heading: "\U0001F6A8 Alerta de erro"
|
|
1281
1291
|
labels:
|
|
1282
1292
|
application: Aplicação
|
|
1283
1293
|
error_type: Tipo de erro
|
|
1284
1294
|
platform: Plataforma
|
|
1295
|
+
environment: Ambiente
|
|
1285
1296
|
occurred_at: Ocorreu em
|
|
1286
1297
|
user_id: ID do usuário
|
|
1287
1298
|
ip_address: Endereço IP
|
|
@@ -1350,6 +1361,7 @@ pt-BR:
|
|
|
1350
1361
|
application: Aplicação
|
|
1351
1362
|
error_type: Tipo de erro
|
|
1352
1363
|
platform: Plataforma
|
|
1364
|
+
environment: Ambiente
|
|
1353
1365
|
occurred: Ocorreu
|
|
1354
1366
|
message: Mensagem
|
|
1355
1367
|
user: Usuário
|
|
@@ -1682,6 +1694,10 @@ pt-BR:
|
|
|
1682
1694
|
platform_chart:
|
|
1683
1695
|
title: Erros por plataforma
|
|
1684
1696
|
quick_links: 'Links rápidos:'
|
|
1697
|
+
environment_chart:
|
|
1698
|
+
title: Erros por ambiente
|
|
1699
|
+
quick_links: 'Links rápidos:'
|
|
1700
|
+
unknown: Desconhecido
|
|
1685
1701
|
type_chart:
|
|
1686
1702
|
title: Os 10 tipos de erro mais frequentes
|
|
1687
1703
|
hour_chart:
|
data/config/locales/ru.yml
CHANGED
|
@@ -147,6 +147,7 @@ ru:
|
|
|
147
147
|
any: Любая важность
|
|
148
148
|
all_apps: Все приложения
|
|
149
149
|
all_platforms: Все платформы
|
|
150
|
+
all_environments: Все окружения
|
|
150
151
|
all_types: Все типы
|
|
151
152
|
all_timeframes: За всё время
|
|
152
153
|
all_frequencies: Любая частота
|
|
@@ -175,6 +176,7 @@ ru:
|
|
|
175
176
|
search: 'Поиск: %{value}'
|
|
176
177
|
application: 'Приложение: %{value}'
|
|
177
178
|
platform: 'Платформа: %{value}'
|
|
179
|
+
environment: 'Окружение: %{value}'
|
|
178
180
|
error_type: 'Тип: %{value}'
|
|
179
181
|
severity: 'Важность: %{value}'
|
|
180
182
|
timeframe: 'Период: %{value}'
|
|
@@ -190,6 +192,7 @@ ru:
|
|
|
190
192
|
last_seen: Последнее появление
|
|
191
193
|
application: Приложение
|
|
192
194
|
platform: Платформа
|
|
195
|
+
environment: Окружение
|
|
193
196
|
select_all: Выбрать все ошибки
|
|
194
197
|
batch:
|
|
195
198
|
selected:
|
|
@@ -659,6 +662,7 @@ ru:
|
|
|
659
662
|
low: НИЗКАЯ
|
|
660
663
|
platform: Платформа
|
|
661
664
|
view_all_platform: 'Все ошибки: %{platform}'
|
|
665
|
+
view_all_environment: 'Все ошибки: %{environment}'
|
|
662
666
|
user: Пользователь
|
|
663
667
|
view_user_errors: Все ошибки этого пользователя
|
|
664
668
|
user_id: 'ID: %{id}'
|
|
@@ -879,8 +883,9 @@ ru:
|
|
|
879
883
|
column_ips: IP-адреса
|
|
880
884
|
column_top_path: Частый путь
|
|
881
885
|
column_top_agent: Основной агент
|
|
882
|
-
overflow_notice_html:
|
|
883
|
-
|
|
886
|
+
overflow_notice_html: Не удалось отнести %{count} событий ни к одному правилу,
|
|
887
|
+
так как буфер трекера был заполнен. Увеличьте %{option}, чтобы записывать
|
|
888
|
+
больше.
|
|
884
889
|
match_type:
|
|
885
890
|
blocklist: blocklist
|
|
886
891
|
throttle: throttle
|
|
@@ -1136,6 +1141,9 @@ ru:
|
|
|
1136
1141
|
sampling_rate: Доля захватываемых ошибок
|
|
1137
1142
|
application_name: Имя этого приложения (определяется автоматически, если не
|
|
1138
1143
|
задано)
|
|
1144
|
+
environment: Окружение, к которому относятся ошибки (Rails.env, если не задано)
|
|
1145
|
+
notification_environments: Уведомлять только для этих окружений (все, если
|
|
1146
|
+
не задано)
|
|
1139
1147
|
database: Активная база данных для журнала ошибок
|
|
1140
1148
|
use_separate_database: Использовать отдельную БД для журнала ошибок
|
|
1141
1149
|
user_model: Имя модели для связи с пользователями (определяется автоматически,
|
|
@@ -1309,11 +1317,14 @@ ru:
|
|
|
1309
1317
|
unknown_application: Неизвестно
|
|
1310
1318
|
error_alert:
|
|
1311
1319
|
subject: "\U0001F6A8 [%{application}] %{error_type}: %{message}"
|
|
1320
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:
|
|
1321
|
+
%{message}"
|
|
1312
1322
|
heading: "\U0001F6A8 Оповещение об ошибке"
|
|
1313
1323
|
labels:
|
|
1314
1324
|
application: Приложение
|
|
1315
1325
|
error_type: Тип ошибки
|
|
1316
1326
|
platform: Платформа
|
|
1327
|
+
environment: Окружение
|
|
1317
1328
|
occurred_at: Время возникновения
|
|
1318
1329
|
user_id: ID пользователя
|
|
1319
1330
|
ip_address: IP-адрес
|
|
@@ -1385,6 +1396,7 @@ ru:
|
|
|
1385
1396
|
application: Приложение
|
|
1386
1397
|
error_type: Тип ошибки
|
|
1387
1398
|
platform: Платформа
|
|
1399
|
+
environment: Окружение
|
|
1388
1400
|
occurred: Возникла
|
|
1389
1401
|
message: Сообщение
|
|
1390
1402
|
user: Пользователь
|
|
@@ -1715,6 +1727,10 @@ ru:
|
|
|
1715
1727
|
platform_chart:
|
|
1716
1728
|
title: Ошибки по платформам
|
|
1717
1729
|
quick_links: 'Быстрые ссылки:'
|
|
1730
|
+
environment_chart:
|
|
1731
|
+
title: Ошибки по окружениям
|
|
1732
|
+
quick_links: 'Быстрые ссылки:'
|
|
1733
|
+
unknown: Неизвестно
|
|
1718
1734
|
type_chart:
|
|
1719
1735
|
title: Топ-10 типов ошибок
|
|
1720
1736
|
hour_chart:
|
data/config/locales/uk.yml
CHANGED
|
@@ -147,6 +147,7 @@ uk:
|
|
|
147
147
|
any: Будь-яка критичність
|
|
148
148
|
all_apps: Усі застосунки
|
|
149
149
|
all_platforms: Усі платформи
|
|
150
|
+
all_environments: Усі середовища
|
|
150
151
|
all_types: Усі типи
|
|
151
152
|
all_timeframes: За весь час
|
|
152
153
|
all_frequencies: Усі частоти
|
|
@@ -175,6 +176,7 @@ uk:
|
|
|
175
176
|
search: 'Пошук: %{value}'
|
|
176
177
|
application: 'Застосунок: %{value}'
|
|
177
178
|
platform: 'Платформа: %{value}'
|
|
179
|
+
environment: 'Середовище: %{value}'
|
|
178
180
|
error_type: 'Тип: %{value}'
|
|
179
181
|
severity: 'Критичність: %{value}'
|
|
180
182
|
timeframe: 'Період: %{value}'
|
|
@@ -190,6 +192,7 @@ uk:
|
|
|
190
192
|
last_seen: Востаннє помічено
|
|
191
193
|
application: Застосунок
|
|
192
194
|
platform: Платформа
|
|
195
|
+
environment: Середовище
|
|
193
196
|
select_all: Вибрати всі помилки
|
|
194
197
|
batch:
|
|
195
198
|
selected:
|
|
@@ -657,6 +660,7 @@ uk:
|
|
|
657
660
|
low: НИЗЬКА
|
|
658
661
|
platform: Платформа
|
|
659
662
|
view_all_platform: Переглянути всі помилки %{platform}
|
|
663
|
+
view_all_environment: Переглянути всі помилки %{environment}
|
|
660
664
|
user: Користувач
|
|
661
665
|
view_user_errors: Переглянути всі помилки цього користувача
|
|
662
666
|
user_id: 'ID: %{id}'
|
|
@@ -877,8 +881,9 @@ uk:
|
|
|
877
881
|
column_ips: IP-адреси
|
|
878
882
|
column_top_path: Найчастіший шлях
|
|
879
883
|
column_top_agent: Основний агент
|
|
880
|
-
overflow_notice_html:
|
|
881
|
-
|
|
884
|
+
overflow_notice_html: Не вдалося віднести %{count} подій до жодного правила,
|
|
885
|
+
оскільки буфер трекера був заповнений. Збільште %{option}, щоб записувати
|
|
886
|
+
більше.
|
|
882
887
|
match_type:
|
|
883
888
|
blocklist: blocklist
|
|
884
889
|
throttle: throttle
|
|
@@ -1132,6 +1137,9 @@ uk:
|
|
|
1132
1137
|
sampling_rate: Відсоток захоплених помилок
|
|
1133
1138
|
application_name: Назва цього застосунку (визначається автоматично, якщо не
|
|
1134
1139
|
задано)
|
|
1140
|
+
environment: Середовище, до якого належать помилки (Rails.env, якщо не задано)
|
|
1141
|
+
notification_environments: Сповіщати лише для цих середовищ (усі, якщо не
|
|
1142
|
+
задано)
|
|
1135
1143
|
database: Активна база даних для журналів помилок
|
|
1136
1144
|
use_separate_database: Використовувати окрему БД для журналів помилок
|
|
1137
1145
|
user_model: Назва моделі для звʼязків із користувачами (визначається автоматично,
|
|
@@ -1307,11 +1315,14 @@ uk:
|
|
|
1307
1315
|
unknown_application: Невідомо
|
|
1308
1316
|
error_alert:
|
|
1309
1317
|
subject: "\U0001F6A8 [%{application}] %{error_type}: %{message}"
|
|
1318
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:
|
|
1319
|
+
%{message}"
|
|
1310
1320
|
heading: "\U0001F6A8 Сповіщення про помилку"
|
|
1311
1321
|
labels:
|
|
1312
1322
|
application: Застосунок
|
|
1313
1323
|
error_type: Тип помилки
|
|
1314
1324
|
platform: Платформа
|
|
1325
|
+
environment: Середовище
|
|
1315
1326
|
occurred_at: Час виникнення
|
|
1316
1327
|
user_id: ID користувача
|
|
1317
1328
|
ip_address: IP-адреса
|
|
@@ -1383,6 +1394,7 @@ uk:
|
|
|
1383
1394
|
application: Застосунок
|
|
1384
1395
|
error_type: Тип помилки
|
|
1385
1396
|
platform: Платформа
|
|
1397
|
+
environment: Середовище
|
|
1386
1398
|
occurred: Сталася
|
|
1387
1399
|
message: Повідомлення
|
|
1388
1400
|
user: Користувач
|
|
@@ -1714,6 +1726,10 @@ uk:
|
|
|
1714
1726
|
platform_chart:
|
|
1715
1727
|
title: Помилки за платформами
|
|
1716
1728
|
quick_links: 'Швидкі посилання:'
|
|
1729
|
+
environment_chart:
|
|
1730
|
+
title: Помилки за середовищами
|
|
1731
|
+
quick_links: 'Швидкі посилання:'
|
|
1732
|
+
unknown: Невідомо
|
|
1717
1733
|
type_chart:
|
|
1718
1734
|
title: 10 найчастіших типів помилок
|
|
1719
1735
|
hour_chart:
|
data/config/locales/zh-CN.yml
CHANGED
|
@@ -137,6 +137,7 @@ zh-CN:
|
|
|
137
137
|
any: 任意严重程度
|
|
138
138
|
all_apps: 全部应用
|
|
139
139
|
all_platforms: 全部平台
|
|
140
|
+
all_environments: 全部环境
|
|
140
141
|
all_types: 全部类型
|
|
141
142
|
all_timeframes: 全部时间
|
|
142
143
|
all_frequencies: 全部频率
|
|
@@ -165,6 +166,7 @@ zh-CN:
|
|
|
165
166
|
search: 搜索:%{value}
|
|
166
167
|
application: 应用:%{value}
|
|
167
168
|
platform: 平台:%{value}
|
|
169
|
+
environment: 环境:%{value}
|
|
168
170
|
error_type: 类型:%{value}
|
|
169
171
|
severity: 严重程度:%{value}
|
|
170
172
|
timeframe: 时间范围:%{value}
|
|
@@ -180,6 +182,7 @@ zh-CN:
|
|
|
180
182
|
last_seen: 最近出现
|
|
181
183
|
application: 应用
|
|
182
184
|
platform: 平台
|
|
185
|
+
environment: 环境
|
|
183
186
|
select_all: 选择全部错误
|
|
184
187
|
batch:
|
|
185
188
|
selected:
|
|
@@ -566,6 +569,7 @@ zh-CN:
|
|
|
566
569
|
low: 低
|
|
567
570
|
platform: 平台
|
|
568
571
|
view_all_platform: 查看 %{platform} 的所有错误
|
|
572
|
+
view_all_environment: 查看 %{environment} 的所有错误
|
|
569
573
|
user: 用户
|
|
570
574
|
view_user_errors: 查看该用户的所有错误
|
|
571
575
|
user_id: ID:%{id}
|
|
@@ -759,8 +763,7 @@ zh-CN:
|
|
|
759
763
|
column_ips: IP 地址
|
|
760
764
|
column_top_path: 主要路径
|
|
761
765
|
column_top_agent: 主要代理
|
|
762
|
-
overflow_notice_html:
|
|
763
|
-
由于跟踪器缓冲区已满,%{count} 个事件无法归属到任何规则。请调高 %{option} 以捕获更多事件。
|
|
766
|
+
overflow_notice_html: 由于跟踪器缓冲区已满,%{count} 个事件无法归属到任何规则。请调高 %{option} 以捕获更多事件。
|
|
764
767
|
match_type:
|
|
765
768
|
blocklist: blocklist
|
|
766
769
|
throttle: throttle
|
|
@@ -950,6 +953,8 @@ zh-CN:
|
|
|
950
953
|
max_backtrace_lines: 堆栈跟踪深度上限
|
|
951
954
|
sampling_rate: 错误捕获的采样比例
|
|
952
955
|
application_name: 本应用的名称(未设置时自动检测)
|
|
956
|
+
environment: 错误所属的环境(未设置时为 Rails.env)
|
|
957
|
+
notification_environments: 仅对这些环境发送通知(未设置时为全部)
|
|
953
958
|
database: 错误日志使用的数据库
|
|
954
959
|
use_separate_database: 为错误日志使用独立数据库
|
|
955
960
|
user_model: 用户关联所用的模型名(未设置时自动检测)
|
|
@@ -1087,11 +1092,13 @@ zh-CN:
|
|
|
1087
1092
|
unknown_application: 未知
|
|
1088
1093
|
error_alert:
|
|
1089
1094
|
subject: "\U0001F6A8 [%{application}] %{error_type}:%{message}"
|
|
1095
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:%{message}"
|
|
1090
1096
|
heading: "\U0001F6A8 错误告警"
|
|
1091
1097
|
labels:
|
|
1092
1098
|
application: 应用
|
|
1093
1099
|
error_type: 错误类型
|
|
1094
1100
|
platform: 平台
|
|
1101
|
+
environment: 环境
|
|
1095
1102
|
occurred_at: 发生时间
|
|
1096
1103
|
user_id: 用户 ID
|
|
1097
1104
|
ip_address: IP 地址
|
|
@@ -1154,6 +1161,7 @@ zh-CN:
|
|
|
1154
1161
|
application: 应用
|
|
1155
1162
|
error_type: 错误类型
|
|
1156
1163
|
platform: 平台
|
|
1164
|
+
environment: 环境
|
|
1157
1165
|
occurred: 发生时间
|
|
1158
1166
|
message: 消息
|
|
1159
1167
|
user: 用户
|
|
@@ -1452,6 +1460,10 @@ zh-CN:
|
|
|
1452
1460
|
platform_chart:
|
|
1453
1461
|
title: 按平台统计错误
|
|
1454
1462
|
quick_links: 快捷链接:
|
|
1463
|
+
environment_chart:
|
|
1464
|
+
title: 按环境统计的错误
|
|
1465
|
+
quick_links: 快捷链接:
|
|
1466
|
+
unknown: 未知
|
|
1455
1467
|
type_chart:
|
|
1456
1468
|
title: 出现最多的 10 种错误类型
|
|
1457
1469
|
hour_chart:
|
|
@@ -1791,6 +1803,6 @@ zh-CN:
|
|
|
1791
1803
|
axis_count: 数量
|
|
1792
1804
|
axis_number_of_errors: 错误数量
|
|
1793
1805
|
axis_hours: 小时
|
|
1794
|
-
suffix_hours:
|
|
1806
|
+
suffix_hours: 小时
|
|
1795
1807
|
series_total_errors: 错误总数
|
|
1796
1808
|
series_hours_to_resolve: 解决耗时(小时)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Records which environment an error came from (production, staging, uat, ...).
|
|
4
|
+
#
|
|
5
|
+
# RED had this column in v0.1.x and dropped it in the v1.0-launch refactor. It
|
|
6
|
+
# returns as a nullable string: NULL means "captured before this column
|
|
7
|
+
# existed", which is information the dashboard uses (no badge, matched as a
|
|
8
|
+
# wildcard by FindOrIncrementError and adopted on the next occurrence), not a
|
|
9
|
+
# gap to paper over with a default. `rails_error_dashboard:backfill_environments`
|
|
10
|
+
# fills NULLs from environment_info.rails_env for anyone who wants history too.
|
|
11
|
+
class AddEnvironmentToErrorLogs < ActiveRecord::Migration[7.0]
|
|
12
|
+
def change
|
|
13
|
+
# Guard against the squashed schema migration having already added this
|
|
14
|
+
# column -- without it, every later migration is silently cancelled.
|
|
15
|
+
return if column_exists?(:rails_error_dashboard_error_logs, :environment)
|
|
16
|
+
|
|
17
|
+
# 64 chars: long enough for "preprod-eu-west-2-canary", short enough that
|
|
18
|
+
# the composite index below stays cheap on MySQL's utf8mb4 key limit.
|
|
19
|
+
add_column :rails_error_dashboard_error_logs, :environment, :string, limit: 64
|
|
20
|
+
|
|
21
|
+
# Mirrors index_error_logs_on_platform_and_occurred_at: the index filter is
|
|
22
|
+
# "this environment, newest first".
|
|
23
|
+
add_index :rails_error_dashboard_error_logs, [ :environment, :occurred_at ],
|
|
24
|
+
name: "index_error_logs_on_environment_and_occurred_at"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -11,6 +11,17 @@ RailsErrorDashboard.configure do |config|
|
|
|
11
11
|
config.dashboard_username = ENV.fetch("ERROR_DASHBOARD_USER", "gandalf")
|
|
12
12
|
config.dashboard_password = ENV.fetch("ERROR_DASHBOARD_PASSWORD", "youshallnotpass")
|
|
13
13
|
|
|
14
|
+
# Environment awareness (v0.11.0). Every error records the environment it
|
|
15
|
+
# came from, the index can filter by it, and staging can be kept out of
|
|
16
|
+
# your pager. Defaults to Rails.env; override when a deploy runs under
|
|
17
|
+
# RAILS_ENV=production but is really "staging", "uat", "preprod"... names
|
|
18
|
+
# are free-form, never an enum.
|
|
19
|
+
# config.environment = ENV.fetch("ERROR_DASHBOARD_ENVIRONMENT", "staging")
|
|
20
|
+
#
|
|
21
|
+
# Only notify (Slack, email, Discord, PagerDuty, webhooks, storm and
|
|
22
|
+
# baseline alerts) for these environments. nil = every environment.
|
|
23
|
+
# config.notification_environments = %w[production]
|
|
24
|
+
|
|
14
25
|
# === Custom Authentication (optional) ===
|
|
15
26
|
# Use your app's existing auth instead of HTTP Basic Auth.
|
|
16
27
|
# The lambda runs in controller context (via instance_exec), giving access to
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Commands
|
|
5
|
+
# Command: fill in `environment` on error logs captured before the column
|
|
6
|
+
# existed, using the rails_env recorded in environment_info at capture time.
|
|
7
|
+
#
|
|
8
|
+
# Opt-in (rails_error_dashboard:backfill_environments). Live errors do not
|
|
9
|
+
# need it — FindOrIncrementError adopts a NULL row on its next occurrence —
|
|
10
|
+
# but history that never recurs would otherwise stay unbadged and
|
|
11
|
+
# unfilterable forever.
|
|
12
|
+
#
|
|
13
|
+
# Runs in batches, one UPDATE per row: the value is inside a JSON text
|
|
14
|
+
# column, so it has to be parsed in Ruby and no portable single UPDATE can
|
|
15
|
+
# do it across PostgreSQL, MySQL and SQLite.
|
|
16
|
+
class BackfillEnvironments
|
|
17
|
+
BATCH_SIZE = 500
|
|
18
|
+
COLUMN_LIMIT = 64
|
|
19
|
+
|
|
20
|
+
def self.call(batch_size: BATCH_SIZE)
|
|
21
|
+
new(batch_size: batch_size).call
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(batch_size: BATCH_SIZE)
|
|
25
|
+
@batch_size = batch_size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Integer] rows updated
|
|
29
|
+
def call
|
|
30
|
+
return 0 unless ErrorLog.column_names.include?("environment")
|
|
31
|
+
|
|
32
|
+
updated = 0
|
|
33
|
+
ErrorLog.where(environment: nil).where.not(environment_info: nil)
|
|
34
|
+
.in_batches(of: @batch_size) do |batch|
|
|
35
|
+
batch.pluck(:id, :environment_info).each do |id, raw|
|
|
36
|
+
env = rails_env_from(raw)
|
|
37
|
+
next if env.nil?
|
|
38
|
+
|
|
39
|
+
updated += ErrorLog.where(id: id, environment: nil).update_all(environment: env)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
updated
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def rails_env_from(raw)
|
|
48
|
+
parsed = JSON.parse(raw.to_s)
|
|
49
|
+
return nil unless parsed.is_a?(Hash)
|
|
50
|
+
|
|
51
|
+
value = parsed["rails_env"].to_s.strip
|
|
52
|
+
value.empty? ? nil : value[0, COLUMN_LIMIT]
|
|
53
|
+
rescue JSON::ParserError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -9,7 +9,24 @@ module RailsErrorDashboard
|
|
|
9
9
|
# 1. Unresolved errors with same hash within 24 hours → increment occurrence count
|
|
10
10
|
# 2. Resolved/wont_fix errors with same hash (any age) → reopen and increment
|
|
11
11
|
# 3. No match → create new error record
|
|
12
|
+
#
|
|
13
|
+
# Environment is a MATCH dimension, not part of the hash: the same error in
|
|
14
|
+
# staging and production is two rows with independent status. A row with a
|
|
15
|
+
# NULL environment predates the column; it matches as a wildcard and is
|
|
16
|
+
# stamped by the first occurrence that claims it, so history migrates
|
|
17
|
+
# itself without a backfill. An exact match always wins over a NULL one.
|
|
12
18
|
class FindOrIncrementError
|
|
19
|
+
# Context that describes THIS occurrence rather than the error as a
|
|
20
|
+
# group. It is refreshed on every recurrence so the row always shows the
|
|
21
|
+
# latest moment of failure, not the first one in the 24 h window. Keys
|
|
22
|
+
# absent from @attributes (feature disabled, column not migrated, or a
|
|
23
|
+
# storm :lite capture that shed context) leave the stored payload alone —
|
|
24
|
+
# a shed capture must never blank out a good snapshot.
|
|
25
|
+
REFRESHED_CONTEXT = %i[
|
|
26
|
+
breadcrumbs system_health local_variables instance_variables
|
|
27
|
+
http_method hostname content_type request_duration_ms
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
13
30
|
def self.call(error_hash, attributes = {})
|
|
14
31
|
new(error_hash, attributes).call
|
|
15
32
|
end
|
|
@@ -35,23 +52,47 @@ module RailsErrorDashboard
|
|
|
35
52
|
private
|
|
36
53
|
|
|
37
54
|
def find_unresolved
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.first
|
|
55
|
+
with_environment(
|
|
56
|
+
ErrorLog.unresolved
|
|
57
|
+
.where(error_hash: @error_hash)
|
|
58
|
+
.where(application_id: @attributes[:application_id])
|
|
59
|
+
.where("occurred_at >= ?", 24.hours.ago)
|
|
60
|
+
).lock.order(last_seen_at: :desc).first
|
|
45
61
|
end
|
|
46
62
|
|
|
47
63
|
def find_resolved
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
with_environment(
|
|
65
|
+
ErrorLog
|
|
66
|
+
.where(error_hash: @error_hash)
|
|
67
|
+
.where(application_id: @attributes[:application_id])
|
|
68
|
+
.where(status: %w[resolved wont_fix])
|
|
69
|
+
).lock.order(last_seen_at: :desc).first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Restrict to this occurrence's environment or a legacy NULL row, exact
|
|
73
|
+
# first. Literal SQL, no interpolation. A blank environment (column not
|
|
74
|
+
# migrated yet, or an attribute-less caller) leaves the scope unchanged.
|
|
75
|
+
def with_environment(scope)
|
|
76
|
+
env = @attributes[:environment]
|
|
77
|
+
return scope if env.blank? || !ErrorLog.column_names.include?("environment")
|
|
78
|
+
|
|
79
|
+
scope.where(environment: [ env, nil ])
|
|
80
|
+
.order(Arel.sql("CASE WHEN environment IS NULL THEN 1 ELSE 0 END"))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The subset of REFRESHED_CONTEXT this occurrence actually captured.
|
|
84
|
+
def latest_context
|
|
85
|
+
REFRESHED_CONTEXT.each_with_object({}) do |key, refreshed|
|
|
86
|
+
refreshed[key] = @attributes[key] unless @attributes[key].nil?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# {} unless this is a legacy NULL-environment row being claimed.
|
|
91
|
+
def environment_adoption(error)
|
|
92
|
+
return {} unless ErrorLog.column_names.include?("environment")
|
|
93
|
+
return {} if error.environment.present? || @attributes[:environment].blank?
|
|
94
|
+
|
|
95
|
+
{ environment: @attributes[:environment] }
|
|
55
96
|
end
|
|
56
97
|
|
|
57
98
|
def increment_existing(error)
|
|
@@ -62,7 +103,9 @@ module RailsErrorDashboard
|
|
|
62
103
|
request_url: @attributes[:request_url] || error.request_url,
|
|
63
104
|
request_params: @attributes[:request_params] || error.request_params,
|
|
64
105
|
user_agent: @attributes[:user_agent] || error.user_agent,
|
|
65
|
-
ip_address: @attributes[:ip_address] || error.ip_address
|
|
106
|
+
ip_address: @attributes[:ip_address] || error.ip_address,
|
|
107
|
+
**latest_context,
|
|
108
|
+
**environment_adoption(error)
|
|
66
109
|
)
|
|
67
110
|
error
|
|
68
111
|
end
|
|
@@ -78,7 +121,9 @@ module RailsErrorDashboard
|
|
|
78
121
|
request_url: @attributes[:request_url] || error.request_url,
|
|
79
122
|
request_params: @attributes[:request_params] || error.request_params,
|
|
80
123
|
user_agent: @attributes[:user_agent] || error.user_agent,
|
|
81
|
-
ip_address: @attributes[:ip_address] || error.ip_address
|
|
124
|
+
ip_address: @attributes[:ip_address] || error.ip_address,
|
|
125
|
+
**latest_context,
|
|
126
|
+
**environment_adoption(error)
|
|
82
127
|
}
|
|
83
128
|
attrs[:reopened_at] = Time.current if ErrorLog.column_names.include?("reopened_at")
|
|
84
129
|
error.update!(attrs)
|
|
@@ -90,27 +135,29 @@ module RailsErrorDashboard
|
|
|
90
135
|
ErrorLog.create!(@attributes.reverse_merge(resolved: false))
|
|
91
136
|
rescue ActiveRecord::RecordNotUnique
|
|
92
137
|
# Race condition: another process created the same error
|
|
93
|
-
retry_existing =
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
138
|
+
retry_existing = with_environment(
|
|
139
|
+
ErrorLog.unresolved
|
|
140
|
+
.where(error_hash: @error_hash)
|
|
141
|
+
.where(application_id: @attributes[:application_id])
|
|
142
|
+
.where("occurred_at >= ?", 24.hours.ago)
|
|
143
|
+
).lock.first
|
|
99
144
|
|
|
100
145
|
if retry_existing
|
|
101
146
|
retry_existing.update!(
|
|
102
147
|
occurrence_count: retry_existing.occurrence_count + 1,
|
|
103
|
-
last_seen_at: Time.current
|
|
148
|
+
last_seen_at: Time.current,
|
|
149
|
+
**latest_context,
|
|
150
|
+
**environment_adoption(retry_existing)
|
|
104
151
|
)
|
|
105
152
|
retry_existing
|
|
106
153
|
else
|
|
107
154
|
# Also check resolved in race condition path
|
|
108
|
-
retry_resolved =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
retry_resolved = with_environment(
|
|
156
|
+
ErrorLog
|
|
157
|
+
.where(error_hash: @error_hash)
|
|
158
|
+
.where(application_id: @attributes[:application_id])
|
|
159
|
+
.where(status: %w[resolved wont_fix])
|
|
160
|
+
).lock.first
|
|
114
161
|
|
|
115
162
|
if retry_resolved
|
|
116
163
|
reopen_existing(retry_resolved)
|