findbug 0.3.4 → 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.
@@ -12,6 +12,12 @@ module Findbug
12
12
  # 3. Dispatcher checks throttling (avoid spam)
13
13
  # 4. Dispatcher sends to enabled channels (async via AlertJob)
14
14
  #
15
+ # CHANNEL SOURCE
16
+ # ==============
17
+ #
18
+ # Alert channels are stored in the database (findbug_alert_channels table)
19
+ # and managed via the dashboard UI at /findbug/alerts.
20
+ #
15
21
  # THROTTLING
16
22
  # ==========
17
23
  #
@@ -36,7 +42,7 @@ module Findbug
36
42
  #
37
43
  def notify(error_event, async: true)
38
44
  return unless Findbug.enabled?
39
- return unless Findbug.config.alerts.any_enabled?
45
+ return unless any_enabled?
40
46
  return unless should_alert?(error_event)
41
47
  return if throttled?(error_event)
42
48
 
@@ -54,17 +60,23 @@ module Findbug
54
60
  # @param error_event [ErrorEvent] the error to alert about
55
61
  #
56
62
  def send_alerts(error_event)
57
- alert_config = Findbug.config.alerts
58
-
59
- alert_config.enabled_channels.each do |channel_name, config|
60
- send_to_channel(channel_name, error_event, config)
63
+ Findbug::AlertChannel.enabled.find_each do |channel_record|
64
+ channel_instance = channel_record.channel_class.new(channel_record.config.symbolize_keys)
65
+ channel_instance.send_alert(error_event)
61
66
  rescue StandardError => e
62
67
  Findbug.logger.error(
63
- "[Findbug] Failed to send alert to #{channel_name}: #{e.message}"
68
+ "[Findbug] Failed to send alert to #{channel_record.name}: #{e.message}"
64
69
  )
65
70
  end
66
71
  end
67
72
 
73
+ # Check if any alert channels are enabled in the database
74
+ def any_enabled?
75
+ Findbug::AlertChannel.enabled.exists?
76
+ rescue StandardError
77
+ false
78
+ end
79
+
68
80
  private
69
81
 
70
82
  # Check if we should alert for this error
@@ -94,32 +106,6 @@ module Findbug
94
106
  def record_alert(error_event)
95
107
  Throttler.record(error_event.fingerprint)
96
108
  end
97
-
98
- # Send to a specific channel
99
- def send_to_channel(channel_name, error_event, config)
100
- channel_class = channel_for(channel_name)
101
- return unless channel_class
102
-
103
- channel = channel_class.new(config)
104
- channel.send_alert(error_event)
105
- end
106
-
107
- # Get the channel class for a channel name
108
- def channel_for(channel_name)
109
- case channel_name.to_sym
110
- when :email
111
- Channels::Email
112
- when :slack
113
- Channels::Slack
114
- when :discord
115
- Channels::Discord
116
- when :webhook
117
- Channels::Webhook
118
- else
119
- Findbug.logger.warn("[Findbug] Unknown alert channel: #{channel_name}")
120
- nil
121
- end
122
- end
123
109
  end
124
110
  end
125
111
  end
@@ -310,70 +310,31 @@ module Findbug
310
310
 
311
311
  # Nested class for alert configuration
312
312
  #
313
- # WHY A SEPARATE CLASS?
314
- # Alerts have their own sub-configuration (multiple channels, each with settings).
315
- # Nesting keeps the main Configuration cleaner.
313
+ # ALERT CHANNELS ARE DB-DRIVEN
314
+ # ============================
315
+ #
316
+ # Alert channels (email, Slack, Discord, webhook) are stored in the database
317
+ # and managed via the dashboard UI at /findbug/alerts.
318
+ #
319
+ # This class only holds global alert settings like throttle_period.
316
320
  #
317
321
  class AlertConfiguration
318
322
  attr_accessor :throttle_period
319
323
 
320
324
  def initialize
321
- @channels = {}
322
325
  @throttle_period = 300 # 5 minutes default
323
326
  end
324
327
 
325
- # Configure email alerts
326
- def email(enabled:, recipients: [], **options)
327
- @channels[:email] = {
328
- enabled: enabled,
329
- recipients: Array(recipients),
330
- **options
331
- }
332
- end
333
-
334
- # Configure Slack alerts
335
- def slack(enabled:, webhook_url: nil, channel: nil, **options)
336
- @channels[:slack] = {
337
- enabled: enabled,
338
- webhook_url: webhook_url,
339
- channel: channel,
340
- **options
341
- }
342
- end
343
-
344
- # Configure Discord alerts
345
- def discord(enabled:, webhook_url: nil, **options)
346
- @channels[:discord] = {
347
- enabled: enabled,
348
- webhook_url: webhook_url,
349
- **options
350
- }
351
- end
352
-
353
- # Configure generic webhook alerts
354
- def webhook(enabled:, url: nil, headers: {}, **options)
355
- @channels[:webhook] = {
356
- enabled: enabled,
357
- url: url,
358
- headers: headers,
359
- **options
360
- }
361
- end
362
-
363
- # Get configuration for a specific channel
364
- def channel(name)
365
- @channels[name.to_sym]
366
- end
367
-
368
- # Get all enabled channels
369
- def enabled_channels
370
- @channels.select { |_, config| config[:enabled] }
371
- end
372
-
373
- # Check if any alerts are configured
374
- def any_enabled?
375
- enabled_channels.any?
376
- end
328
+ # Deprecated DSL methods — kept for backward compatibility so existing
329
+ # initializers don't raise NoMethodError on upgrade. These are no-ops;
330
+ # alert channels are now configured via the dashboard UI.
331
+ def email(**) = nil
332
+ def slack(**) = nil
333
+ def discord(**) = nil
334
+ def webhook(**) = nil
335
+ def channel(_name) = nil
336
+ def enabled_channels = {}
337
+ def any_enabled? = false
377
338
  end
378
339
 
379
340
  # Custom error for configuration issues
@@ -11,6 +11,7 @@ require_relative "../../app/controllers/findbug/application_controller"
11
11
  require_relative "../../app/controllers/findbug/dashboard_controller"
12
12
  require_relative "../../app/controllers/findbug/errors_controller"
13
13
  require_relative "../../app/controllers/findbug/performance_controller"
14
+ require_relative "../../app/controllers/findbug/alerts_controller"
14
15
 
15
16
  module Findbug
16
17
  # Engine is the main Rails integration point for Findbug.
@@ -97,6 +98,14 @@ Findbug::Engine.routes.draw do
97
98
  # Performance
98
99
  resources :performance, only: [:index, :show]
99
100
 
101
+ # Alerts
102
+ resources :alerts, only: [:index, :new, :create, :edit, :update, :destroy] do
103
+ member do
104
+ post :toggle
105
+ post :test
106
+ end
107
+ end
108
+
100
109
  # Health check (useful for monitoring)
101
110
  get "health", to: "dashboard#health"
102
111
 
@@ -60,6 +60,7 @@ module Findbug
60
60
  models_path = File.expand_path("../../app/models/findbug", __dir__)
61
61
  require "#{models_path}/error_event"
62
62
  require "#{models_path}/performance_event"
63
+ require "#{models_path}/alert_channel"
63
64
  end
64
65
 
65
66
  # Register our middleware to catch exceptions
@@ -84,6 +85,13 @@ module Findbug
84
85
  require_relative "capture/middleware"
85
86
 
86
87
  Rails.application.config.middleware.use(Findbug::Capture::Middleware)
88
+
89
+ # Ensure Rack::MethodOverride is available so the dashboard's HTML
90
+ # forms can use PATCH/DELETE via the _method hidden field.
91
+ # API-only Rails apps don't include this middleware by default.
92
+ if Rails.application.config.api_only
93
+ Rails.application.config.middleware.insert_before 0, Rack::MethodOverride
94
+ end
87
95
  end
88
96
 
89
97
  # Set up Rails error reporting integration (Rails 7+)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Findbug
4
- VERSION = "0.3.4"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -49,8 +49,14 @@ module Findbug
49
49
  "db/migrate/create_findbug_performance_events.rb"
50
50
  )
51
51
 
52
+ migration_template(
53
+ "create_findbug_alert_channels.rb",
54
+ "db/migrate/create_findbug_alert_channels.rb"
55
+ )
56
+
52
57
  say_status :create, "db/migrate/create_findbug_error_events.rb", :green
53
58
  say_status :create, "db/migrate/create_findbug_performance_events.rb", :green
59
+ say_status :create, "db/migrate/create_findbug_alert_channels.rb", :green
54
60
  end
55
61
 
56
62
  def display_post_install
@@ -29,18 +29,15 @@ Next steps:
29
29
  FINDBUG_USERNAME=your-username
30
30
  FINDBUG_PASSWORD=your-secure-password
31
31
 
32
- 4. (Optional) Configure alerts in config/initializers/findbug.rb:
32
+ 4. (Optional) Configure alerts via the dashboard:
33
33
 
34
- config.alerts do |alerts|
35
- alerts.slack(
36
- enabled: true,
37
- webhook_url: ENV["SLACK_WEBHOOK_URL"]
38
- )
39
- end
34
+ http://localhost:3000/findbug/alerts
35
+
36
+ Add Email, Slack, Discord, or Webhook channels directly from the UI.
40
37
 
41
38
  5. (Multi-tenant apps) If using Apartment/ros-apartment, add to apartment.rb:
42
39
 
43
- config.excluded_models = %w[Findbug::ErrorEvent Findbug::PerformanceEvent]
40
+ config.excluded_models = %w[Findbug::ErrorEvent Findbug::PerformanceEvent Findbug::AlertChannel]
44
41
 
45
42
  And exclude /findbug from your tenant switching middleware.
46
43
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateFindbugAlertChannels < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :findbug_alert_channels do |t|
6
+ # Channel identification
7
+ t.string :channel_type, null: false # email, slack, discord, webhook
8
+ t.string :name, null: false # user-friendly label
9
+
10
+ # Status
11
+ t.boolean :enabled, default: false
12
+
13
+ # Configuration (JSON-serialized, encrypted at rest)
14
+ # Using text instead of jsonb so Rails encryption can work on it
15
+ t.text :config_data
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ # Indexes for common queries
21
+ add_index :findbug_alert_channels, :channel_type
22
+ add_index :findbug_alert_channels, :enabled
23
+ end
24
+ end
@@ -113,35 +113,12 @@ Findbug.configure do |config|
113
113
  # ALERTS
114
114
  # ============================================================================
115
115
 
116
+ # Alert channels (Email, Slack, Discord, Webhook) are configured via the
117
+ # dashboard UI at /findbug/alerts — no code changes needed.
118
+
116
119
  config.alerts do |alerts|
117
120
  # Throttle period - don't alert for same error more than once in this period
118
121
  alerts.throttle_period = 5.minutes
119
-
120
- # Email alerts
121
- # alerts.email(
122
- # enabled: true,
123
- # recipients: ["dev-team@example.com"]
124
- # )
125
-
126
- # Slack alerts
127
- # alerts.slack(
128
- # enabled: true,
129
- # webhook_url: ENV["SLACK_WEBHOOK_URL"],
130
- # channel: "#errors" # optional
131
- # )
132
-
133
- # Discord alerts
134
- # alerts.discord(
135
- # enabled: true,
136
- # webhook_url: ENV["DISCORD_WEBHOOK_URL"]
137
- # )
138
-
139
- # Generic webhook
140
- # alerts.webhook(
141
- # enabled: true,
142
- # url: "https://your-service.com/findbug-webhook",
143
- # headers: { "Authorization" => "Bearer #{ENV['WEBHOOK_TOKEN']}" }
144
- # )
145
122
  end
146
123
 
147
124
  # ============================================================================
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Findbug
7
+ module Generators
8
+ # UpgradeGenerator creates any missing migrations for existing Findbug installations.
9
+ #
10
+ # Usage:
11
+ # rails generate findbug:upgrade
12
+ #
13
+ # This is safe to run multiple times — it only creates migrations that don't
14
+ # already exist. Use this when upgrading Findbug to a new version that adds
15
+ # new database tables.
16
+ #
17
+ class UpgradeGenerator < Rails::Generators::Base
18
+ include Rails::Generators::Migration
19
+
20
+ source_root File.expand_path("templates", __dir__)
21
+
22
+ desc "Upgrade Findbug: creates any missing migrations"
23
+
24
+ def self.next_migration_number(dirname)
25
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
26
+ end
27
+
28
+ def create_missing_migrations
29
+ create_migration_if_missing(
30
+ "create_findbug_alert_channels.rb",
31
+ "db/migrate/create_findbug_alert_channels.rb"
32
+ )
33
+ end
34
+
35
+ def display_next_steps
36
+ return unless behavior == :invoke
37
+
38
+ say ""
39
+ say "=================================================================", :green
40
+ say " Findbug upgrade complete!", :green
41
+ say "=================================================================", :green
42
+ say ""
43
+ say "Next steps:"
44
+ say ""
45
+ say " 1. Run migrations: rails db:migrate"
46
+ say ""
47
+ say " 2. Configure alerts via the dashboard:"
48
+ say " http://localhost:3000/findbug/alerts"
49
+ say ""
50
+ say " 3. (Multi-tenant apps) Add to apartment.rb excluded_models:"
51
+ say " Findbug::AlertChannel"
52
+ say ""
53
+ end
54
+
55
+ private
56
+
57
+ def migration_version
58
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
59
+ end
60
+
61
+ # Only create a migration if no migration with the same class name exists
62
+ def create_migration_if_missing(template_name, destination)
63
+ migration_name = template_name.sub(/\.rb$/, "")
64
+
65
+ if migration_exists?(migration_name)
66
+ say_status :skip, destination, :yellow
67
+ else
68
+ migration_template(template_name, destination)
69
+ say_status :create, destination, :green
70
+ end
71
+ end
72
+
73
+ # Check if a migration file already exists in db/migrate/
74
+ def migration_exists?(migration_name)
75
+ Dir.glob("db/migrate/[0-9]*_#{migration_name}.rb").any?
76
+ end
77
+ end
78
+ end
79
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: findbug
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soumit Das
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-04 00:00:00.000000000 Z
10
+ date: 2026-02-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties
@@ -177,6 +177,7 @@ files:
177
177
  - LICENSE.txt
178
178
  - README.md
179
179
  - Rakefile
180
+ - app/controllers/findbug/alerts_controller.rb
180
181
  - app/controllers/findbug/application_controller.rb
181
182
  - app/controllers/findbug/dashboard_controller.rb
182
183
  - app/controllers/findbug/errors_controller.rb
@@ -184,14 +185,19 @@ files:
184
185
  - app/jobs/findbug/alert_job.rb
185
186
  - app/jobs/findbug/cleanup_job.rb
186
187
  - app/jobs/findbug/persist_job.rb
188
+ - app/models/findbug/alert_channel.rb
187
189
  - app/models/findbug/error_event.rb
188
190
  - app/models/findbug/performance_event.rb
191
+ - app/views/findbug/alerts/edit.html.erb
192
+ - app/views/findbug/alerts/index.html.erb
193
+ - app/views/findbug/alerts/new.html.erb
189
194
  - app/views/findbug/dashboard/index.html.erb
190
195
  - app/views/findbug/errors/index.html.erb
191
196
  - app/views/findbug/errors/show.html.erb
192
197
  - app/views/findbug/performance/index.html.erb
193
198
  - app/views/findbug/performance/show.html.erb
194
199
  - app/views/layouts/findbug/application.html.erb
200
+ - docs/index.html
195
201
  - lib/findbug.rb
196
202
  - lib/findbug/alerts/channels/base.rb
197
203
  - lib/findbug/alerts/channels/discord.rb
@@ -220,15 +226,17 @@ files:
220
226
  - lib/findbug/version.rb
221
227
  - lib/generators/findbug/install_generator.rb
222
228
  - lib/generators/findbug/templates/POST_INSTALL
229
+ - lib/generators/findbug/templates/create_findbug_alert_channels.rb
223
230
  - lib/generators/findbug/templates/create_findbug_error_events.rb
224
231
  - lib/generators/findbug/templates/create_findbug_performance_events.rb
225
232
  - lib/generators/findbug/templates/initializer.rb
233
+ - lib/generators/findbug/upgrade_generator.rb
226
234
  - sig/findbug.rbs
227
- homepage: https://github.com/ITSSOUMIT/findbug
235
+ homepage: https://findbug.dev
228
236
  licenses:
229
237
  - MIT
230
238
  metadata:
231
- homepage_uri: https://github.com/ITSSOUMIT/findbug
239
+ homepage_uri: https://findbug.dev
232
240
  source_code_uri: https://github.com/ITSSOUMIT/findbug
233
241
  changelog_uri: https://github.com/ITSSOUMIT/findbug/blob/main/CHANGELOG.md
234
242
  rdoc_options: []