rails_error_dashboard 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -5
  3. data/app/controllers/rails_error_dashboard/application_controller.rb +28 -4
  4. data/app/controllers/rails_error_dashboard/errors_controller.rb +68 -2
  5. data/app/controllers/rails_error_dashboard/health_controller.rb +38 -0
  6. data/app/helpers/rails_error_dashboard/application_helper.rb +2 -2
  7. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +56 -2
  8. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +20 -2
  9. data/app/jobs/rails_error_dashboard/cascade_detection_job.rb +30 -0
  10. data/app/jobs/rails_error_dashboard/concerns/plain_channel_message.rb +33 -6
  11. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +1 -1
  12. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +1 -1
  13. data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +26 -0
  14. data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +6 -1
  15. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +1 -1
  16. data/app/jobs/rails_error_dashboard/telegram_error_notification_job.rb +25 -0
  17. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +2 -18
  18. data/app/models/rails_error_dashboard/error_log.rb +43 -12
  19. data/app/models/rails_error_dashboard/missing_translation.rb +43 -0
  20. data/app/views/layouts/rails_error_dashboard.html.erb +11 -1
  21. data/app/views/rails_error_dashboard/errors/_error_info.html.erb +3 -1
  22. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +3 -1
  23. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +3 -9
  24. data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -0
  25. data/app/views/rails_error_dashboard/errors/boot_errors.html.erb +130 -0
  26. data/app/views/rails_error_dashboard/errors/index.html.erb +1 -1
  27. data/app/views/rails_error_dashboard/errors/missing_translations.html.erb +143 -0
  28. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +7 -4
  29. data/app/views/rails_error_dashboard/errors/settings.html.erb +19 -5
  30. data/config/locales/de.yml +68 -5
  31. data/config/locales/en.yml +78 -4
  32. data/config/locales/es.yml +68 -5
  33. data/config/locales/fr.yml +68 -5
  34. data/config/locales/it.yml +68 -5
  35. data/config/locales/ja.yml +68 -4
  36. data/config/locales/pl.yml +68 -5
  37. data/config/locales/pt-BR.yml +68 -5
  38. data/config/locales/ru.yml +68 -5
  39. data/config/locales/uk.yml +68 -5
  40. data/config/locales/zh-CN.yml +68 -4
  41. data/config/routes.rb +6 -0
  42. data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +10 -2
  43. data/db/migrate/20261010000001_create_missing_translations.rb +50 -0
  44. data/lib/generators/rails_error_dashboard/install/install_generator.rb +9 -4
  45. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +27 -0
  46. data/lib/rails_error_dashboard/commands/assign_error.rb +24 -1
  47. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +9 -1
  48. data/lib/rails_error_dashboard/commands/flush_missing_translations.rb +103 -0
  49. data/lib/rails_error_dashboard/commands/log_error.rb +1 -1
  50. data/lib/rails_error_dashboard/commands/resolve_error.rb +2 -15
  51. data/lib/rails_error_dashboard/commands/update_error_status.rb +7 -0
  52. data/lib/rails_error_dashboard/configuration.rb +42 -1
  53. data/lib/rails_error_dashboard/engine.rb +42 -1
  54. data/lib/rails_error_dashboard/middleware/rate_limiter.rb +7 -1
  55. data/lib/rails_error_dashboard/plugin.rb +19 -3
  56. data/lib/rails_error_dashboard/plugin_registry.rb +61 -12
  57. data/lib/rails_error_dashboard/queries/boot_errors.rb +76 -0
  58. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +3 -11
  59. data/lib/rails_error_dashboard/queries/errors_list.rb +8 -16
  60. data/lib/rails_error_dashboard/queries/health_status.rb +191 -0
  61. data/lib/rails_error_dashboard/queries/missing_translation_summary.rb +82 -0
  62. data/lib/rails_error_dashboard/services/active_storage_probe.rb +91 -0
  63. data/lib/rails_error_dashboard/services/backtrace_parser.rb +13 -7
  64. data/lib/rails_error_dashboard/services/backtrace_processor.rb +15 -0
  65. data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +30 -0
  66. data/lib/rails_error_dashboard/services/crash_capture.rb +85 -2
  67. data/lib/rails_error_dashboard/services/digest_builder.rb +9 -6
  68. data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +6 -0
  69. data/lib/rails_error_dashboard/services/github_link_generator.rb +25 -9
  70. data/lib/rails_error_dashboard/services/missing_translation_handler.rb +152 -0
  71. data/lib/rails_error_dashboard/services/missing_translation_tracker.rb +316 -0
  72. data/lib/rails_error_dashboard/services/resolution_callbacks.rb +44 -0
  73. data/lib/rails_error_dashboard/services/severity_classifier.rb +52 -0
  74. data/lib/rails_error_dashboard/services/source_code_reader.rb +23 -3
  75. data/lib/rails_error_dashboard/services/telegram_delivery.rb +134 -0
  76. data/lib/rails_error_dashboard/services/telegram_payload_builder.rb +105 -0
  77. data/lib/rails_error_dashboard/services/webhook_delivery.rb +63 -0
  78. data/lib/rails_error_dashboard/services/webhook_signer.rb +80 -0
  79. data/lib/rails_error_dashboard/version.rb +1 -1
  80. data/lib/rails_error_dashboard.rb +12 -0
  81. metadata +21 -2
@@ -60,6 +60,8 @@ ru:
60
60
  llm: LLM
61
61
  n_plus_one: N+1 запросы
62
62
  deprecations: Устаревшее
63
+ missing_translations: "Переводы"
64
+ boot_errors: "Ошибки запуска"
63
65
  swallowed: Подавленные
64
66
  diagnostic_dumps: Диагностика
65
67
  storms: Штормы
@@ -868,6 +870,53 @@ ru:
868
870
  error_log
869
871
  footer_tip: Подавленные исключения возбуждаются, а затем молча перехватываются.
870
872
  Они могут скрывать настоящие ошибки.
873
+ missing_translations_page:
874
+ title: "Отсутствующие переводы"
875
+ empty_title: "Отсутствующие переводы не зафиксированы"
876
+ empty_body: "За последние %{days} дн. отсутствующие ключи перевода не зафиксированы."
877
+ how_title: "Как отслеживаются отсутствующие переводы:"
878
+ how_enabled_html: "Отслеживание должно быть включено (%{option})"
879
+ how_hooked: "RED оборачивает I18n.exception_handler и передаёт вызов обработчику, который уже был в приложении; больше ничего в настройках I18n не меняется"
880
+ how_recorded: "Каждый запрос, дошедший до обработчика — как через I18n.t, так и через хелпер t() во view — считается по локали и ключу вместе с первым замеченным местом вызова"
881
+ how_not_seen: "Запрос с :default никогда не даёт промаха, а запрос с raise_on_missing_translations выбрасывает исключение до вызова любого обработчика, поэтому здесь не учитываются ни тот, ни другой"
882
+ how_buffered: "Промахи считаются в памяти и записываются в конце запроса или задания, так что они появятся с небольшой задержкой"
883
+ missing_keys: "Отсутствующие ключи"
884
+ total_misses: "Всего промахов"
885
+ locales_affected: "Затронутые локали"
886
+ all_title: "Отсутствующие ключи перевода"
887
+ column_key: "Ключ"
888
+ column_locale: "Локаль"
889
+ column_source: "Впервые замечен в"
890
+ column_misses: "Промахи"
891
+ column_first_seen: "Впервые замечен"
892
+ overflow_notice: "%{count} промахов не удалось отнести к ключу: буфер трекера (%{limit} ключей на поток) был заполнен."
893
+ footer_tip: "Добавьте ключ в файл локали или передайте :default. Ключи, на которые действительно наткнулись пользователи, идут первыми."
894
+ docs: "Руководство Rails по i18n"
895
+
896
+ boot_errors_page:
897
+ title: "Ошибки запуска"
898
+ empty_title: "Ошибки запуска не зафиксированы"
899
+ empty_body: "За последние %{days} дн. ни один процесс не упал при запуске."
900
+ how_title: "Как перехватываются ошибки запуска:"
901
+ how_enabled_html: "Перехват аварий должен быть включён (%{option})"
902
+ how_hooked: "RED регистрирует свой хук at_exit сразу после ваших инициализаторов и до eager loading в Rails, поэтому авария при запуске видна, даже если приложение так и не запустилось"
903
+ how_captured_html: "%{zeitwerk} из файла, определяющего не ту константу, %{syntax} в модели, инициализатор с исключением: всё, что убивает процесс до обработки первого запроса"
904
+ how_imported: "Авария записывается на диск, потому что базы данных ещё нет, и импортируется при следующем успешном запуске"
905
+ how_never: "Процесс, убитый SIGKILL или упавший с segfault, at_exit не увидит ни в одном инструменте"
906
+ unresolved: "Нерешённые"
907
+ boot_crashes: "Аварии при запуске"
908
+ failed_boots: "Неудачные запуски"
909
+ all_title: "Аварии при запуске по ошибке"
910
+ column_error: "Ошибка"
911
+ column_file: "Файл"
912
+ column_constant: "Константа"
913
+ column_boots: "Запуски"
914
+ column_first_seen: "Впервые замечена"
915
+ column_status: "Статус"
916
+ status_open: "Открыта"
917
+ status_resolved: "Решена"
918
+ footer_tip: "Авария при запуске означает, что процесс не обработал ни одного запроса. Исправьте её до следующего деплоя; строка закрывается как любая другая ошибка."
919
+
871
920
  rack_attack_page:
872
921
  title: События ограничения частоты
873
922
  missing_title: Rack Attack не установлен
@@ -1153,7 +1202,7 @@ ru:
1153
1202
  unable_to_detect: Не удалось определить
1154
1203
  no_user_model: Модель User не обнаружена
1155
1204
  unable_to_query_count: Не удалось получить количество
1156
- manual_cleanup_required: Требуется ручная очистка
1205
+ retention_enforced_by_job: "Удаляются RetentionCleanupJob, когда его запускает ваш планировщик"
1157
1206
  keep_forever: Хранить всегда
1158
1207
  no_automatic_deletion: Автоматическое удаление отключено
1159
1208
  run_command: 'Выполните:'
@@ -1161,8 +1210,7 @@ ru:
1161
1210
  options:
1162
1211
  enable_middleware: Перехватывает необработанные исключения
1163
1212
  enable_error_subscriber: Подписывается на события ошибок Rails
1164
- retention_days: 'Только ручная очистка (выполните: rails error_dashboard:cleanup_resolved
1165
- DAYS=90)'
1213
+ retention_days: "Удалять ошибки, не встречавшиеся столько дней. Выполняет RetentionCleanupJob, который гем сам не планирует"
1166
1214
  max_backtrace_lines: Ограничение глубины backtrace
1167
1215
  sampling_rate: Доля захватываемых ошибок
1168
1216
  application_name: Имя этого приложения (определяется автоматически, если не
@@ -1180,7 +1228,7 @@ ru:
1180
1228
  если не задано)
1181
1229
  async_logging: Обработка в фоновых задачах
1182
1230
  async_adapter: Адаптер очереди задач
1183
- enable_rate_limiting: Ограничивать частоту API-запросов
1231
+ enable_rate_limiting: "Ограничивать запросы к панели по IP и пути"
1184
1232
  rate_limit_per_minute: Запросов в минуту на IP
1185
1233
  enable_slack_notifications: Уведомления в реальном времени
1186
1234
  slack_webhook_url: Адрес webhook Slack
@@ -1189,10 +1237,15 @@ ru:
1189
1237
  notification_email_from: Адрес отправителя
1190
1238
  enable_discord_notifications: Уведомления через webhook Discord
1191
1239
  discord_webhook_url: Адрес webhook Discord
1240
+ enable_telegram_notifications: "Уведомления Telegram через Bot API"
1241
+ telegram_bot_token: "Токен бота от @BotFather (задайте переменную окружения TELEGRAM_BOT_TOKEN)"
1242
+ telegram_chat_id: "Чат, группа или канал, куда пишет бот"
1243
+ telegram_api_base_url: "Сервер Bot API (менять только для прокси или собственного сервера)"
1192
1244
  enable_pagerduty_notifications: Эскалация критических ошибок
1193
1245
  pagerduty_integration_key: API-ключ PagerDuty
1194
1246
  enable_webhook_notifications: Произвольные webhook-адреса
1195
1247
  webhook_urls: Свои webhook-адреса
1248
+ webhook_signing_secret: Подписывает исходящие webhook-запросы HMAC-SHA256 (задайте переменную окружения WEBHOOK_SIGNING_SECRET)
1196
1249
  enable_scheduled_digests: Ежедневные/еженедельные сводки. Планирование через
1197
1250
  SolidQueue, Sidekiq или cron
1198
1251
  digest_frequency: Как часто отправлять сводки
@@ -1211,7 +1264,7 @@ ru:
1211
1264
  source_code_context_lines: Строк до и после целевой строки
1212
1265
  enable_git_blame: Показывать сведения git blame
1213
1266
  source_code_cache_ttl: Время жизни кэша исходного кода
1214
- only_show_app_code_source: Скрывать gem-ы и stdlib в целях безопасности
1267
+ only_show_app_code_source: "Скрывать код гемов и vendor в просмотрщике исходников"
1215
1268
  git_branch_strategy: Стратегия определения ветки
1216
1269
  enable_breadcrumbs: Записывать след активности запроса
1217
1270
  breadcrumb_buffer_size: Максимум breadcrumbs на запрос
@@ -1241,6 +1294,7 @@ ru:
1241
1294
  enable_crash_capture: Хук at_exit для необработанных аварийных завершений
1242
1295
  enable_coverage_tracking: Диагностический режим — включите в панели, чтобы
1243
1296
  видеть выполненные строки в исходном коде (Ruby 3.2+)
1297
+ enable_missing_translation_tracking: "Считает промахи I18n приложения по локали и ключу (оборачивает I18n.exception_handler)"
1244
1298
  enable_rack_attack_tracking: Записывать события throttle/blocklist в отдельную
1245
1299
  таблицу
1246
1300
  enable_actioncable_tracking: Отслеживать события каналов WebSocket как breadcrumbs
@@ -1300,6 +1354,8 @@ ru:
1300
1354
  breadcrumbs: Breadcrumbs
1301
1355
  system_health: Состояние системы
1302
1356
  swallowed_exceptions: Обнаружение подавленных исключений (swallowed exception)
1357
+ missing_translations: "Отслеживание отсутствующих переводов"
1358
+ boot_errors: "Перехват ошибок запуска"
1303
1359
  rack_attack: Отслеживание Rack Attack
1304
1360
  actioncable: Отслеживание ActionCable
1305
1361
  activestorage: Отслеживание ActiveStorage
@@ -1713,6 +1769,13 @@ ru:
1713
1769
  guide_link: Руководство по ActionCable
1714
1770
  activestorage:
1715
1771
  title: Состояние ActiveStorage
1772
+ probe:
1773
+ title: "Доступность сервиса"
1774
+ reachable: "Доступен"
1775
+ unreachable: "Недоступен"
1776
+ service: "Сервис: %{name}"
1777
+ latency: "%{ms} мс туда и обратно"
1778
+ how: "Одна проверка существования ключа, который никогда не сохраняется, при загрузке этой страницы. GET /health выполняет ту же проверку."
1716
1779
  empty:
1717
1780
  title: События ActiveStorage не найдены
1718
1781
  message: За последние %{days} дн. в breadcrumbs ошибок не обнаружено операций
@@ -60,6 +60,8 @@ uk:
60
60
  llm: LLM
61
61
  n_plus_one: Запити N+1
62
62
  deprecations: Deprecations
63
+ missing_translations: "Переклади"
64
+ boot_errors: "Помилки запуску"
63
65
  swallowed: Придушені
64
66
  diagnostic_dumps: Діагностика
65
67
  storms: Шторми
@@ -866,6 +868,53 @@ uk:
866
868
  до error_log
867
869
  footer_tip: Придушені винятки піднімаються, а потім мовчки перехоплюються.
868
870
  Вони можуть приховувати справжні помилки.
871
+ missing_translations_page:
872
+ title: "Відсутні переклади"
873
+ empty_title: "Відсутні переклади не зафіксовано"
874
+ empty_body: "За останні %{days} дн. відсутніх ключів перекладу не зафіксовано."
875
+ how_title: "Як відстежуються відсутні переклади:"
876
+ how_enabled_html: "Відстеження має бути ввімкнено (%{option})"
877
+ how_hooked: "RED обгортає I18n.exception_handler і передає виклик обробнику, який уже був у застосунку; більше нічого в налаштуваннях I18n не змінюється"
878
+ how_recorded: "Кожен запит, що дійшов до обробника — як через I18n.t, так і через хелпер t() у view — рахується за локаллю та ключем разом із першим поміченим місцем виклику"
879
+ how_not_seen: "Запит із :default ніколи не промахується, а запит із raise_on_missing_translations кидає виняток до виклику будь-якого обробника, тому тут не враховуються ні той, ні інший"
880
+ how_buffered: "Промахи рахуються в пам'яті й записуються наприкінці запиту або завдання, тож вони з'являться з невеликою затримкою"
881
+ missing_keys: "Відсутні ключі"
882
+ total_misses: "Усього промахів"
883
+ locales_affected: "Зачеплені локалі"
884
+ all_title: "Відсутні ключі перекладу"
885
+ column_key: "Ключ"
886
+ column_locale: "Локаль"
887
+ column_source: "Уперше помічено в"
888
+ column_misses: "Промахи"
889
+ column_first_seen: "Уперше помічено"
890
+ overflow_notice: "%{count} промахів не вдалося віднести до ключа: буфер трекера (%{limit} ключів на потік) був заповнений."
891
+ footer_tip: "Додайте ключ до файлу локалі або передайте :default. Ключі, на які справді натрапили користувачі, йдуть першими."
892
+ docs: "Посібник Rails з i18n"
893
+
894
+ boot_errors_page:
895
+ title: "Помилки запуску"
896
+ empty_title: "Помилки запуску не зафіксовано"
897
+ empty_body: "За останні %{days} дн. жоден процес не впав під час запуску."
898
+ how_title: "Як перехоплюються помилки запуску:"
899
+ how_enabled_html: "Перехоплення аварій має бути ввімкнено (%{option})"
900
+ how_hooked: "RED реєструє свій хук at_exit одразу після ваших ініціалізаторів і до eager loading у Rails, тому аварія під час запуску видима, навіть якщо застосунок так і не запустився"
901
+ how_captured_html: "%{zeitwerk} із файлу, що визначає не ту константу, %{syntax} у моделі, ініціалізатор з винятком: усе, що вбиває процес до обробки першого запиту"
902
+ how_imported: "Аварія записується на диск, бо бази даних ще немає, і імпортується під час наступного успішного запуску"
903
+ how_never: "Процес, убитий SIGKILL або segfault, at_exit не побачить у жодному інструменті"
904
+ unresolved: "Невирішені"
905
+ boot_crashes: "Аварії під час запуску"
906
+ failed_boots: "Невдалі запуски"
907
+ all_title: "Аварії під час запуску за помилкою"
908
+ column_error: "Помилка"
909
+ column_file: "Файл"
910
+ column_constant: "Константа"
911
+ column_boots: "Запуски"
912
+ column_first_seen: "Уперше помічено"
913
+ column_status: "Статус"
914
+ status_open: "Відкрита"
915
+ status_resolved: "Вирішена"
916
+ footer_tip: "Аварія під час запуску означає, що процес не обробив жодного запиту. Виправте її до наступного розгортання; рядок закривається як будь-яка інша помилка."
917
+
869
918
  rack_attack_page:
870
919
  title: Події обмеження запитів
871
920
  missing_title: Rack Attack не встановлено
@@ -1149,7 +1198,7 @@ uk:
1149
1198
  unable_to_detect: Не вдалося визначити
1150
1199
  no_user_model: Модель User не виявлено
1151
1200
  unable_to_query_count: Не вдалося отримати кількість
1152
- manual_cleanup_required: Потрібне ручне очищення
1201
+ retention_enforced_by_job: "Видаляються RetentionCleanupJob, коли його запускає ваш планувальник"
1153
1202
  keep_forever: Зберігати назавжди
1154
1203
  no_automatic_deletion: Без автоматичного видалення
1155
1204
  run_command: 'Виконайте:'
@@ -1157,8 +1206,7 @@ uk:
1157
1206
  options:
1158
1207
  enable_middleware: Перехоплює необроблені винятки
1159
1208
  enable_error_subscriber: Підписується на події помилок Rails
1160
- retention_days: 'Лише ручне очищення (виконайте: rails error_dashboard:cleanup_resolved
1161
- DAYS=90)'
1209
+ retention_days: "Видаляти помилки, яких не було стільки днів. Виконує RetentionCleanupJob, який гем сам не планує"
1162
1210
  max_backtrace_lines: Обмеження глибини стеку викликів
1163
1211
  sampling_rate: Відсоток захоплених помилок
1164
1212
  application_name: Назва цього застосунку (визначається автоматично, якщо не
@@ -1176,7 +1224,7 @@ uk:
1176
1224
  якщо не задано)
1177
1225
  async_logging: Обробка у фонових завданнях
1178
1226
  async_adapter: Адаптер черги завдань
1179
- enable_rate_limiting: Обмежувати запити до API
1227
+ enable_rate_limiting: "Обмежувати запити до панелі за IP і шляхом"
1180
1228
  rate_limit_per_minute: Запитів за хвилину з однієї IP-адреси
1181
1229
  enable_slack_notifications: Сповіщення в реальному часі
1182
1230
  slack_webhook_url: Кінцева точка webhook у Slack
@@ -1185,10 +1233,15 @@ uk:
1185
1233
  notification_email_from: Адреса відправника
1186
1234
  enable_discord_notifications: Сповіщення через webhook у Discord
1187
1235
  discord_webhook_url: Кінцева точка webhook у Discord
1236
+ enable_telegram_notifications: "Сповіщення Telegram через Bot API"
1237
+ telegram_bot_token: "Токен бота від @BotFather (задайте змінну середовища TELEGRAM_BOT_TOKEN)"
1238
+ telegram_chat_id: "Чат, група або канал, куди пише бот"
1239
+ telegram_api_base_url: "Сервер Bot API (змінюйте лише для проксі або власного сервера)"
1188
1240
  enable_pagerduty_notifications: Ескалація критичних помилок
1189
1241
  pagerduty_integration_key: Ключ API PagerDuty
1190
1242
  enable_webhook_notifications: Довільні кінцеві точки webhook
1191
1243
  webhook_urls: Власні кінцеві точки webhook
1244
+ webhook_signing_secret: Підписує вихідні webhook-запити HMAC-SHA256 (задайте змінну середовища WEBHOOK_SIGNING_SECRET)
1192
1245
  enable_scheduled_digests: Щоденні/щотижневі підсумкові листи. Плануйте через
1193
1246
  SolidQueue, Sidekiq або cron
1194
1247
  digest_frequency: Як часто надсилати підсумкові листи
@@ -1209,7 +1262,7 @@ uk:
1209
1262
  source_code_context_lines: Рядків до та після цільового рядка
1210
1263
  enable_git_blame: Показувати дані git blame
1211
1264
  source_code_cache_ttl: Тривалість кешування вихідного коду
1212
- only_show_app_code_source: Приховати gems/stdlib з міркувань безпеки
1265
+ only_show_app_code_source: "Приховувати код гемів і vendor у переглядачі коду"
1213
1266
  git_branch_strategy: Стратегія визначення гілки
1214
1267
  enable_breadcrumbs: Захоплювати послідовність дій у запиті
1215
1268
  breadcrumb_buffer_size: Максимум breadcrumbs на запит
@@ -1240,6 +1293,7 @@ uk:
1240
1293
  enable_crash_capture: Хук at_exit для необроблених аварійних завершень
1241
1294
  enable_coverage_tracking: Діагностичний режим — увімкніть через панель, щоб
1242
1295
  бачити виконані рядки у перегляді коду (Ruby 3.2+)
1296
+ enable_missing_translation_tracking: "Рахує промахи I18n застосунку за локаллю та ключем (обгортає I18n.exception_handler)"
1243
1297
  enable_rack_attack_tracking: Записувати події throttle/blocklist в окрему
1244
1298
  таблицю
1245
1299
  enable_actioncable_tracking: Відстежувати події каналів WebSocket як breadcrumbs
@@ -1299,6 +1353,8 @@ uk:
1299
1353
  breadcrumbs: Breadcrumbs
1300
1354
  system_health: Стан системи
1301
1355
  swallowed_exceptions: Виявлення придушених винятків
1356
+ missing_translations: "Відстеження відсутніх перекладів"
1357
+ boot_errors: "Перехоплення помилок запуску"
1302
1358
  rack_attack: Відстеження Rack Attack
1303
1359
  actioncable: Відстеження ActionCable
1304
1360
  activestorage: Відстеження ActiveStorage
@@ -1712,6 +1768,13 @@ uk:
1712
1768
  guide_link: Посібник з ActionCable
1713
1769
  activestorage:
1714
1770
  title: Стан ActiveStorage
1771
+ probe:
1772
+ title: "Доступність сервісу"
1773
+ reachable: "Доступний"
1774
+ unreachable: "Недоступний"
1775
+ service: "Сервіс: %{name}"
1776
+ latency: "%{ms} мс туди й назад"
1777
+ how: "Одна перевірка існування ключа, який ніколи не зберігається, під час завантаження цієї сторінки. GET /health виконує ту саму перевірку."
1715
1778
  empty:
1716
1779
  title: Подій ActiveStorage не знайдено
1717
1780
  message: За останні %{days} днів у breadcrumbs помилок не виявлено операцій
@@ -60,6 +60,8 @@ zh-CN:
60
60
  llm: LLM
61
61
  n_plus_one: N+1 查询
62
62
  deprecations: Deprecations
63
+ missing_translations: "翻译"
64
+ boot_errors: "启动错误"
63
65
  swallowed: 被吞异常
64
66
  diagnostic_dumps: 诊断
65
67
  storms: 风暴
@@ -753,6 +755,53 @@ zh-CN:
753
755
  column_ratio: 比例
754
756
  view_unrescued: 查看未被捕获、已进入 error_log 的 %{exception} 错误
755
757
  footer_tip: 被吞异常先被抛出,随后被静默捕获,可能掩盖真正的缺陷。
758
+ missing_translations_page:
759
+ title: "缺失的翻译"
760
+ empty_title: "未记录到缺失的翻译"
761
+ empty_body: "最近 %{days} 天内未记录到缺失的翻译键。"
762
+ how_title: "缺失翻译的跟踪方式:"
763
+ how_enabled_html: "必须启用跟踪(%{option})"
764
+ how_hooked: "RED 会包装 I18n.exception_handler,并把调用交给应用原有的处理器;I18n 配置的其他部分不会改变"
765
+ how_recorded: "每一次到达处理器的查找——无论来自 I18n.t 还是视图助手 t()——都会按语言区域和键计数,并记录首次观测到的调用位置"
766
+ how_not_seen: "提供了 :default 的查找永远不会缺失;使用 raise_on_missing_translations 的查找会在任何处理器运行前抛出异常,因此两者都不会在此计数"
767
+ how_buffered: "缺失在内存中计数,并在请求或任务结束时写入,因此显示会有短暂延迟"
768
+ missing_keys: "缺失的键"
769
+ total_misses: "缺失总数"
770
+ locales_affected: "受影响的语言区域"
771
+ all_title: "缺失的翻译键"
772
+ column_key: "键"
773
+ column_locale: "语言区域"
774
+ column_source: "首次出现于"
775
+ column_misses: "缺失次数"
776
+ column_first_seen: "首次出现"
777
+ overflow_notice: "由于跟踪器的缓冲区(每线程 %{limit} 个键)已满,有 %{count} 次缺失无法归到具体的键。"
778
+ footer_tip: "把该键加入语言文件,或传入 :default。用户实际遇到的键排在最前。"
779
+ docs: "Rails i18n 指南"
780
+
781
+ boot_errors_page:
782
+ title: "启动错误"
783
+ empty_title: "未记录到启动错误"
784
+ empty_body: "最近 %{days} 天内没有进程在启动时崩溃。"
785
+ how_title: "启动错误的捕获方式:"
786
+ how_enabled_html: "必须启用崩溃捕获(%{option})"
787
+ how_hooked: "RED 在你的 initializer 运行之后、Rails 预加载之前注册 at_exit 钩子,因此即使应用从未完成启动,启动期间的崩溃也会被捕获"
788
+ how_captured_html: "文件定义了错误常量导致的 %{zeitwerk}、模型中的 %{syntax}、抛出异常的 initializer:任何在处理请求之前就终止进程的问题"
789
+ how_imported: "此时还没有数据库,所以崩溃先写入磁盘,并在下一次成功启动时导入"
790
+ how_never: "被 SIGKILL 或段错误终止的进程,任何工具的 at_exit 都看不到"
791
+ unresolved: "未解决"
792
+ boot_crashes: "启动崩溃"
793
+ failed_boots: "失败的启动"
794
+ all_title: "按错误分组的启动崩溃"
795
+ column_error: "错误"
796
+ column_file: "文件"
797
+ column_constant: "常量"
798
+ column_boots: "启动次数"
799
+ column_first_seen: "首次出现"
800
+ column_status: "状态"
801
+ status_open: "未处理"
802
+ status_resolved: "已解决"
803
+ footer_tip: "启动崩溃意味着进程从未处理过请求。请在下次部署前修复;该行会像其他错误一样被标记为已解决。"
804
+
756
805
  rack_attack_page:
757
806
  title: 限流事件
758
807
  missing_title: 尚未安装 Rack Attack
@@ -967,7 +1016,7 @@ zh-CN:
967
1016
  unable_to_detect: 无法检测
968
1017
  no_user_model: 未检测到 User 模型
969
1018
  unable_to_query_count: 无法查询数量
970
- manual_cleanup_required: 需要手动清理
1019
+ retention_enforced_by_job: "由 RetentionCleanupJob 在您的调度器运行它时删除"
971
1020
  keep_forever: 永久保留
972
1021
  no_automatic_deletion: 不自动删除
973
1022
  run_command: 执行:
@@ -975,7 +1024,7 @@ zh-CN:
975
1024
  options:
976
1025
  enable_middleware: 捕获未处理的异常
977
1026
  enable_error_subscriber: 订阅 Rails 错误事件
978
- retention_days: 仅手动清理(执行:rails error_dashboard:cleanup_resolved DAYS=90)
1027
+ retention_days: "删除这么多天内未再出现的错误。由 RetentionCleanupJob 执行,gem 本身不会安排它运行"
979
1028
  max_backtrace_lines: 堆栈跟踪深度上限
980
1029
  sampling_rate: 错误捕获的采样比例
981
1030
  application_name: 本应用的名称(未设置时自动检测)
@@ -989,7 +1038,7 @@ zh-CN:
989
1038
  total_users_for_impact: 用于计算影响百分比(未设置时自动检测)
990
1039
  async_logging: 在后台任务中处理
991
1040
  async_adapter: 任务队列适配器
992
- enable_rate_limiting: 对 API 请求限流
1041
+ enable_rate_limiting: "按 IP 和路径限制仪表盘请求"
993
1042
  rate_limit_per_minute: 每个 IP 每分钟的请求数
994
1043
  enable_slack_notifications: 实时通知
995
1044
  slack_webhook_url: Slack webhook 地址
@@ -998,10 +1047,15 @@ zh-CN:
998
1047
  notification_email_from: 发件人地址
999
1048
  enable_discord_notifications: 通过 Discord webhook 通知
1000
1049
  discord_webhook_url: Discord webhook 地址
1050
+ enable_telegram_notifications: "Telegram Bot API 通知"
1051
+ telegram_bot_token: "来自 @BotFather 的机器人令牌(设置环境变量 TELEGRAM_BOT_TOKEN)"
1052
+ telegram_chat_id: "机器人发送消息的聊天、群组或频道"
1053
+ telegram_api_base_url: "Bot API 服务器(仅在使用代理或自建服务器时更改)"
1001
1054
  enable_pagerduty_notifications: 严重错误升级
1002
1055
  pagerduty_integration_key: PagerDuty API 密钥
1003
1056
  enable_webhook_notifications: 通用 webhook 地址
1004
1057
  webhook_urls: 自定义 webhook 地址
1058
+ webhook_signing_secret: 使用 HMAC-SHA256 签名发出的 webhook(设置环境变量 WEBHOOK_SIGNING_SECRET)
1005
1059
  enable_scheduled_digests: 每日/每周摘要邮件。可通过 SolidQueue、Sidekiq 或 cron 调度
1006
1060
  digest_frequency: 摘要邮件的发送频率
1007
1061
  digest_recipients: 邮件地址(默认使用通知收件人)
@@ -1019,7 +1073,7 @@ zh-CN:
1019
1073
  source_code_context_lines: 目标行前后显示的行数
1020
1074
  enable_git_blame: 显示 git blame 信息
1021
1075
  source_code_cache_ttl: 源代码缓存时长
1022
- only_show_app_code_source: 出于安全考虑隐藏 gems/stdlib
1076
+ only_show_app_code_source: "在源码查看器中隐藏 gem 和 vendor 代码"
1023
1077
  git_branch_strategy: 分支解析策略
1024
1078
  enable_breadcrumbs: 记录请求的活动轨迹
1025
1079
  breadcrumb_buffer_size: 每个请求最多记录的 breadcrumbs 数
@@ -1043,6 +1097,7 @@ zh-CN:
1043
1097
  enable_diagnostic_dump: 按需生成系统状态快照
1044
1098
  enable_crash_capture: 用 at_exit 钩子捕获未处理的崩溃
1045
1099
  enable_coverage_tracking: 诊断模式 — 在面板中启用后,可在源码视图中看到已执行的行(Ruby 3.2+)
1100
+ enable_missing_translation_tracking: "按语言区域和键统计应用的 I18n 缺失(包装 I18n.exception_handler)"
1046
1101
  enable_rack_attack_tracking: 将 throttle/blocklist 事件记录到独立数据表
1047
1102
  enable_actioncable_tracking: 将 WebSocket 频道事件记录为 breadcrumbs
1048
1103
  enable_activestorage_tracking: 将上传/下载/删除记录为 breadcrumbs
@@ -1088,6 +1143,8 @@ zh-CN:
1088
1143
  breadcrumbs: Breadcrumbs
1089
1144
  system_health: 系统状态
1090
1145
  swallowed_exceptions: 被吞异常检测
1146
+ missing_translations: "缺失翻译跟踪"
1147
+ boot_errors: "启动错误捕获"
1091
1148
  rack_attack: Rack Attack 跟踪
1092
1149
  actioncable: ActionCable 跟踪
1093
1150
  activestorage: ActiveStorage 跟踪
@@ -1450,6 +1507,13 @@ zh-CN:
1450
1507
  guide_link: ActionCable 指南
1451
1508
  activestorage:
1452
1509
  title: ActiveStorage 状态
1510
+ probe:
1511
+ title: "服务可达性"
1512
+ reachable: "可达"
1513
+ unreachable: "不可达"
1514
+ service: "服务:%{name}"
1515
+ latency: "往返 %{ms} ms"
1516
+ how: "加载本页时对一个永远不会存储的键做一次存在性检查。GET /health 执行同样的探测。"
1453
1517
  empty:
1454
1518
  title: 未找到 ActiveStorage 事件
1455
1519
  message: 最近 %{days} 天的错误 breadcrumbs 中未检测到 ActiveStorage 的服务操作。
data/config/routes.rb CHANGED
@@ -7,6 +7,10 @@ RailsErrorDashboard::Engine.routes.draw do
7
7
  # Settings page
8
8
  get "settings", to: "errors#settings", as: :settings
9
9
 
10
+ # Health check (JSON): error database, queue, storm breaker, gem version.
11
+ # 200 when the dashboard can do its job, 503 when the error DB is unreachable.
12
+ get "health", to: "health#show", as: :health
13
+
10
14
  # Per-user dashboard language, persisted in the session (P5-T1).
11
15
  # POST only: changing it is a state change, and a GET would let any link or
12
16
  # prefetch alter the user's language.
@@ -41,6 +45,8 @@ RailsErrorDashboard::Engine.routes.draw do
41
45
  get :database_health_summary
42
46
  get :swallowed_exceptions
43
47
  get :rack_attack_summary
48
+ get :missing_translations
49
+ get :boot_errors
44
50
  get :actioncable_health_summary
45
51
  get :activestorage_health_summary
46
52
  get :llm_health_summary
@@ -26,7 +26,7 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
26
26
  t.string :error_type, null: false
27
27
  t.text :message, null: false
28
28
  t.text :backtrace
29
- t.integer :user_id
29
+ t.column :user_id, user_id_type
30
30
  t.text :request_url
31
31
  t.text :request_params
32
32
  t.text :user_agent
@@ -157,7 +157,7 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
157
157
  create_table :rails_error_dashboard_error_occurrences do |t|
158
158
  t.bigint :error_log_id, null: false
159
159
  t.datetime :occurred_at, null: false
160
- t.integer :user_id
160
+ t.column :user_id, user_id_type
161
161
  t.string :request_id
162
162
  t.string :session_id
163
163
  t.timestamps
@@ -275,4 +275,12 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
275
275
  drop_table :rails_error_dashboard_error_logs, if_exists: true
276
276
  drop_table :rails_error_dashboard_applications, if_exists: true
277
277
  end
278
+
279
+ private
280
+
281
+ # Same type as the app's primary keys, as ActiveStorage does for record_id.
282
+ def user_id_type
283
+ config = Rails.configuration.generators
284
+ config.options[config.orm][:primary_key_type] || :integer
285
+ end
278
286
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # One row per (locale, key, application) the host app failed to translate.
4
+ #
5
+ # A missing translation is not an error: Rails renders "translation missing"
6
+ # (or the humanized key) and the request succeeds, so it never reaches
7
+ # error_logs and nothing in the dashboard would otherwise show it. The
8
+ # tracker counts misses in memory and upserts them here at the end of the
9
+ # request or job that produced them -- a loop rendering a missing key a
10
+ # thousand times is one UPDATE, not a thousand INSERTs.
11
+ class CreateMissingTranslations < ActiveRecord::Migration[7.0]
12
+ def change
13
+ # Guard against the squashed schema migration having already created this
14
+ # table -- without it, every later migration is silently cancelled.
15
+ return if table_exists?(:rails_error_dashboard_missing_translations)
16
+
17
+ create_table :rails_error_dashboard_missing_translations do |t|
18
+ # BCP 47 tags ("pt-BR", "zh-Hans-CN", "en-US-x-twain") fit in 35.
19
+ t.string :locale, null: false, limit: 35
20
+ # The full dotted key as I18n normalised it ("users.show.greeting").
21
+ # Capped at 191 so the unique upsert index stays inside MySQL's
22
+ # 3072-byte utf8mb4 limit: (35 + 191) * 4 + application_id. The tracker
23
+ # truncates before the value reaches the index.
24
+ t.string :translation_key, null: false, limit: 191
25
+ # First host call site seen for this key ("app/views/users/show.html.erb:12").
26
+ # First-write-wins; not part of the row's identity.
27
+ t.string :source, limit: 250
28
+ t.bigint :miss_count, null: false, default: 0
29
+ t.datetime :first_seen_at, null: false
30
+ t.datetime :last_seen_at, null: false
31
+ # Nullable: a miss recorded before application scoping, or with no
32
+ # application_name configured, applies to the whole install.
33
+ t.bigint :application_id
34
+ t.timestamps
35
+ end
36
+
37
+ # Named explicitly: an auto-generated name would exceed PostgreSQL's
38
+ # 63-character limit and fail during the HOST app's deploy.
39
+ add_index :rails_error_dashboard_missing_translations,
40
+ [ :locale, :translation_key, :application_id ],
41
+ unique: true,
42
+ name: "index_red_missing_translations_upsert_key"
43
+
44
+ # The dashboard reads "misses seen in the last N days" and retention
45
+ # prunes by the same column.
46
+ add_index :rails_error_dashboard_missing_translations,
47
+ :last_seen_at,
48
+ name: "index_red_missing_translations_on_last_seen_at"
49
+ end
50
+ end
@@ -12,6 +12,7 @@ module RailsErrorDashboard
12
12
  class_option :slack, type: :boolean, default: false, desc: "Enable Slack notifications"
13
13
  class_option :email, type: :boolean, default: false, desc: "Enable email notifications"
14
14
  class_option :discord, type: :boolean, default: false, desc: "Enable Discord notifications"
15
+ class_option :telegram, type: :boolean, default: false, desc: "Enable Telegram notifications"
15
16
  class_option :pagerduty, type: :boolean, default: false, desc: "Enable PagerDuty notifications"
16
17
  class_option :webhooks, type: :boolean, default: false, desc: "Enable webhook notifications"
17
18
  # Performance options
@@ -71,13 +72,13 @@ module RailsErrorDashboard
71
72
  @selected_features = {}
72
73
 
73
74
  # =====================================================================
74
- # QUESTION 1: Notifications (gated — one y/N opens 5 sub-questions)
75
+ # QUESTION 1: Notifications (gated — one y/N opens 6 sub-questions)
75
76
  # =====================================================================
76
- notification_keys = %i[slack email discord pagerduty webhooks]
77
+ notification_keys = %i[slack email discord telegram pagerduty webhooks]
77
78
  any_notification_cli_flag = notification_keys.any? { |k| options[k] }
78
79
 
79
80
  say "[1/3] Notifications [background/dashboard only — zero request overhead]", :cyan
80
- say " Alert your team via Slack, email, Discord, PagerDuty, or webhooks.", :white
81
+ say " Alert your team via Slack, email, Discord, Telegram, PagerDuty, or webhooks.", :white
81
82
 
82
83
  if any_notification_cli_flag
83
84
  # Individual CLI flags passed — respect them, skip the gate prompt
@@ -90,6 +91,7 @@ module RailsErrorDashboard
90
91
  { key: :slack, name: "Slack", hint: "SLACK_WEBHOOK_URL" },
91
92
  { key: :email, name: "Email", hint: "ERROR_NOTIFICATION_EMAILS" },
92
93
  { key: :discord, name: "Discord", hint: "DISCORD_WEBHOOK_URL" },
94
+ { key: :telegram, name: "Telegram", hint: "TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID" },
93
95
  { key: :pagerduty, name: "PagerDuty", hint: "PAGERDUTY_INTEGRATION_KEY" },
94
96
  { key: :webhooks, name: "Webhooks", hint: "WEBHOOK_URLS" }
95
97
  ]
@@ -294,6 +296,7 @@ module RailsErrorDashboard
294
296
  @enable_slack = @selected_features&.dig(:slack) || options[:slack]
295
297
  @enable_email = @selected_features&.dig(:email) || options[:email]
296
298
  @enable_discord = @selected_features&.dig(:discord) || options[:discord]
299
+ @enable_telegram = @selected_features&.dig(:telegram) || options[:telegram]
297
300
  @enable_pagerduty = @selected_features&.dig(:pagerduty) || options[:pagerduty]
298
301
  @enable_webhooks = @selected_features&.dig(:webhooks) || options[:webhooks]
299
302
 
@@ -417,6 +420,7 @@ module RailsErrorDashboard
417
420
  notification_features << "Slack" if @enable_slack
418
421
  notification_features << "Email" if @enable_email
419
422
  notification_features << "Discord" if @enable_discord
423
+ notification_features << "Telegram" if @enable_telegram
420
424
  notification_features << "PagerDuty" if @enable_pagerduty
421
425
  notification_features << "Webhooks" if @enable_webhooks
422
426
 
@@ -481,6 +485,7 @@ module RailsErrorDashboard
481
485
  say " → Set SLACK_WEBHOOK_URL in .env", :yellow if @enable_slack
482
486
  say " → Set ERROR_NOTIFICATION_EMAILS in .env", :yellow if @enable_email
483
487
  say " → Set DISCORD_WEBHOOK_URL in .env", :yellow if @enable_discord
488
+ say " → Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env", :yellow if @enable_telegram
484
489
  say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
485
490
  say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
486
491
  if @enable_async_logging
@@ -569,7 +574,7 @@ module RailsErrorDashboard
569
574
  system_health: true, # ~1ms per error — GC, memory, threads at exact error moment
570
575
  error_sampling: true, # 50% sampling on non-critical errors — halves storage, still shows patterns
571
576
  # OFF — require credentials, config, or niche use case
572
- slack: false, email: false, discord: false, pagerduty: false, webhooks: false,
577
+ slack: false, email: false, discord: false, telegram: false, pagerduty: false, webhooks: false,
573
578
  source_code_integration: false,
574
579
  git_blame: false,
575
580
  swallowed_exceptions: false,