rails_error_dashboard 0.14.3 → 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.
- checksums.yaml +4 -4
- data/README.md +45 -17
- data/app/controllers/rails_error_dashboard/application_controller.rb +28 -4
- data/app/controllers/rails_error_dashboard/errors_controller.rb +68 -2
- data/app/controllers/rails_error_dashboard/health_controller.rb +38 -0
- data/app/helpers/rails_error_dashboard/application_helper.rb +10 -2
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +56 -2
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +20 -2
- data/app/jobs/rails_error_dashboard/cascade_detection_job.rb +30 -0
- data/app/jobs/rails_error_dashboard/concerns/plain_channel_message.rb +33 -6
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +1 -1
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +1 -1
- data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +26 -0
- data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +6 -1
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +1 -1
- data/app/jobs/rails_error_dashboard/telegram_error_notification_job.rb +25 -0
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +2 -18
- data/app/models/rails_error_dashboard/error_log.rb +43 -12
- data/app/models/rails_error_dashboard/missing_translation.rb +43 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +24 -1
- data/app/views/rails_error_dashboard/errors/_error_info.html.erb +3 -1
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +3 -1
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +3 -9
- data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -0
- data/app/views/rails_error_dashboard/errors/boot_errors.html.erb +130 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +1 -1
- data/app/views/rails_error_dashboard/errors/missing_translations.html.erb +143 -0
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +7 -4
- data/app/views/rails_error_dashboard/errors/settings.html.erb +19 -5
- data/config/locales/de.yml +72 -5
- data/config/locales/en.yml +83 -4
- data/config/locales/es.yml +72 -5
- data/config/locales/fr.yml +72 -5
- data/config/locales/it.yml +72 -5
- data/config/locales/ja.yml +72 -4
- data/config/locales/pl.yml +72 -5
- data/config/locales/pt-BR.yml +72 -5
- data/config/locales/ru.yml +72 -5
- data/config/locales/uk.yml +72 -5
- data/config/locales/zh-CN.yml +72 -4
- data/config/routes.rb +6 -0
- data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +10 -2
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +9 -0
- data/db/migrate/20261010000001_create_missing_translations.rb +50 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +40 -38
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +31 -3
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +11 -2
- data/lib/rails_error_dashboard/commands/assign_error.rb +24 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +9 -1
- data/lib/rails_error_dashboard/commands/flush_missing_translations.rb +103 -0
- data/lib/rails_error_dashboard/commands/log_error.rb +6 -3
- data/lib/rails_error_dashboard/commands/resolve_error.rb +2 -15
- data/lib/rails_error_dashboard/commands/update_error_status.rb +7 -0
- data/lib/rails_error_dashboard/configuration.rb +75 -2
- data/lib/rails_error_dashboard/engine.rb +52 -5
- data/lib/rails_error_dashboard/middleware/rate_limiter.rb +21 -43
- data/lib/rails_error_dashboard/plugin.rb +49 -3
- data/lib/rails_error_dashboard/plugin_registry.rb +61 -12
- data/lib/rails_error_dashboard/queries/boot_errors.rb +76 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +3 -11
- data/lib/rails_error_dashboard/queries/errors_list.rb +11 -17
- data/lib/rails_error_dashboard/queries/health_status.rb +191 -0
- data/lib/rails_error_dashboard/queries/missing_translation_summary.rb +82 -0
- data/lib/rails_error_dashboard/services/active_storage_probe.rb +91 -0
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +13 -7
- data/lib/rails_error_dashboard/services/backtrace_processor.rb +15 -0
- data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +30 -0
- data/lib/rails_error_dashboard/services/crash_capture.rb +85 -2
- data/lib/rails_error_dashboard/services/digest_builder.rb +9 -6
- data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +6 -0
- data/lib/rails_error_dashboard/services/exception_filter.rb +4 -1
- data/lib/rails_error_dashboard/services/github_link_generator.rb +25 -9
- data/lib/rails_error_dashboard/services/missing_translation_handler.rb +152 -0
- data/lib/rails_error_dashboard/services/missing_translation_tracker.rb +316 -0
- data/lib/rails_error_dashboard/services/resolution_callbacks.rb +44 -0
- data/lib/rails_error_dashboard/services/severity_classifier.rb +52 -0
- data/lib/rails_error_dashboard/services/solid_queue_config_check.rb +47 -0
- data/lib/rails_error_dashboard/services/source_code_reader.rb +23 -3
- data/lib/rails_error_dashboard/services/telegram_delivery.rb +134 -0
- data/lib/rails_error_dashboard/services/telegram_payload_builder.rb +105 -0
- data/lib/rails_error_dashboard/services/webhook_delivery.rb +63 -0
- data/lib/rails_error_dashboard/services/webhook_signer.rb +80 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +12 -0
- data/lib/tasks/error_dashboard.rake +43 -2
- metadata +21 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "erb"
|
|
5
|
+
|
|
6
|
+
module RailsErrorDashboard
|
|
7
|
+
module Services
|
|
8
|
+
# Pure algorithm: build the Telegram sendMessage payload for an error.
|
|
9
|
+
#
|
|
10
|
+
# No HTTP calls. Returns { text:, parse_mode:, disable_web_page_preview: };
|
|
11
|
+
# TelegramDelivery adds the chat id, which is configuration, not content.
|
|
12
|
+
#
|
|
13
|
+
# WHY HTML AND NOT MARKDOWNV2: MarkdownV2 reserves eighteen characters,
|
|
14
|
+
# every one of which must be escaped in every value, and a single
|
|
15
|
+
# unescaped "_" in a class name like Net::OpenTimeout_Retry fails the
|
|
16
|
+
# whole message with "can't parse entities". HTML mode needs only <, >
|
|
17
|
+
# and & escaped, which ERB::Util.html_escape does. Telegram allows <b>,
|
|
18
|
+
# <i>, <code>, <pre> and <a href>; nothing else is used here.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# TelegramPayloadBuilder.call(error_log)
|
|
22
|
+
# # => { text: "<b>🚨 Error Alert</b>\n...", parse_mode: "HTML", ... }
|
|
23
|
+
class TelegramPayloadBuilder
|
|
24
|
+
# Telegram rejects a text longer than this. The fields below are all
|
|
25
|
+
# bounded (message 500, location 100, the rest short), so a message
|
|
26
|
+
# built here is a few hundred characters; the fallback exists for the
|
|
27
|
+
# one pathological row and sends the same content unformatted rather
|
|
28
|
+
# than losing the notification to a parse error from a cut tag.
|
|
29
|
+
MAX_TEXT_LENGTH = 4096
|
|
30
|
+
MESSAGE_LENGTH = 500
|
|
31
|
+
|
|
32
|
+
# @param error_log [ErrorLog]
|
|
33
|
+
# @param locale [String] resolved by the enqueueing thread (P4-T1)
|
|
34
|
+
# @return [Hash] sendMessage payload without chat_id
|
|
35
|
+
def self.call(error_log, locale: I18nStore::DEFAULT_LOCALE)
|
|
36
|
+
unknown = NotificationHelpers.unknown(locale)
|
|
37
|
+
not_available = NotificationHelpers.not_available(locale)
|
|
38
|
+
|
|
39
|
+
lines = [
|
|
40
|
+
bold(NotificationHelpers.t("red.notifications.error_alert.heading", locale)),
|
|
41
|
+
# error_type is an exception class name -- verbatim, escaped.
|
|
42
|
+
bold(error_log.error_type),
|
|
43
|
+
code(NotificationHelpers.truncate_message(error_log.message, MESSAGE_LENGTH)),
|
|
44
|
+
"",
|
|
45
|
+
field(:application, NotificationHelpers.app_name(error_log), locale),
|
|
46
|
+
*environment_line(error_log, locale),
|
|
47
|
+
field(:platform, error_log.platform || unknown, locale),
|
|
48
|
+
field(:occurrences, error_log.occurrence_count.to_s, locale),
|
|
49
|
+
field(:controller, error_log.controller_name || not_available, locale),
|
|
50
|
+
field(:action, error_log.action_name || not_available, locale),
|
|
51
|
+
field(:first_seen, NotificationHelpers.format_datetime_or_na(error_log.first_seen_at, locale), locale),
|
|
52
|
+
# Backtrace content is diagnostic output -- never translated.
|
|
53
|
+
field(:location, NotificationHelpers.extract_first_backtrace_line(error_log.backtrace, locale: locale), locale, code: true),
|
|
54
|
+
"",
|
|
55
|
+
link(NotificationHelpers.dashboard_url(error_log),
|
|
56
|
+
NotificationHelpers.t("red.notifications.error_alert.view_in_dashboard", locale))
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
html_payload(lines.join("\n"))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# "<b>Label:</b> value". The colon and the bold are Telegram markup, not
|
|
63
|
+
# language, so they stay here rather than in each translation (same
|
|
64
|
+
# reasoning as NotificationHelpers.field for Slack).
|
|
65
|
+
def self.field(label, value, locale, code: false)
|
|
66
|
+
rendered = code ? code(value) : escape(value)
|
|
67
|
+
"#{bold("#{NotificationHelpers.label(label, locale)}:")} #{rendered}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.environment_line(error_log, locale)
|
|
71
|
+
return [] unless error_log.respond_to?(:environment) && error_log.environment.present?
|
|
72
|
+
|
|
73
|
+
[ field(:environment, error_log.environment, locale) ]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.bold(text)
|
|
77
|
+
"<b>#{escape(text)}</b>"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.code(text)
|
|
81
|
+
"<code>#{escape(text)}</code>"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.link(url, text)
|
|
85
|
+
%(<a href="#{escape(url)}">#{escape(text)}</a>)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Plain String, not a SafeBuffer: these go into JSON, not a view.
|
|
89
|
+
def self.escape(value)
|
|
90
|
+
ERB::Util.html_escape(value.to_s).to_str
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The payload for an already-formatted HTML text, degrading to plain
|
|
94
|
+
# text when it would not fit.
|
|
95
|
+
def self.html_payload(text)
|
|
96
|
+
if text.length > MAX_TEXT_LENGTH
|
|
97
|
+
plain = CGI.unescapeHTML(text.gsub(/<[^>]+>/, ""))
|
|
98
|
+
return { text: plain[0, MAX_TEXT_LENGTH], disable_web_page_preview: true }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
{ text: text, parse_mode: "HTML", disable_web_page_preview: true }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module RailsErrorDashboard
|
|
7
|
+
module Services
|
|
8
|
+
# One POST to a custom webhook URL (config.webhook_urls), signed when
|
|
9
|
+
# config.webhook_signing_secret is set.
|
|
10
|
+
#
|
|
11
|
+
# WHY ONE PLACE: three jobs post to webhook_urls (per-error notifications,
|
|
12
|
+
# storm/burst-summary messages, baseline alerts) and each carried its own
|
|
13
|
+
# copy of the HTTP code. The signature must be computed over the exact
|
|
14
|
+
# bytes on the wire, so the body is serialised once here and the same
|
|
15
|
+
# string is both signed and sent. Slack, Discord and PagerDuty keep their
|
|
16
|
+
# own senders: their payloads are not ours to sign.
|
|
17
|
+
#
|
|
18
|
+
# Errors propagate. Every caller already rescues around its send and logs
|
|
19
|
+
# in its own words (the specs pin those messages), so swallowing here would
|
|
20
|
+
# only hide the failure from them.
|
|
21
|
+
class WebhookDelivery
|
|
22
|
+
OPEN_TIMEOUT = 5
|
|
23
|
+
READ_TIMEOUT = 10
|
|
24
|
+
USER_AGENT = "RailsErrorDashboard/1.0"
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# @param url [String]
|
|
28
|
+
# @param payload [Hash] serialised with #to_json
|
|
29
|
+
# @param headers [Hash{String => String}] merged over the defaults
|
|
30
|
+
# @return [HTTParty::Response, Net::HTTPResponse]
|
|
31
|
+
def post(url, payload, headers: {})
|
|
32
|
+
body = payload.to_json
|
|
33
|
+
all_headers = { "Content-Type" => "application/json", "User-Agent" => USER_AGENT }
|
|
34
|
+
.merge(headers)
|
|
35
|
+
.merge(signature_headers(body))
|
|
36
|
+
|
|
37
|
+
if defined?(HTTParty)
|
|
38
|
+
HTTParty.post(url, body: body, headers: all_headers, timeout: READ_TIMEOUT)
|
|
39
|
+
else
|
|
40
|
+
uri = URI(url)
|
|
41
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
42
|
+
http.use_ssl = uri.scheme == "https"
|
|
43
|
+
http.open_timeout = OPEN_TIMEOUT
|
|
44
|
+
http.read_timeout = READ_TIMEOUT
|
|
45
|
+
# request_uri, not path: a webhook URL that carries a query string
|
|
46
|
+
# (a token, a channel id) used to lose it on this branch.
|
|
47
|
+
request = Net::HTTP::Post.new(uri.request_uri, all_headers)
|
|
48
|
+
request.body = body
|
|
49
|
+
http.request(request)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Signature headers for +body+, or {} when no secret is configured.
|
|
54
|
+
def signature_headers(body)
|
|
55
|
+
secret = RailsErrorDashboard.configuration.webhook_signing_secret
|
|
56
|
+
return {} if secret.blank?
|
|
57
|
+
|
|
58
|
+
WebhookSigner.headers_for(body, secret: secret)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module RailsErrorDashboard
|
|
6
|
+
module Services
|
|
7
|
+
# HMAC-SHA256 signatures for OUTBOUND webhooks (the ones RED sends to
|
|
8
|
+
# config.webhook_urls). Inbound verification for GitHub/GitLab/Codeberg/
|
|
9
|
+
# Linear lives in WebhooksController and is unrelated.
|
|
10
|
+
#
|
|
11
|
+
# Scheme, chosen to match what Stripe, Svix and GitHub receivers already
|
|
12
|
+
# know how to check:
|
|
13
|
+
#
|
|
14
|
+
# X-Error-Dashboard-Timestamp: 1718000000 (Unix seconds)
|
|
15
|
+
# X-Error-Dashboard-Signature-256: sha256=<hex>
|
|
16
|
+
#
|
|
17
|
+
# hex = HMAC_SHA256(secret, "#{timestamp}.#{raw_body}")
|
|
18
|
+
#
|
|
19
|
+
# The timestamp is part of the signed string, not just a header, so a
|
|
20
|
+
# captured request cannot be replayed later with a fresh timestamp. The
|
|
21
|
+
# receiver verifies the digest over the RAW body it received (before
|
|
22
|
+
# parsing JSON) and rejects timestamps outside its tolerance window.
|
|
23
|
+
#
|
|
24
|
+
# Pure functions, no I/O, nothing here can raise on well-typed input.
|
|
25
|
+
class WebhookSigner
|
|
26
|
+
TIMESTAMP_HEADER = "X-Error-Dashboard-Timestamp"
|
|
27
|
+
SIGNATURE_HEADER = "X-Error-Dashboard-Signature-256"
|
|
28
|
+
PREFIX = "sha256="
|
|
29
|
+
|
|
30
|
+
# Default replay window for verifiers, in seconds. Five minutes is the
|
|
31
|
+
# value Stripe and Svix document; it absorbs clock skew without leaving
|
|
32
|
+
# a captured request useful for long.
|
|
33
|
+
DEFAULT_TOLERANCE = 300
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# Headers to merge into the outbound request.
|
|
37
|
+
#
|
|
38
|
+
# @param body [String] the exact bytes being sent
|
|
39
|
+
# @param secret [String]
|
|
40
|
+
# @param timestamp [Integer] Unix seconds; defaults to now
|
|
41
|
+
# @return [Hash{String => String}]
|
|
42
|
+
def headers_for(body, secret:, timestamp: Time.now.to_i)
|
|
43
|
+
ts = timestamp.to_i
|
|
44
|
+
{
|
|
45
|
+
TIMESTAMP_HEADER => ts.to_s,
|
|
46
|
+
SIGNATURE_HEADER => PREFIX + digest(body, secret: secret, timestamp: ts)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [String] lowercase hex HMAC over "#{timestamp}.#{body}"
|
|
51
|
+
def digest(body, secret:, timestamp:)
|
|
52
|
+
OpenSSL::HMAC.hexdigest("SHA256", secret.to_s, "#{timestamp.to_i}.#{body}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Receiver-side check, offered so a Ruby receiver (and this gem's own
|
|
56
|
+
# specs and docs) need not re-derive the scheme.
|
|
57
|
+
#
|
|
58
|
+
# @param body [String] raw request body
|
|
59
|
+
# @param timestamp [String, Integer] value of X-Error-Dashboard-Timestamp
|
|
60
|
+
# @param signature [String] value of X-Error-Dashboard-Signature-256
|
|
61
|
+
# @param secret [String]
|
|
62
|
+
# @param tolerance [Integer, nil] max age in seconds; nil skips the window check
|
|
63
|
+
# @param now [Integer] injectable clock for tests
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def valid?(body, timestamp:, signature:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
|
|
66
|
+
return false if secret.to_s.empty? || signature.to_s.empty?
|
|
67
|
+
|
|
68
|
+
ts = Integer(timestamp.to_s, 10)
|
|
69
|
+
return false if tolerance && (now - ts).abs > tolerance
|
|
70
|
+
|
|
71
|
+
expected = PREFIX + digest(body, secret: secret, timestamp: ts)
|
|
72
|
+
ActiveSupport::SecurityUtils.secure_compare(expected, signature.to_s)
|
|
73
|
+
rescue ArgumentError, TypeError
|
|
74
|
+
# A timestamp that is not an integer is a bad signature, not an error.
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -44,6 +44,7 @@ require "rails_error_dashboard/services/localized_time_formatter"
|
|
|
44
44
|
require "rails_error_dashboard/services/notification_helpers"
|
|
45
45
|
require "rails_error_dashboard/services/slack_payload_builder"
|
|
46
46
|
require "rails_error_dashboard/services/discord_payload_builder"
|
|
47
|
+
require "rails_error_dashboard/services/telegram_payload_builder"
|
|
47
48
|
require "rails_error_dashboard/services/pagerduty_payload_builder"
|
|
48
49
|
require "rails_error_dashboard/services/webhook_payload_builder"
|
|
49
50
|
require "rails_error_dashboard/services/baseline_alert_payload_builder"
|
|
@@ -88,6 +89,13 @@ require "rails_error_dashboard/services/variable_serializer"
|
|
|
88
89
|
require "rails_error_dashboard/services/local_variable_capturer"
|
|
89
90
|
require "rails_error_dashboard/services/swallowed_exception_tracker"
|
|
90
91
|
require "rails_error_dashboard/services/rack_attack_tracker"
|
|
92
|
+
require "rails_error_dashboard/services/webhook_signer"
|
|
93
|
+
require "rails_error_dashboard/services/webhook_delivery"
|
|
94
|
+
require "rails_error_dashboard/services/telegram_delivery"
|
|
95
|
+
require "rails_error_dashboard/services/resolution_callbacks"
|
|
96
|
+
require "rails_error_dashboard/services/missing_translation_tracker"
|
|
97
|
+
require "rails_error_dashboard/services/missing_translation_handler"
|
|
98
|
+
require "rails_error_dashboard/services/active_storage_probe"
|
|
91
99
|
require "rails_error_dashboard/services/ai_agent_classifier"
|
|
92
100
|
require "rails_error_dashboard/services/crash_capture"
|
|
93
101
|
require "rails_error_dashboard/services/diagnostic_dump_generator"
|
|
@@ -136,6 +144,7 @@ require "rails_error_dashboard/commands/upsert_cascade_pattern"
|
|
|
136
144
|
require "rails_error_dashboard/commands/upsert_baseline"
|
|
137
145
|
require "rails_error_dashboard/commands/flush_swallowed_exceptions"
|
|
138
146
|
require "rails_error_dashboard/commands/flush_rack_attack_events"
|
|
147
|
+
require "rails_error_dashboard/commands/flush_missing_translations"
|
|
139
148
|
require "rails_error_dashboard/commands/scrub_invalid_encoding"
|
|
140
149
|
require "rails_error_dashboard/commands/backfill_resolved_at"
|
|
141
150
|
require "rails_error_dashboard/queries/uninstall_plan"
|
|
@@ -158,6 +167,9 @@ require "rails_error_dashboard/queries/job_health_summary"
|
|
|
158
167
|
require "rails_error_dashboard/queries/database_health_summary"
|
|
159
168
|
require "rails_error_dashboard/queries/swallowed_exception_summary"
|
|
160
169
|
require "rails_error_dashboard/queries/rack_attack_summary"
|
|
170
|
+
require "rails_error_dashboard/queries/health_status"
|
|
171
|
+
require "rails_error_dashboard/queries/missing_translation_summary"
|
|
172
|
+
require "rails_error_dashboard/queries/boot_errors"
|
|
161
173
|
require "rails_error_dashboard/queries/llm_health_summary"
|
|
162
174
|
require "rails_error_dashboard/queries/release_timeline"
|
|
163
175
|
require "rails_error_dashboard/error_reporter"
|
|
@@ -34,6 +34,25 @@ namespace :error_dashboard do
|
|
|
34
34
|
end
|
|
35
35
|
checks_passed += 1
|
|
36
36
|
|
|
37
|
+
# 2b. Separate database: the database.yml entry for this environment. Without
|
|
38
|
+
# it the engine skips connects_to and RED uses the main database, which
|
|
39
|
+
# usually has no RED tables, so nothing is recorded.
|
|
40
|
+
if config.use_separate_database
|
|
41
|
+
print " Error database entry... "
|
|
42
|
+
db_name = config.database || :error_dashboard
|
|
43
|
+
if config.separate_database_entry?
|
|
44
|
+
puts "OK"
|
|
45
|
+
checks_passed += 1
|
|
46
|
+
else
|
|
47
|
+
puts "FAILED"
|
|
48
|
+
puts " config/database.yml has no '#{db_name}' entry for the '#{Rails.env}' environment."
|
|
49
|
+
puts " RED is using the main database instead, so errors are not recorded in the error database."
|
|
50
|
+
puts " Add '#{db_name}:' under '#{Rails.env}:' with migrations_paths: db/error_dashboard_migrate"
|
|
51
|
+
puts " (see docs/guides/DATABASE_OPTIONS.md), then restart."
|
|
52
|
+
checks_failed += 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
37
56
|
# 3. Database connection
|
|
38
57
|
print " Database connection... "
|
|
39
58
|
begin
|
|
@@ -218,6 +237,28 @@ namespace :error_dashboard do
|
|
|
218
237
|
end
|
|
219
238
|
end
|
|
220
239
|
|
|
240
|
+
# 10. json 3.0 breaks Rails before 8.1.4 and 7.2.4: dashboard pages return
|
|
241
|
+
# 500 and captures can fail, with no error message. The bug is in Rails.
|
|
242
|
+
json_version = Gem.loaded_specs["json"]&.version
|
|
243
|
+
if json_version && json_version >= Gem::Version.new("3")
|
|
244
|
+
print " json gem... "
|
|
245
|
+
rails_version = Gem::Version.new(Rails.version)
|
|
246
|
+
series = rails_version.segments.first(2)
|
|
247
|
+
json3_ok = (series == [ 8, 1 ] && rails_version >= Gem::Version.new("8.1.4")) ||
|
|
248
|
+
(series == [ 7, 2 ] && rails_version >= Gem::Version.new("7.2.4")) ||
|
|
249
|
+
(series <=> [ 8, 1 ]) == 1
|
|
250
|
+
if json3_ok
|
|
251
|
+
puts "OK (json #{json_version}, Rails #{Rails.version})"
|
|
252
|
+
checks_passed += 1
|
|
253
|
+
else
|
|
254
|
+
puts "WARNING"
|
|
255
|
+
puts " json #{json_version} breaks Rails #{Rails.version} (fixed in Rails 8.1.4 and 7.2.4):"
|
|
256
|
+
puts " dashboard pages return 500 and errors can fail to be captured."
|
|
257
|
+
puts " Add gem \"json\", \"< 3\" to your Gemfile and run bundle update json, or upgrade Rails."
|
|
258
|
+
warnings += 1
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
221
262
|
# Summary
|
|
222
263
|
puts "\n" + "-" * 70
|
|
223
264
|
puts " Results: #{checks_passed} passed, #{checks_failed} failed, #{warnings} warnings"
|
|
@@ -324,7 +365,7 @@ namespace :error_dashboard do
|
|
|
324
365
|
|
|
325
366
|
# Confirm before proceeding
|
|
326
367
|
print "\nProceed with backfill? (y/N): "
|
|
327
|
-
confirmation = $stdin.gets.chomp.downcase
|
|
368
|
+
confirmation = $stdin.gets.to_s.chomp.downcase
|
|
328
369
|
|
|
329
370
|
unless confirmation == "y" || confirmation == "yes"
|
|
330
371
|
puts "\n✗ Backfill cancelled"
|
|
@@ -569,7 +610,7 @@ namespace :error_dashboard do
|
|
|
569
610
|
end
|
|
570
611
|
|
|
571
612
|
print "\n Proceed with deletion? (y/N): "
|
|
572
|
-
confirmation = $stdin.gets.chomp.downcase
|
|
613
|
+
confirmation = $stdin.gets.to_s.chomp.downcase
|
|
573
614
|
|
|
574
615
|
unless confirmation == "y" || confirmation == "yes"
|
|
575
616
|
puts "\n Cleanup cancelled"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_error_dashboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -268,6 +268,7 @@ files:
|
|
|
268
268
|
- Rakefile
|
|
269
269
|
- app/controllers/rails_error_dashboard/application_controller.rb
|
|
270
270
|
- app/controllers/rails_error_dashboard/errors_controller.rb
|
|
271
|
+
- app/controllers/rails_error_dashboard/health_controller.rb
|
|
271
272
|
- app/controllers/rails_error_dashboard/locales_controller.rb
|
|
272
273
|
- app/controllers/rails_error_dashboard/webhooks_controller.rb
|
|
273
274
|
- app/helpers/rails_error_dashboard/application_helper.rb
|
|
@@ -281,6 +282,7 @@ files:
|
|
|
281
282
|
- app/jobs/rails_error_dashboard/async_error_logging_job.rb
|
|
282
283
|
- app/jobs/rails_error_dashboard/baseline_alert_job.rb
|
|
283
284
|
- app/jobs/rails_error_dashboard/baseline_calculation_job.rb
|
|
285
|
+
- app/jobs/rails_error_dashboard/cascade_detection_job.rb
|
|
284
286
|
- app/jobs/rails_error_dashboard/close_linked_issue_job.rb
|
|
285
287
|
- app/jobs/rails_error_dashboard/concerns/localized_job.rb
|
|
286
288
|
- app/jobs/rails_error_dashboard/concerns/plain_channel_message.rb
|
|
@@ -297,6 +299,7 @@ files:
|
|
|
297
299
|
- app/jobs/rails_error_dashboard/storm_flush_job.rb
|
|
298
300
|
- app/jobs/rails_error_dashboard/storm_notification_job.rb
|
|
299
301
|
- app/jobs/rails_error_dashboard/swallowed_exception_flush_job.rb
|
|
302
|
+
- app/jobs/rails_error_dashboard/telegram_error_notification_job.rb
|
|
300
303
|
- app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
|
|
301
304
|
- app/mailers/rails_error_dashboard/application_mailer.rb
|
|
302
305
|
- app/mailers/rails_error_dashboard/digest_mailer.rb
|
|
@@ -311,6 +314,7 @@ files:
|
|
|
311
314
|
- app/models/rails_error_dashboard/error_occurrence.rb
|
|
312
315
|
- app/models/rails_error_dashboard/event_count.rb
|
|
313
316
|
- app/models/rails_error_dashboard/event_timing_gap.rb
|
|
317
|
+
- app/models/rails_error_dashboard/missing_translation.rb
|
|
314
318
|
- app/models/rails_error_dashboard/rack_attack_event.rb
|
|
315
319
|
- app/models/rails_error_dashboard/storm_event.rb
|
|
316
320
|
- app/models/rails_error_dashboard/storm_flush_batch.rb
|
|
@@ -344,6 +348,7 @@ files:
|
|
|
344
348
|
- app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb
|
|
345
349
|
- app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb
|
|
346
350
|
- app/views/rails_error_dashboard/errors/analytics.html.erb
|
|
351
|
+
- app/views/rails_error_dashboard/errors/boot_errors.html.erb
|
|
347
352
|
- app/views/rails_error_dashboard/errors/cache_health_summary.html.erb
|
|
348
353
|
- app/views/rails_error_dashboard/errors/correlation.html.erb
|
|
349
354
|
- app/views/rails_error_dashboard/errors/database_health_summary.html.erb
|
|
@@ -352,6 +357,7 @@ files:
|
|
|
352
357
|
- app/views/rails_error_dashboard/errors/index.html.erb
|
|
353
358
|
- app/views/rails_error_dashboard/errors/job_health_summary.html.erb
|
|
354
359
|
- app/views/rails_error_dashboard/errors/llm_health_summary.html.erb
|
|
360
|
+
- app/views/rails_error_dashboard/errors/missing_translations.html.erb
|
|
355
361
|
- app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb
|
|
356
362
|
- app/views/rails_error_dashboard/errors/overview.html.erb
|
|
357
363
|
- app/views/rails_error_dashboard/errors/platform_comparison.html.erb
|
|
@@ -425,6 +431,7 @@ files:
|
|
|
425
431
|
- db/migrate/20260919000001_create_event_counts.rb
|
|
426
432
|
- db/migrate/20260920000001_add_buckets_incomplete_to_storm_events.rb
|
|
427
433
|
- db/migrate/20260920000002_create_event_timing_gaps.rb
|
|
434
|
+
- db/migrate/20261010000001_create_missing_translations.rb
|
|
428
435
|
- lib/generators/rails_error_dashboard/install/install_generator.rb
|
|
429
436
|
- lib/generators/rails_error_dashboard/install/templates/README
|
|
430
437
|
- lib/generators/rails_error_dashboard/install/templates/initializer.rb
|
|
@@ -444,6 +451,7 @@ files:
|
|
|
444
451
|
- lib/rails_error_dashboard/commands/drop_all_tables.rb
|
|
445
452
|
- lib/rails_error_dashboard/commands/find_or_create_application.rb
|
|
446
453
|
- lib/rails_error_dashboard/commands/find_or_increment_error.rb
|
|
454
|
+
- lib/rails_error_dashboard/commands/flush_missing_translations.rb
|
|
447
455
|
- lib/rails_error_dashboard/commands/flush_rack_attack_events.rb
|
|
448
456
|
- lib/rails_error_dashboard/commands/flush_storm_counts.rb
|
|
449
457
|
- lib/rails_error_dashboard/commands/flush_swallowed_exceptions.rb
|
|
@@ -486,6 +494,7 @@ files:
|
|
|
486
494
|
- lib/rails_error_dashboard/queries/active_storage_summary.rb
|
|
487
495
|
- lib/rails_error_dashboard/queries/analytics_stats.rb
|
|
488
496
|
- lib/rails_error_dashboard/queries/baseline_stats.rb
|
|
497
|
+
- lib/rails_error_dashboard/queries/boot_errors.rb
|
|
489
498
|
- lib/rails_error_dashboard/queries/cache_health_summary.rb
|
|
490
499
|
- lib/rails_error_dashboard/queries/co_occurring_errors.rb
|
|
491
500
|
- lib/rails_error_dashboard/queries/critical_alerts.rb
|
|
@@ -497,8 +506,10 @@ files:
|
|
|
497
506
|
- lib/rails_error_dashboard/queries/errors_list.rb
|
|
498
507
|
- lib/rails_error_dashboard/queries/event_volume.rb
|
|
499
508
|
- lib/rails_error_dashboard/queries/filter_options.rb
|
|
509
|
+
- lib/rails_error_dashboard/queries/health_status.rb
|
|
500
510
|
- lib/rails_error_dashboard/queries/job_health_summary.rb
|
|
501
511
|
- lib/rails_error_dashboard/queries/llm_health_summary.rb
|
|
512
|
+
- lib/rails_error_dashboard/queries/missing_translation_summary.rb
|
|
502
513
|
- lib/rails_error_dashboard/queries/mttr_stats.rb
|
|
503
514
|
- lib/rails_error_dashboard/queries/n_plus_one_summary.rb
|
|
504
515
|
- lib/rails_error_dashboard/queries/platform_comparison.rb
|
|
@@ -510,6 +521,7 @@ files:
|
|
|
510
521
|
- lib/rails_error_dashboard/queries/swallowed_exception_summary.rb
|
|
511
522
|
- lib/rails_error_dashboard/queries/uninstall_plan.rb
|
|
512
523
|
- lib/rails_error_dashboard/queries/user_impact_summary.rb
|
|
524
|
+
- lib/rails_error_dashboard/services/active_storage_probe.rb
|
|
513
525
|
- lib/rails_error_dashboard/services/ai_agent_classifier.rb
|
|
514
526
|
- lib/rails_error_dashboard/services/analytics_cache_manager.rb
|
|
515
527
|
- lib/rails_error_dashboard/services/backtrace_parser.rb
|
|
@@ -550,6 +562,8 @@ files:
|
|
|
550
562
|
- lib/rails_error_dashboard/services/local_variable_capturer.rb
|
|
551
563
|
- lib/rails_error_dashboard/services/localized_time_formatter.rb
|
|
552
564
|
- lib/rails_error_dashboard/services/markdown_error_formatter.rb
|
|
565
|
+
- lib/rails_error_dashboard/services/missing_translation_handler.rb
|
|
566
|
+
- lib/rails_error_dashboard/services/missing_translation_tracker.rb
|
|
553
567
|
- lib/rails_error_dashboard/services/n_plus_one_detector.rb
|
|
554
568
|
- lib/rails_error_dashboard/services/notification_helpers.rb
|
|
555
569
|
- lib/rails_error_dashboard/services/notification_throttler.rb
|
|
@@ -559,6 +573,7 @@ files:
|
|
|
559
573
|
- lib/rails_error_dashboard/services/platform_detector.rb
|
|
560
574
|
- lib/rails_error_dashboard/services/priority_score_calculator.rb
|
|
561
575
|
- lib/rails_error_dashboard/services/rack_attack_tracker.rb
|
|
576
|
+
- lib/rails_error_dashboard/services/resolution_callbacks.rb
|
|
562
577
|
- lib/rails_error_dashboard/services/rspec_generator.rb
|
|
563
578
|
- lib/rails_error_dashboard/services/sensitive_data_filter.rb
|
|
564
579
|
- lib/rails_error_dashboard/services/severity_classifier.rb
|
|
@@ -573,9 +588,13 @@ files:
|
|
|
573
588
|
- lib/rails_error_dashboard/services/storm_protection/gate.rb
|
|
574
589
|
- lib/rails_error_dashboard/services/swallowed_exception_tracker.rb
|
|
575
590
|
- lib/rails_error_dashboard/services/system_health_snapshot.rb
|
|
591
|
+
- lib/rails_error_dashboard/services/telegram_delivery.rb
|
|
592
|
+
- lib/rails_error_dashboard/services/telegram_payload_builder.rb
|
|
576
593
|
- lib/rails_error_dashboard/services/url_safety.rb
|
|
577
594
|
- lib/rails_error_dashboard/services/variable_serializer.rb
|
|
595
|
+
- lib/rails_error_dashboard/services/webhook_delivery.rb
|
|
578
596
|
- lib/rails_error_dashboard/services/webhook_payload_builder.rb
|
|
597
|
+
- lib/rails_error_dashboard/services/webhook_signer.rb
|
|
579
598
|
- lib/rails_error_dashboard/subscribers/action_cable_subscriber.rb
|
|
580
599
|
- lib/rails_error_dashboard/subscribers/active_storage_subscriber.rb
|
|
581
600
|
- lib/rails_error_dashboard/subscribers/breadcrumb_subscriber.rb
|
|
@@ -601,7 +620,7 @@ metadata:
|
|
|
601
620
|
funding_uri: https://github.com/sponsors/AnjanJ
|
|
602
621
|
post_install_message: |
|
|
603
622
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
604
|
-
RED (Rails Error Dashboard) v0.
|
|
623
|
+
RED (Rails Error Dashboard) v0.15.0
|
|
605
624
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
606
625
|
|
|
607
626
|
First install:
|