findbug 0.2.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +375 -0
- data/Rakefile +12 -0
- data/app/controllers/findbug/application_controller.rb +105 -0
- data/app/controllers/findbug/dashboard_controller.rb +93 -0
- data/app/controllers/findbug/errors_controller.rb +129 -0
- data/app/controllers/findbug/performance_controller.rb +80 -0
- data/app/jobs/findbug/alert_job.rb +40 -0
- data/app/jobs/findbug/cleanup_job.rb +132 -0
- data/app/jobs/findbug/persist_job.rb +158 -0
- data/app/models/findbug/error_event.rb +197 -0
- data/app/models/findbug/performance_event.rb +237 -0
- data/app/views/findbug/dashboard/index.html.erb +199 -0
- data/app/views/findbug/errors/index.html.erb +137 -0
- data/app/views/findbug/errors/show.html.erb +185 -0
- data/app/views/findbug/performance/index.html.erb +168 -0
- data/app/views/findbug/performance/show.html.erb +203 -0
- data/app/views/layouts/findbug/application.html.erb +601 -0
- data/lib/findbug/alerts/channels/base.rb +75 -0
- data/lib/findbug/alerts/channels/discord.rb +155 -0
- data/lib/findbug/alerts/channels/email.rb +179 -0
- data/lib/findbug/alerts/channels/slack.rb +149 -0
- data/lib/findbug/alerts/channels/webhook.rb +143 -0
- data/lib/findbug/alerts/dispatcher.rb +126 -0
- data/lib/findbug/alerts/throttler.rb +110 -0
- data/lib/findbug/background_persister.rb +142 -0
- data/lib/findbug/capture/context.rb +301 -0
- data/lib/findbug/capture/exception_handler.rb +141 -0
- data/lib/findbug/capture/exception_subscriber.rb +228 -0
- data/lib/findbug/capture/message_handler.rb +104 -0
- data/lib/findbug/capture/middleware.rb +247 -0
- data/lib/findbug/configuration.rb +381 -0
- data/lib/findbug/engine.rb +109 -0
- data/lib/findbug/performance/instrumentation.rb +336 -0
- data/lib/findbug/performance/transaction.rb +193 -0
- data/lib/findbug/processing/data_scrubber.rb +163 -0
- data/lib/findbug/rails/controller_methods.rb +152 -0
- data/lib/findbug/railtie.rb +222 -0
- data/lib/findbug/storage/circuit_breaker.rb +223 -0
- data/lib/findbug/storage/connection_pool.rb +134 -0
- data/lib/findbug/storage/redis_buffer.rb +285 -0
- data/lib/findbug/tasks/findbug.rake +167 -0
- data/lib/findbug/version.rb +5 -0
- data/lib/findbug.rb +216 -0
- data/lib/generators/findbug/install_generator.rb +67 -0
- data/lib/generators/findbug/templates/POST_INSTALL +41 -0
- data/lib/generators/findbug/templates/create_findbug_error_events.rb +44 -0
- data/lib/generators/findbug/templates/create_findbug_performance_events.rb +47 -0
- data/lib/generators/findbug/templates/initializer.rb +157 -0
- data/sig/findbug.rbs +4 -0
- metadata +251 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Findbug Rake Tasks
|
|
4
|
+
#
|
|
5
|
+
# These tasks help with maintenance and debugging.
|
|
6
|
+
# Run `rake -T findbug` to see all available tasks.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
namespace :findbug do
|
|
10
|
+
desc "Show Findbug configuration and status"
|
|
11
|
+
task status: :environment do
|
|
12
|
+
config = Findbug.config
|
|
13
|
+
|
|
14
|
+
puts "\n=== Findbug Status ==="
|
|
15
|
+
puts ""
|
|
16
|
+
puts "Enabled: #{Findbug.enabled? ? 'Yes' : 'No'}"
|
|
17
|
+
puts "Environment: #{config.environment}"
|
|
18
|
+
puts "Release: #{config.release || '(not set)'}"
|
|
19
|
+
puts ""
|
|
20
|
+
|
|
21
|
+
puts "--- Error Capture ---"
|
|
22
|
+
puts "Sample Rate: #{(config.sample_rate * 100).round(1)}%"
|
|
23
|
+
puts "Ignored Exceptions: #{config.ignored_exceptions.map(&:name).join(', ').presence || '(none)'}"
|
|
24
|
+
puts ""
|
|
25
|
+
|
|
26
|
+
puts "--- Performance ---"
|
|
27
|
+
puts "Performance Enabled: #{config.performance_enabled ? 'Yes' : 'No'}"
|
|
28
|
+
puts "Performance Sample: #{(config.performance_sample_rate * 100).round(1)}%"
|
|
29
|
+
puts "Slow Request Threshold: #{config.slow_request_threshold_ms}ms"
|
|
30
|
+
puts "Slow Query Threshold: #{config.slow_query_threshold_ms}ms"
|
|
31
|
+
puts ""
|
|
32
|
+
|
|
33
|
+
puts "--- Storage ---"
|
|
34
|
+
puts "Redis URL: #{config.redis_url.gsub(/:[^@]+@/, ':***@')}" # Hide password
|
|
35
|
+
puts "Redis Pool Size: #{config.redis_pool_size}"
|
|
36
|
+
puts "Retention Days: #{config.retention_days}"
|
|
37
|
+
puts ""
|
|
38
|
+
|
|
39
|
+
puts "--- Dashboard ---"
|
|
40
|
+
puts "Dashboard Enabled: #{config.web_enabled? ? 'Yes' : 'No'}"
|
|
41
|
+
puts "Dashboard Path: #{config.web_path}" if config.web_enabled?
|
|
42
|
+
puts ""
|
|
43
|
+
|
|
44
|
+
puts "--- Alerts ---"
|
|
45
|
+
if config.alerts.any_enabled?
|
|
46
|
+
config.alerts.enabled_channels.each do |name, _|
|
|
47
|
+
puts " #{name}: enabled"
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
puts "(no alerts configured)"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts ""
|
|
54
|
+
|
|
55
|
+
# Show Redis buffer stats if available
|
|
56
|
+
if Findbug.enabled?
|
|
57
|
+
puts "--- Buffer Status ---"
|
|
58
|
+
begin
|
|
59
|
+
stats = Findbug::Storage::RedisBuffer.stats
|
|
60
|
+
puts "Error Queue Length: #{stats[:error_queue_length]}"
|
|
61
|
+
puts "Performance Queue Length: #{stats[:performance_queue_length]}"
|
|
62
|
+
puts "Circuit Breaker State: #{stats[:circuit_breaker_state]}"
|
|
63
|
+
puts "Circuit Breaker Failures: #{stats[:circuit_breaker_failures]}"
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
puts "Could not fetch buffer stats: #{e.message}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
puts "\n"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc "Clear all Findbug data from Redis buffers"
|
|
73
|
+
task clear_buffers: :environment do
|
|
74
|
+
puts "Clearing Findbug Redis buffers..."
|
|
75
|
+
Findbug::Storage::RedisBuffer.clear!
|
|
76
|
+
puts "Done!"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc "Flush Redis buffers to database immediately"
|
|
80
|
+
task flush: :environment do
|
|
81
|
+
puts "Flushing Findbug buffers to database..."
|
|
82
|
+
|
|
83
|
+
require_relative "../jobs/persist_job"
|
|
84
|
+
|
|
85
|
+
error_count = 0
|
|
86
|
+
perf_count = 0
|
|
87
|
+
|
|
88
|
+
loop do
|
|
89
|
+
events = Findbug::Storage::RedisBuffer.pop_errors(100)
|
|
90
|
+
break if events.empty?
|
|
91
|
+
|
|
92
|
+
Findbug::Jobs::PersistJob.persist_errors(events)
|
|
93
|
+
error_count += events.size
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
loop do
|
|
97
|
+
events = Findbug::Storage::RedisBuffer.pop_performance(100)
|
|
98
|
+
break if events.empty?
|
|
99
|
+
|
|
100
|
+
Findbug::Jobs::PersistJob.persist_performance(events)
|
|
101
|
+
perf_count += events.size
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
puts "Flushed #{error_count} error events and #{perf_count} performance events."
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
desc "Run cleanup to remove old records"
|
|
108
|
+
task cleanup: :environment do
|
|
109
|
+
puts "Running Findbug cleanup..."
|
|
110
|
+
|
|
111
|
+
require_relative "../jobs/cleanup_job"
|
|
112
|
+
Findbug::Jobs::CleanupJob.perform_now
|
|
113
|
+
|
|
114
|
+
puts "Done!"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
desc "Test error capture by raising a test exception"
|
|
118
|
+
task test: :environment do
|
|
119
|
+
puts "Testing Findbug error capture..."
|
|
120
|
+
|
|
121
|
+
# Capture a test exception
|
|
122
|
+
begin
|
|
123
|
+
raise "Findbug Test Exception - #{Time.now}"
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
Findbug.capture_exception(e, test: true)
|
|
126
|
+
puts "Test exception captured!"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Wait for async write
|
|
130
|
+
sleep 0.5
|
|
131
|
+
|
|
132
|
+
# Check if it made it to Redis
|
|
133
|
+
stats = Findbug::Storage::RedisBuffer.stats
|
|
134
|
+
puts "Error queue length: #{stats[:error_queue_length]}"
|
|
135
|
+
|
|
136
|
+
if stats[:error_queue_length].positive?
|
|
137
|
+
puts "\nTest passed! Exception was captured."
|
|
138
|
+
else
|
|
139
|
+
puts "\nTest may have failed. Check configuration."
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
namespace :db do
|
|
144
|
+
desc "Show database record counts"
|
|
145
|
+
task stats: :environment do
|
|
146
|
+
puts "\n=== Findbug Database Stats ==="
|
|
147
|
+
|
|
148
|
+
if defined?(Findbug::ErrorEvent)
|
|
149
|
+
total_errors = Findbug::ErrorEvent.count
|
|
150
|
+
unresolved = Findbug::ErrorEvent.where(status: "unresolved").count
|
|
151
|
+
puts "Total Errors: #{total_errors}"
|
|
152
|
+
puts "Unresolved: #{unresolved}"
|
|
153
|
+
else
|
|
154
|
+
puts "ErrorEvent model not loaded"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if defined?(Findbug::PerformanceEvent)
|
|
158
|
+
total_perf = Findbug::PerformanceEvent.count
|
|
159
|
+
puts "Performance Events: #{total_perf}"
|
|
160
|
+
else
|
|
161
|
+
puts "PerformanceEvent model not loaded"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
puts ""
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/findbug.rb
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "findbug/version"
|
|
4
|
+
require_relative "findbug/configuration"
|
|
5
|
+
|
|
6
|
+
# Findbug - Self-hosted error tracking and performance monitoring for Rails
|
|
7
|
+
#
|
|
8
|
+
# ARCHITECTURE OVERVIEW
|
|
9
|
+
# =====================
|
|
10
|
+
#
|
|
11
|
+
# Findbug is designed with ONE critical goal: NEVER slow down your application.
|
|
12
|
+
#
|
|
13
|
+
# How we achieve this:
|
|
14
|
+
#
|
|
15
|
+
# 1. ASYNC WRITES
|
|
16
|
+
# When an error occurs, we don't write to the database immediately.
|
|
17
|
+
# Instead, we push to a Redis buffer in a background thread.
|
|
18
|
+
# This takes ~1-2ms and doesn't block your request.
|
|
19
|
+
#
|
|
20
|
+
# 2. BACKGROUND PERSISTENCE
|
|
21
|
+
# A periodic job (via ActiveJob or built-in thread) pulls events from Redis
|
|
22
|
+
# and batch-inserts them to the database. This happens outside your
|
|
23
|
+
# request cycle.
|
|
24
|
+
#
|
|
25
|
+
# 3. CIRCUIT BREAKER
|
|
26
|
+
# If Redis is down, we don't keep retrying and slowing down your app.
|
|
27
|
+
# The circuit breaker "opens" after 5 failures and stops attempting
|
|
28
|
+
# writes for 30 seconds.
|
|
29
|
+
#
|
|
30
|
+
# 4. CONNECTION POOLING
|
|
31
|
+
# We maintain our OWN Redis connection pool, separate from your app's
|
|
32
|
+
# Redis/Sidekiq. This prevents connection contention.
|
|
33
|
+
#
|
|
34
|
+
# 5. SAMPLING
|
|
35
|
+
# For high-traffic apps, you can sample errors (e.g., capture 50%)
|
|
36
|
+
# to reduce overhead further.
|
|
37
|
+
#
|
|
38
|
+
# DATA FLOW
|
|
39
|
+
# =========
|
|
40
|
+
#
|
|
41
|
+
# [Exception occurs]
|
|
42
|
+
# |
|
|
43
|
+
# v
|
|
44
|
+
# [Middleware catches it]
|
|
45
|
+
# |
|
|
46
|
+
# v
|
|
47
|
+
# [Scrub sensitive data]
|
|
48
|
+
# |
|
|
49
|
+
# v
|
|
50
|
+
# [Push to Redis buffer] <-- Async, non-blocking (Thread.new)
|
|
51
|
+
# |
|
|
52
|
+
# v
|
|
53
|
+
# [BackgroundPersister runs every 30s]
|
|
54
|
+
# |
|
|
55
|
+
# v
|
|
56
|
+
# [Batch insert to Database]
|
|
57
|
+
# |
|
|
58
|
+
# v
|
|
59
|
+
# [Dashboard displays data]
|
|
60
|
+
#
|
|
61
|
+
module Findbug
|
|
62
|
+
# Base error class for Findbug-specific exceptions
|
|
63
|
+
class Error < StandardError; end
|
|
64
|
+
|
|
65
|
+
class << self
|
|
66
|
+
# Access the configuration object
|
|
67
|
+
#
|
|
68
|
+
# @return [Configuration] the current configuration
|
|
69
|
+
#
|
|
70
|
+
# WHY A CLASS METHOD?
|
|
71
|
+
# -------------------
|
|
72
|
+
# We use `Findbug.config` instead of a global variable because:
|
|
73
|
+
# 1. It's lazily initialized (created on first access)
|
|
74
|
+
# 2. It's thread-safe (||= is atomic in MRI Ruby)
|
|
75
|
+
# 3. It's mockable in tests
|
|
76
|
+
# 4. It follows Ruby conventions (like Rails.config)
|
|
77
|
+
#
|
|
78
|
+
def config
|
|
79
|
+
@config ||= Configuration.new
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Configure Findbug with a block
|
|
83
|
+
#
|
|
84
|
+
# @yield [Configuration] the configuration object
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# Findbug.configure do |config|
|
|
88
|
+
# config.redis_url = "redis://localhost:6379/1"
|
|
89
|
+
# config.sample_rate = 0.5
|
|
90
|
+
#
|
|
91
|
+
# config.alerts do |alerts|
|
|
92
|
+
# alerts.slack enabled: true, webhook_url: ENV["SLACK_WEBHOOK"]
|
|
93
|
+
# end
|
|
94
|
+
# end
|
|
95
|
+
#
|
|
96
|
+
def configure
|
|
97
|
+
yield(config) if block_given?
|
|
98
|
+
config.validate!
|
|
99
|
+
config
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Reset configuration to defaults (useful for testing)
|
|
103
|
+
#
|
|
104
|
+
# WHY EXPOSE THIS?
|
|
105
|
+
# ----------------
|
|
106
|
+
# In tests, you often want to reset state between examples.
|
|
107
|
+
# This makes Findbug test-friendly.
|
|
108
|
+
#
|
|
109
|
+
def reset!
|
|
110
|
+
@config = nil
|
|
111
|
+
@redis_pool = nil
|
|
112
|
+
@logger = nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Get the logger instance
|
|
116
|
+
#
|
|
117
|
+
# Falls back to Rails.logger, then to a null logger
|
|
118
|
+
#
|
|
119
|
+
def logger
|
|
120
|
+
@logger ||= config.logger || (defined?(Rails) && Rails.logger) || Logger.new(IO::NULL)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Set a custom logger
|
|
124
|
+
def logger=(new_logger)
|
|
125
|
+
@logger = new_logger
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check if Findbug is enabled
|
|
129
|
+
#
|
|
130
|
+
# This is a convenience method used throughout the codebase.
|
|
131
|
+
# It checks both the enabled flag AND validates we're properly configured.
|
|
132
|
+
#
|
|
133
|
+
def enabled?
|
|
134
|
+
config.enabled && config.redis_url.present?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Capture an exception manually
|
|
138
|
+
#
|
|
139
|
+
# @param exception [Exception] the exception to capture
|
|
140
|
+
# @param context [Hash] additional context to attach
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# begin
|
|
144
|
+
# risky_operation
|
|
145
|
+
# rescue => e
|
|
146
|
+
# Findbug.capture_exception(e, user_id: current_user.id)
|
|
147
|
+
# raise # re-raise to let Rails handle it
|
|
148
|
+
# end
|
|
149
|
+
#
|
|
150
|
+
# WHY A MANUAL CAPTURE METHOD?
|
|
151
|
+
# ----------------------------
|
|
152
|
+
# Sometimes you want to capture an exception without crashing.
|
|
153
|
+
# For example, in a rescue block where you handle the error
|
|
154
|
+
# gracefully but still want to track it.
|
|
155
|
+
#
|
|
156
|
+
def capture_exception(exception, context = {})
|
|
157
|
+
return unless enabled?
|
|
158
|
+
return unless config.should_capture_exception?(exception)
|
|
159
|
+
|
|
160
|
+
Capture::ExceptionHandler.capture(exception, context)
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
# CRITICAL: Never let Findbug crash your app
|
|
163
|
+
logger.error("[Findbug] Failed to capture exception: #{e.message}")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Capture a message (non-exception event)
|
|
167
|
+
#
|
|
168
|
+
# @param message [String] the message to capture
|
|
169
|
+
# @param level [Symbol] severity level (:info, :warning, :error)
|
|
170
|
+
# @param context [Hash] additional context
|
|
171
|
+
#
|
|
172
|
+
# @example
|
|
173
|
+
# Findbug.capture_message("User exceeded rate limit", :warning, user_id: 123)
|
|
174
|
+
#
|
|
175
|
+
def capture_message(message, level = :info, context = {})
|
|
176
|
+
return unless enabled?
|
|
177
|
+
|
|
178
|
+
Capture::MessageHandler.capture(message, level, context)
|
|
179
|
+
rescue StandardError => e
|
|
180
|
+
logger.error("[Findbug] Failed to capture message: #{e.message}")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Wrap a block with performance tracking
|
|
184
|
+
#
|
|
185
|
+
# @param name [String] name for this operation
|
|
186
|
+
# @yield the block to track
|
|
187
|
+
#
|
|
188
|
+
# @example
|
|
189
|
+
# Findbug.track_performance("external_api_call") do
|
|
190
|
+
# ExternalAPI.fetch_data
|
|
191
|
+
# end
|
|
192
|
+
#
|
|
193
|
+
def track_performance(name, &block)
|
|
194
|
+
return yield unless enabled? && config.performance_enabled
|
|
195
|
+
|
|
196
|
+
Performance::Transaction.track(name, &block)
|
|
197
|
+
rescue StandardError => e
|
|
198
|
+
logger.error("[Findbug] Performance tracking failed: #{e.message}")
|
|
199
|
+
yield # Still execute the block even if tracking fails
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Load core library modules (these stay in lib/ as they're not Rails-autoloadable)
|
|
205
|
+
require_relative "findbug/storage/connection_pool"
|
|
206
|
+
require_relative "findbug/storage/circuit_breaker"
|
|
207
|
+
require_relative "findbug/storage/redis_buffer"
|
|
208
|
+
require_relative "findbug/processing/data_scrubber"
|
|
209
|
+
require_relative "findbug/capture/context"
|
|
210
|
+
require_relative "findbug/capture/exception_handler"
|
|
211
|
+
require_relative "findbug/capture/message_handler"
|
|
212
|
+
require_relative "findbug/capture/middleware"
|
|
213
|
+
|
|
214
|
+
# Load the Railtie if Rails is available
|
|
215
|
+
# This auto-configures Findbug when Rails boots
|
|
216
|
+
require_relative "findbug/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module Findbug
|
|
7
|
+
module Generators
|
|
8
|
+
# InstallGenerator sets up Findbug in a Rails application.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# rails generate findbug:install
|
|
12
|
+
#
|
|
13
|
+
# This will:
|
|
14
|
+
# 1. Create the initializer (config/initializers/findbug.rb)
|
|
15
|
+
# 2. Create database migrations
|
|
16
|
+
# 3. Display next steps
|
|
17
|
+
#
|
|
18
|
+
class InstallGenerator < Rails::Generators::Base
|
|
19
|
+
include Rails::Generators::Migration
|
|
20
|
+
|
|
21
|
+
source_root File.expand_path("templates", __dir__)
|
|
22
|
+
|
|
23
|
+
desc "Install Findbug: creates initializer and migrations"
|
|
24
|
+
|
|
25
|
+
class_option :skip_migrations,
|
|
26
|
+
type: :boolean,
|
|
27
|
+
default: false,
|
|
28
|
+
desc: "Skip creating migrations"
|
|
29
|
+
|
|
30
|
+
def self.next_migration_number(dirname)
|
|
31
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_initializer
|
|
35
|
+
template "initializer.rb", "config/initializers/findbug.rb"
|
|
36
|
+
say_status :create, "config/initializers/findbug.rb", :green
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create_migrations
|
|
40
|
+
return if options[:skip_migrations]
|
|
41
|
+
|
|
42
|
+
migration_template(
|
|
43
|
+
"create_findbug_error_events.rb",
|
|
44
|
+
"db/migrate/create_findbug_error_events.rb"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
migration_template(
|
|
48
|
+
"create_findbug_performance_events.rb",
|
|
49
|
+
"db/migrate/create_findbug_performance_events.rb"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
say_status :create, "db/migrate/create_findbug_error_events.rb", :green
|
|
53
|
+
say_status :create, "db/migrate/create_findbug_performance_events.rb", :green
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def display_post_install
|
|
57
|
+
readme "POST_INSTALL" if behavior == :invoke
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def migration_version
|
|
63
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
===============================================================================
|
|
3
|
+
Findbug has been installed successfully!
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
Next steps:
|
|
7
|
+
|
|
8
|
+
1. Run the migrations:
|
|
9
|
+
|
|
10
|
+
rails db:migrate
|
|
11
|
+
|
|
12
|
+
2. Configure Redis URL (if different from default):
|
|
13
|
+
|
|
14
|
+
# Add to your environment variables or .env file:
|
|
15
|
+
FINDBUG_REDIS_URL=redis://localhost:6379/1
|
|
16
|
+
|
|
17
|
+
# Or set directly in config/initializers/findbug.rb:
|
|
18
|
+
config.redis_url = "redis://localhost:6379/1"
|
|
19
|
+
|
|
20
|
+
3. Configure dashboard authentication (required):
|
|
21
|
+
|
|
22
|
+
# Add to your environment variables or .env file:
|
|
23
|
+
FINDBUG_USERNAME=admin
|
|
24
|
+
FINDBUG_PASSWORD=your-secure-password
|
|
25
|
+
|
|
26
|
+
5. Access the dashboard:
|
|
27
|
+
|
|
28
|
+
http://localhost:3000/findbug
|
|
29
|
+
|
|
30
|
+
6. (Optional) Configure alerts in config/initializers/findbug.rb:
|
|
31
|
+
|
|
32
|
+
config.alerts do |alerts|
|
|
33
|
+
alerts.slack(
|
|
34
|
+
enabled: true,
|
|
35
|
+
webhook_url: ENV["SLACK_WEBHOOK_URL"]
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
For more information, see: https://github.com/ITSSOUMIT/findbug
|
|
40
|
+
|
|
41
|
+
===============================================================================
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateFindbugErrorEvents < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :findbug_error_events do |t|
|
|
6
|
+
# Error identification
|
|
7
|
+
t.string :fingerprint, null: false
|
|
8
|
+
t.string :exception_class, null: false
|
|
9
|
+
t.text :message
|
|
10
|
+
t.text :backtrace
|
|
11
|
+
|
|
12
|
+
# Context (stored as JSON for flexibility)
|
|
13
|
+
t.jsonb :context, default: {}
|
|
14
|
+
t.jsonb :request_data, default: {}
|
|
15
|
+
|
|
16
|
+
# Metadata
|
|
17
|
+
t.string :environment
|
|
18
|
+
t.string :release_version
|
|
19
|
+
t.string :severity, default: "error"
|
|
20
|
+
t.string :source
|
|
21
|
+
t.boolean :handled, default: false
|
|
22
|
+
|
|
23
|
+
# Aggregation
|
|
24
|
+
t.integer :occurrence_count, default: 1
|
|
25
|
+
t.datetime :first_seen_at
|
|
26
|
+
t.datetime :last_seen_at
|
|
27
|
+
|
|
28
|
+
# Status tracking
|
|
29
|
+
t.string :status, default: "unresolved"
|
|
30
|
+
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Indexes for common queries
|
|
35
|
+
add_index :findbug_error_events, :fingerprint
|
|
36
|
+
add_index :findbug_error_events, :exception_class
|
|
37
|
+
add_index :findbug_error_events, :status
|
|
38
|
+
add_index :findbug_error_events, :severity
|
|
39
|
+
add_index :findbug_error_events, :last_seen_at
|
|
40
|
+
add_index :findbug_error_events, :created_at
|
|
41
|
+
add_index :findbug_error_events, [:status, :last_seen_at]
|
|
42
|
+
add_index :findbug_error_events, [:exception_class, :created_at]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateFindbugPerformanceEvents < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :findbug_performance_events do |t|
|
|
6
|
+
# Transaction identification
|
|
7
|
+
t.string :transaction_name, null: false
|
|
8
|
+
t.string :transaction_type, default: "request"
|
|
9
|
+
|
|
10
|
+
# Request info
|
|
11
|
+
t.string :request_method
|
|
12
|
+
t.string :request_path
|
|
13
|
+
t.string :format
|
|
14
|
+
t.integer :status
|
|
15
|
+
|
|
16
|
+
# Timing (all in milliseconds)
|
|
17
|
+
t.float :duration_ms, null: false
|
|
18
|
+
t.float :db_time_ms, default: 0
|
|
19
|
+
t.float :view_time_ms, default: 0
|
|
20
|
+
|
|
21
|
+
# Query tracking
|
|
22
|
+
t.integer :query_count, default: 0
|
|
23
|
+
t.jsonb :slow_queries, default: []
|
|
24
|
+
t.jsonb :n_plus_one_queries, default: []
|
|
25
|
+
t.boolean :has_n_plus_one, default: false
|
|
26
|
+
t.integer :view_count, default: 0
|
|
27
|
+
|
|
28
|
+
# Context
|
|
29
|
+
t.jsonb :context, default: {}
|
|
30
|
+
|
|
31
|
+
# Metadata
|
|
32
|
+
t.string :environment
|
|
33
|
+
t.string :release_version
|
|
34
|
+
t.datetime :captured_at
|
|
35
|
+
|
|
36
|
+
t.timestamps
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Indexes for common queries (using short names to stay under 63 char limit)
|
|
40
|
+
add_index :findbug_performance_events, :transaction_name, name: "idx_fb_perf_txn_name"
|
|
41
|
+
add_index :findbug_performance_events, :transaction_type, name: "idx_fb_perf_txn_type"
|
|
42
|
+
add_index :findbug_performance_events, :captured_at, name: "idx_fb_perf_captured_at"
|
|
43
|
+
add_index :findbug_performance_events, :duration_ms, name: "idx_fb_perf_duration"
|
|
44
|
+
add_index :findbug_performance_events, :has_n_plus_one, name: "idx_fb_perf_n_plus_one"
|
|
45
|
+
add_index :findbug_performance_events, [:transaction_name, :captured_at], name: "idx_fb_perf_txn_captured"
|
|
46
|
+
end
|
|
47
|
+
end
|