error_radar 0.2.0 → 0.4.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/CHANGELOG.md +52 -0
- data/app/controllers/error_radar/dashboard_controller.rb +0 -22
- data/app/controllers/error_radar/errors_controller.rb +137 -0
- data/app/mailers/error_radar/error_mailer.rb +32 -0
- data/app/models/error_radar/error_log.rb +7 -1
- data/app/views/error_radar/dashboard/index.html.erb +9 -3
- data/app/views/error_radar/error_mailer/new_error.html.erb +82 -0
- data/app/views/error_radar/error_mailer/new_error.text.erb +21 -0
- data/app/views/error_radar/errors/index.html.erb +297 -0
- data/app/views/error_radar/errors/show.html.erb +165 -0
- data/app/views/layouts/error_radar/application.html.erb +19 -3
- data/config/routes.rb +6 -2
- data/lib/error_radar/configuration.rb +51 -3
- data/lib/error_radar/engine.rb +23 -0
- data/lib/error_radar/integrations/rake.rb +32 -0
- data/lib/error_radar/notifications/discord.rb +61 -0
- data/lib/error_radar/notifications/email.rb +18 -0
- data/lib/error_radar/notifications/slack.rb +76 -0
- data/lib/error_radar/notifications/webhook.rb +48 -0
- data/lib/error_radar/notifier.rb +91 -0
- data/lib/error_radar/tracking.rb +6 -2
- data/lib/error_radar/version.rb +1 -1
- data/lib/error_radar.rb +1 -0
- data/lib/generators/error_radar/install/templates/initializer.rb +35 -10
- metadata +14 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module ErrorRadar
|
|
8
|
+
module Notifications
|
|
9
|
+
module Discord
|
|
10
|
+
SEV_COLORS = {
|
|
11
|
+
'critical' => 0x7b001c,
|
|
12
|
+
'error' => 0xdc3545,
|
|
13
|
+
'warning' => 0xfd7e14,
|
|
14
|
+
'info' => 0x17a2b8
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.deliver(log)
|
|
18
|
+
url = URI(ErrorRadar.config.discord_webhook_url)
|
|
19
|
+
payload = build_payload(log)
|
|
20
|
+
|
|
21
|
+
http = Net::HTTP.new(url.host, url.port)
|
|
22
|
+
http.use_ssl = url.scheme == 'https'
|
|
23
|
+
http.open_timeout = 5
|
|
24
|
+
http.read_timeout = 5
|
|
25
|
+
|
|
26
|
+
req = Net::HTTP::Post.new(url)
|
|
27
|
+
req['Content-Type'] = 'application/json'
|
|
28
|
+
req.body = payload.to_json
|
|
29
|
+
|
|
30
|
+
http.request(req)
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
ErrorRadar::Tracking.warn_internal("Discord notification failed: #{e.message}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.build_payload(log)
|
|
36
|
+
app = ErrorRadar::Notifier.app_name
|
|
37
|
+
url = ErrorRadar::Notifier.error_url(log)
|
|
38
|
+
prefix = log.new_fingerprint? ? '🆕 New error' : '🔁 Critical error'
|
|
39
|
+
|
|
40
|
+
fields = [
|
|
41
|
+
{ name: 'Source', value: (log.source || 'unknown').truncate(100), inline: true },
|
|
42
|
+
{ name: 'Category', value: log.category.to_s, inline: true },
|
|
43
|
+
{ name: 'Severity', value: log.severity.to_s, inline: true },
|
|
44
|
+
{ name: 'Occurrences', value: log.occurrences.to_s, inline: true }
|
|
45
|
+
]
|
|
46
|
+
fields << { name: 'View', value: "[Error Radar](#{url})", inline: false } if url
|
|
47
|
+
|
|
48
|
+
embed = {
|
|
49
|
+
title: "#{prefix}: #{log.error_class}",
|
|
50
|
+
description: "```#{log.message.to_s.truncate(300)}```",
|
|
51
|
+
color: SEV_COLORS[log.severity] || 0x6c757d,
|
|
52
|
+
fields: fields,
|
|
53
|
+
footer: { text: "[#{app}] Error Radar" },
|
|
54
|
+
timestamp: log.last_seen_at&.iso8601
|
|
55
|
+
}.compact
|
|
56
|
+
|
|
57
|
+
{ embeds: [embed] }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ErrorRadar
|
|
4
|
+
module Notifications
|
|
5
|
+
# Delivers notification emails via ActionMailer (must be configured in
|
|
6
|
+
# the host app). Uses deliver_later so it doesn't block the request cycle.
|
|
7
|
+
module Email
|
|
8
|
+
def self.deliver(log)
|
|
9
|
+
return unless defined?(::ActionMailer::Base)
|
|
10
|
+
|
|
11
|
+
require 'error_radar/mailers/error_mailer'
|
|
12
|
+
ErrorRadar::ErrorMailer.new_error(log).deliver_later
|
|
13
|
+
rescue StandardError => e
|
|
14
|
+
ErrorRadar::Tracking.warn_internal("Email notification failed: #{e.message}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module ErrorRadar
|
|
8
|
+
module Notifications
|
|
9
|
+
module Slack
|
|
10
|
+
SEV_EMOJI = {
|
|
11
|
+
'critical' => ':red_circle:',
|
|
12
|
+
'error' => ':large_orange_circle:',
|
|
13
|
+
'warning' => ':large_yellow_circle:',
|
|
14
|
+
'info' => ':large_blue_circle:'
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.deliver(log)
|
|
18
|
+
url = URI(ErrorRadar.config.slack_webhook_url)
|
|
19
|
+
payload = build_payload(log)
|
|
20
|
+
|
|
21
|
+
http = Net::HTTP.new(url.host, url.port)
|
|
22
|
+
http.use_ssl = url.scheme == 'https'
|
|
23
|
+
http.open_timeout = 5
|
|
24
|
+
http.read_timeout = 5
|
|
25
|
+
|
|
26
|
+
req = Net::HTTP::Post.new(url)
|
|
27
|
+
req['Content-Type'] = 'application/json'
|
|
28
|
+
req.body = payload.to_json
|
|
29
|
+
|
|
30
|
+
http.request(req)
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
ErrorRadar::Tracking.warn_internal("Slack notification failed: #{e.message}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.build_payload(log)
|
|
36
|
+
emoji = SEV_EMOJI[log.severity] || ':white_circle:'
|
|
37
|
+
title = "#{emoji} #{log.new_fingerprint? ? 'New error' : 'Critical error'}: *#{log.error_class}*"
|
|
38
|
+
url = ErrorRadar::Notifier.error_url(log)
|
|
39
|
+
app = ErrorRadar::Notifier.app_name
|
|
40
|
+
channel = ErrorRadar.config.slack_channel
|
|
41
|
+
|
|
42
|
+
blocks = [
|
|
43
|
+
{ type: 'section', text: { type: 'mrkdwn', text: "*[#{app}]* #{title}" } },
|
|
44
|
+
{
|
|
45
|
+
type: 'section',
|
|
46
|
+
fields: [
|
|
47
|
+
{ type: 'mrkdwn', text: "*Source*\n#{(log.source || 'unknown').truncate(80)}" },
|
|
48
|
+
{ type: 'mrkdwn', text: "*Category*\n#{log.category}" },
|
|
49
|
+
{ type: 'mrkdwn', text: "*Severity*\n#{log.severity}" },
|
|
50
|
+
{ type: 'mrkdwn', text: "*Occurrences*\n#{log.occurrences}" }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: 'section',
|
|
55
|
+
text: { type: 'mrkdwn', text: "*Message*\n```#{log.message.to_s.truncate(400)}```" }
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
if url
|
|
60
|
+
blocks << {
|
|
61
|
+
type: 'actions',
|
|
62
|
+
elements: [{
|
|
63
|
+
type: 'button',
|
|
64
|
+
text: { type: 'plain_text', text: 'View in Error Radar', emoji: true },
|
|
65
|
+
url: url
|
|
66
|
+
}]
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
payload = { blocks: blocks, text: "[#{app}] #{log.error_class}: #{log.message.to_s.truncate(200)}" }
|
|
71
|
+
payload[:channel] = channel if channel.to_s != ''
|
|
72
|
+
payload
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module ErrorRadar
|
|
8
|
+
module Notifications
|
|
9
|
+
# Generic outbound webhook. POSTs a JSON payload to any URL.
|
|
10
|
+
# Useful for PagerDuty, OpsGenie, custom scripts, etc.
|
|
11
|
+
module Webhook
|
|
12
|
+
def self.deliver(log, url:)
|
|
13
|
+
uri = URI(url)
|
|
14
|
+
|
|
15
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
16
|
+
http.use_ssl = uri.scheme == 'https'
|
|
17
|
+
http.open_timeout = 5
|
|
18
|
+
http.read_timeout = 5
|
|
19
|
+
|
|
20
|
+
req = Net::HTTP::Post.new(uri)
|
|
21
|
+
req['Content-Type'] = 'application/json'
|
|
22
|
+
req['User-Agent'] = "ErrorRadar/#{ErrorRadar::VERSION}"
|
|
23
|
+
req.body = build_payload(log).to_json
|
|
24
|
+
|
|
25
|
+
http.request(req)
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
ErrorRadar::Tracking.warn_internal("Webhook (#{url}) notification failed: #{e.message}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.build_payload(log)
|
|
31
|
+
{
|
|
32
|
+
event: log.new_fingerprint? ? 'new_error' : 'recurring_error',
|
|
33
|
+
app: ErrorRadar::Notifier.app_name,
|
|
34
|
+
error_class: log.error_class,
|
|
35
|
+
source: log.source,
|
|
36
|
+
category: log.category,
|
|
37
|
+
severity: log.severity,
|
|
38
|
+
message: log.message.to_s.truncate(500),
|
|
39
|
+
occurrences: log.occurrences,
|
|
40
|
+
fingerprint: log.fingerprint,
|
|
41
|
+
first_seen_at: log.first_seen_at&.iso8601,
|
|
42
|
+
last_seen_at: log.last_seen_at&.iso8601,
|
|
43
|
+
url: ErrorRadar::Notifier.error_url(log)
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ErrorRadar
|
|
4
|
+
# Dispatches alert notifications (Slack, Discord, email, webhooks, custom
|
|
5
|
+
# callbacks) after an ErrorLog is persisted. Called from Tracking.capture and
|
|
6
|
+
# Tracking.notify. Never raises — a broken notification must not affect the
|
|
7
|
+
# host application.
|
|
8
|
+
module Notifier
|
|
9
|
+
THROTTLE_MUTEX = Mutex.new
|
|
10
|
+
# fingerprint => Time of last notification (in-memory, resets on restart)
|
|
11
|
+
THROTTLE = {}
|
|
12
|
+
THROTTLE_INTERVAL = 3600 # seconds between repeat alerts per fingerprint
|
|
13
|
+
|
|
14
|
+
def self.dispatch(log)
|
|
15
|
+
return unless ErrorRadar.config.enabled
|
|
16
|
+
return unless should_fire?(log)
|
|
17
|
+
|
|
18
|
+
cfg = ErrorRadar.config
|
|
19
|
+
send_slack(log) if cfg.slack_webhook_url.to_s.start_with?('http')
|
|
20
|
+
send_discord(log) if cfg.discord_webhook_url.to_s.start_with?('http')
|
|
21
|
+
send_email(log) if cfg.email_recipients.any?
|
|
22
|
+
cfg.webhook_urls.each { |url| send_webhook(log, url) }
|
|
23
|
+
cfg.error_callbacks.each { |cb| safe_call(cb, log) }
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
ErrorRadar::Tracking.warn_internal("Notifier.dispatch failed: #{e.message}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ── Fire decision ──────────────────────────────────────────────────────
|
|
29
|
+
def self.should_fire?(log)
|
|
30
|
+
rules = Array(ErrorRadar.config.notify_on).map(&:to_sym)
|
|
31
|
+
return false if rules.empty?
|
|
32
|
+
|
|
33
|
+
# :new_error — fire exactly once per fingerprint (no throttle needed)
|
|
34
|
+
return true if rules.include?(:new_error) && log.new_fingerprint?
|
|
35
|
+
|
|
36
|
+
# :critical / :all — fire for recurring events, subject to throttle
|
|
37
|
+
matches_severity = rules.include?(:all) ||
|
|
38
|
+
(rules.include?(:critical) && log.severity_critical?)
|
|
39
|
+
matches_severity && throttle_ok?(log.fingerprint)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.throttle_ok?(fingerprint)
|
|
43
|
+
THROTTLE_MUTEX.synchronize do
|
|
44
|
+
last = THROTTLE[fingerprint]
|
|
45
|
+
ok = last.nil? || (Time.current - last) >= THROTTLE_INTERVAL
|
|
46
|
+
THROTTLE[fingerprint] = Time.current if ok
|
|
47
|
+
ok
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# ── Channel dispatchers ────────────────────────────────────────────────
|
|
52
|
+
def self.send_slack(log)
|
|
53
|
+
require 'error_radar/notifications/slack'
|
|
54
|
+
Notifications::Slack.deliver(log)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.send_discord(log)
|
|
58
|
+
require 'error_radar/notifications/discord'
|
|
59
|
+
Notifications::Discord.deliver(log)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.send_email(log)
|
|
63
|
+
require 'error_radar/notifications/email'
|
|
64
|
+
Notifications::Email.deliver(log)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.send_webhook(log, url)
|
|
68
|
+
require 'error_radar/notifications/webhook'
|
|
69
|
+
Notifications::Webhook.deliver(log, url: url)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.safe_call(cb, log)
|
|
73
|
+
cb.call(log)
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
ErrorRadar::Tracking.warn_internal("on_error callback failed: #{e.message}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Build a deep-link URL to the error detail page.
|
|
79
|
+
def self.error_url(log)
|
|
80
|
+
host = ErrorRadar.config.app_host.to_s.chomp('/')
|
|
81
|
+
return nil if host.empty?
|
|
82
|
+
|
|
83
|
+
"#{host}/error_radar/errors/#{log.id}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.app_name
|
|
87
|
+
ErrorRadar.config.app_name ||
|
|
88
|
+
(defined?(Rails) && Rails.application ? Rails.application.class.module_parent_name : 'App')
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/error_radar/tracking.rb
CHANGED
|
@@ -27,7 +27,9 @@ module ErrorRadar
|
|
|
27
27
|
attrs.merge!(extra.compact) if extra.is_a?(Hash)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
ErrorRadar::ErrorLog.record(**attrs)
|
|
30
|
+
log = ErrorRadar::ErrorLog.record(**attrs)
|
|
31
|
+
ErrorRadar::Notifier.dispatch(log) if log
|
|
32
|
+
log
|
|
31
33
|
rescue StandardError => e
|
|
32
34
|
warn_internal("capture failed: #{e.class}: #{e.message}")
|
|
33
35
|
nil
|
|
@@ -47,7 +49,9 @@ module ErrorRadar
|
|
|
47
49
|
def notify(message, category: :application, severity: :error, source: nil, context: {})
|
|
48
50
|
return nil unless ErrorRadar.config.enabled
|
|
49
51
|
|
|
50
|
-
ErrorRadar::ErrorLog.record(category: category, severity: severity, message: message, source: source, context: context)
|
|
52
|
+
log = ErrorRadar::ErrorLog.record(category: category, severity: severity, message: message, source: source, context: context)
|
|
53
|
+
ErrorRadar::Notifier.dispatch(log) if log
|
|
54
|
+
log
|
|
51
55
|
rescue StandardError => e
|
|
52
56
|
warn_internal("notify failed: #{e.class}: #{e.message}")
|
|
53
57
|
nil
|
data/lib/error_radar/version.rb
CHANGED
data/lib/error_radar.rb
CHANGED
|
@@ -8,9 +8,38 @@ ErrorRadar.configure do |config|
|
|
|
8
8
|
config.enabled = !Rails.env.test?
|
|
9
9
|
|
|
10
10
|
# --- Integrations (auto-detected; flip off if you don't want them) ---
|
|
11
|
-
config.install_middleware = true # Rack: capture web-layer exceptions
|
|
12
|
-
config.install_sidekiq = true # capture Sidekiq job failures (if Sidekiq present)
|
|
13
|
-
config.
|
|
11
|
+
config.install_middleware = true # Rack: capture web-layer exceptions (auto)
|
|
12
|
+
config.install_sidekiq = true # capture Sidekiq job failures (auto, if Sidekiq present)
|
|
13
|
+
config.install_active_job = true # capture ActiveJob failures for any adapter (auto)
|
|
14
|
+
config.install_rake = true # capture Rake task failures (auto)
|
|
15
|
+
config.install_rails_admin = true # register the ErrorLog board (auto, if RailsAdmin present)
|
|
16
|
+
|
|
17
|
+
# --- Notifications ---
|
|
18
|
+
# When to fire: :new_error (first occurrence of a fingerprint, default),
|
|
19
|
+
# :critical (any critical severity, throttled to 1/hour),
|
|
20
|
+
# :all (every occurrence, throttled to 1/hour per fingerprint)
|
|
21
|
+
config.notify_on = [:new_error]
|
|
22
|
+
|
|
23
|
+
# Slack incoming webhook (https://api.slack.com/messaging/webhooks)
|
|
24
|
+
# config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
|
|
25
|
+
# config.slack_channel = '#errors' # optional override
|
|
26
|
+
|
|
27
|
+
# Discord incoming webhook
|
|
28
|
+
# config.discord_webhook_url = ENV['DISCORD_WEBHOOK_URL']
|
|
29
|
+
|
|
30
|
+
# Email (requires ActionMailer to be configured in the host app)
|
|
31
|
+
# config.email_recipients = ['dev@myapp.com', 'oncall@myapp.com']
|
|
32
|
+
# config.email_from = 'errors@myapp.com'
|
|
33
|
+
|
|
34
|
+
# Generic webhook — POST JSON to any URL (PagerDuty, OpsGenie, custom scripts)
|
|
35
|
+
# config.webhook_urls = [ENV['PAGERDUTY_WEBHOOK_URL']]
|
|
36
|
+
|
|
37
|
+
# Base URL used to generate deep-links in notifications
|
|
38
|
+
# config.app_host = 'https://myapp.com'
|
|
39
|
+
# config.app_name = 'MyApp' # shown in notification title/subject
|
|
40
|
+
|
|
41
|
+
# Custom callback — runs after all built-in channels
|
|
42
|
+
# config.on_error { |error_log| MyPager.create_incident(error_log) }
|
|
14
43
|
|
|
15
44
|
# --- Dashboard access control ---
|
|
16
45
|
# Run as a before_action; raise/redirect inside to deny. Example with Devise:
|
|
@@ -45,10 +74,6 @@ ErrorRadar.configure do |config|
|
|
|
45
74
|
# ]
|
|
46
75
|
end
|
|
47
76
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# Sidekiq
|
|
51
|
-
#
|
|
52
|
-
# class ApplicationJob < ActiveJob::Base
|
|
53
|
-
# include ErrorRadar::Integrations::ActiveJob
|
|
54
|
-
# end
|
|
77
|
+
# ActiveJob is now captured automatically via install_active_job = true above.
|
|
78
|
+
# No changes needed in ApplicationJob. Set install_active_job = false if you
|
|
79
|
+
# only use Sidekiq and want to avoid double-counting.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: error_radar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- chienbn9x
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -59,10 +59,16 @@ files:
|
|
|
59
59
|
- Rakefile
|
|
60
60
|
- app/controllers/error_radar/application_controller.rb
|
|
61
61
|
- app/controllers/error_radar/dashboard_controller.rb
|
|
62
|
+
- app/controllers/error_radar/errors_controller.rb
|
|
63
|
+
- app/mailers/error_radar/error_mailer.rb
|
|
62
64
|
- app/models/error_radar/application_record.rb
|
|
63
65
|
- app/models/error_radar/error_log.rb
|
|
64
66
|
- app/views/error_radar/dashboard/index.html.erb
|
|
65
67
|
- app/views/error_radar/dashboard/show.html.erb
|
|
68
|
+
- app/views/error_radar/error_mailer/new_error.html.erb
|
|
69
|
+
- app/views/error_radar/error_mailer/new_error.text.erb
|
|
70
|
+
- app/views/error_radar/errors/index.html.erb
|
|
71
|
+
- app/views/error_radar/errors/show.html.erb
|
|
66
72
|
- app/views/layouts/error_radar/application.html.erb
|
|
67
73
|
- config/routes.rb
|
|
68
74
|
- lib/error_radar.rb
|
|
@@ -70,8 +76,14 @@ files:
|
|
|
70
76
|
- lib/error_radar/engine.rb
|
|
71
77
|
- lib/error_radar/integrations/active_job.rb
|
|
72
78
|
- lib/error_radar/integrations/rails_admin.rb
|
|
79
|
+
- lib/error_radar/integrations/rake.rb
|
|
73
80
|
- lib/error_radar/integrations/sidekiq.rb
|
|
74
81
|
- lib/error_radar/middleware.rb
|
|
82
|
+
- lib/error_radar/notifications/discord.rb
|
|
83
|
+
- lib/error_radar/notifications/email.rb
|
|
84
|
+
- lib/error_radar/notifications/slack.rb
|
|
85
|
+
- lib/error_radar/notifications/webhook.rb
|
|
86
|
+
- lib/error_radar/notifier.rb
|
|
75
87
|
- lib/error_radar/server_monitor.rb
|
|
76
88
|
- lib/error_radar/tracking.rb
|
|
77
89
|
- lib/error_radar/version.rb
|