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,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Findbug
|
|
4
|
+
module Processing
|
|
5
|
+
# DataScrubber removes sensitive data from captured events.
|
|
6
|
+
#
|
|
7
|
+
# WHY SCRUBBING IS CRITICAL
|
|
8
|
+
# =========================
|
|
9
|
+
#
|
|
10
|
+
# Error data often contains sensitive information:
|
|
11
|
+
# - User passwords (in form params)
|
|
12
|
+
# - API keys (in headers)
|
|
13
|
+
# - Credit card numbers (in payment flows)
|
|
14
|
+
# - Personal data (in user context)
|
|
15
|
+
#
|
|
16
|
+
# Even though Findbug is self-hosted, you don't want this data:
|
|
17
|
+
# 1. Stored in your database
|
|
18
|
+
# 2. Visible in the dashboard
|
|
19
|
+
# 3. In logs or backups
|
|
20
|
+
# 4. Accessible to developers who shouldn't see it
|
|
21
|
+
#
|
|
22
|
+
# SCRUBBING STRATEGY
|
|
23
|
+
# ==================
|
|
24
|
+
#
|
|
25
|
+
# We replace sensitive values with "[FILTERED]" rather than removing them.
|
|
26
|
+
# This way you can see that the field existed (helpful for debugging)
|
|
27
|
+
# without exposing the actual value.
|
|
28
|
+
#
|
|
29
|
+
# WHAT WE SCRUB
|
|
30
|
+
# =============
|
|
31
|
+
#
|
|
32
|
+
# 1. Known field names (password, api_key, etc.)
|
|
33
|
+
# 2. Credit card patterns (16 digits)
|
|
34
|
+
# 3. SSN patterns (XXX-XX-XXXX)
|
|
35
|
+
# 4. Sensitive headers (Authorization, Cookie)
|
|
36
|
+
# 5. Custom fields from configuration
|
|
37
|
+
#
|
|
38
|
+
class DataScrubber
|
|
39
|
+
FILTERED = "[FILTERED]"
|
|
40
|
+
|
|
41
|
+
# Credit card patterns (Visa, MasterCard, Amex, etc.)
|
|
42
|
+
CREDIT_CARD_PATTERN = /\b(?:\d{4}[-\s]?){3}\d{4}\b/
|
|
43
|
+
|
|
44
|
+
# SSN pattern
|
|
45
|
+
SSN_PATTERN = /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
|
|
46
|
+
|
|
47
|
+
# Bearer token in text
|
|
48
|
+
BEARER_TOKEN_PATTERN = /Bearer\s+[A-Za-z0-9\-_.~+\/]+=*/i
|
|
49
|
+
|
|
50
|
+
# API key-like patterns (long alphanumeric strings)
|
|
51
|
+
API_KEY_PATTERN = /\b[A-Za-z0-9]{32,}\b/
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
# Scrub an entire event hash
|
|
55
|
+
#
|
|
56
|
+
# @param event [Hash] the event data to scrub
|
|
57
|
+
# @return [Hash] scrubbed event data
|
|
58
|
+
#
|
|
59
|
+
def scrub(event)
|
|
60
|
+
deep_scrub(event)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Scrub a string value for patterns
|
|
64
|
+
#
|
|
65
|
+
# @param value [String] the string to scrub
|
|
66
|
+
# @return [String] scrubbed string
|
|
67
|
+
#
|
|
68
|
+
def scrub_string(value)
|
|
69
|
+
return value unless value.is_a?(String)
|
|
70
|
+
|
|
71
|
+
value = value.dup
|
|
72
|
+
|
|
73
|
+
# Scrub credit card numbers
|
|
74
|
+
value.gsub!(CREDIT_CARD_PATTERN, FILTERED)
|
|
75
|
+
|
|
76
|
+
# Scrub SSN
|
|
77
|
+
value.gsub!(SSN_PATTERN, FILTERED)
|
|
78
|
+
|
|
79
|
+
# Scrub Bearer tokens
|
|
80
|
+
value.gsub!(BEARER_TOKEN_PATTERN, "Bearer #{FILTERED}")
|
|
81
|
+
|
|
82
|
+
# Scrub potential API keys (but not in backtraces)
|
|
83
|
+
# Only scrub in certain contexts to avoid false positives
|
|
84
|
+
# value.gsub!(API_KEY_PATTERN, FILTERED)
|
|
85
|
+
|
|
86
|
+
value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def deep_scrub(obj, path = [])
|
|
92
|
+
case obj
|
|
93
|
+
when Hash
|
|
94
|
+
# Preserve original key type (symbol or string)
|
|
95
|
+
obj.each_with_object({}) do |(key, value), result|
|
|
96
|
+
result[key] = if sensitive_key?(key)
|
|
97
|
+
FILTERED
|
|
98
|
+
else
|
|
99
|
+
deep_scrub(value, path + [key])
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
when Array
|
|
103
|
+
obj.map.with_index { |item, i| deep_scrub(item, path + [i]) }
|
|
104
|
+
when String
|
|
105
|
+
scrub_string(obj)
|
|
106
|
+
else
|
|
107
|
+
obj
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sensitive_key?(key)
|
|
112
|
+
key_s = key.to_s.downcase
|
|
113
|
+
|
|
114
|
+
# Check against configured scrub fields
|
|
115
|
+
scrub_fields.any? do |field|
|
|
116
|
+
key_s.include?(field.downcase)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def scrub_fields
|
|
121
|
+
@scrub_fields ||= build_scrub_fields
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_scrub_fields
|
|
125
|
+
default_fields = %w[
|
|
126
|
+
password
|
|
127
|
+
passwd
|
|
128
|
+
secret
|
|
129
|
+
token
|
|
130
|
+
api_key
|
|
131
|
+
apikey
|
|
132
|
+
access_key
|
|
133
|
+
accesskey
|
|
134
|
+
private_key
|
|
135
|
+
privatekey
|
|
136
|
+
credit_card
|
|
137
|
+
creditcard
|
|
138
|
+
card_number
|
|
139
|
+
cardnumber
|
|
140
|
+
cvv
|
|
141
|
+
cvc
|
|
142
|
+
ssn
|
|
143
|
+
social_security
|
|
144
|
+
authorization
|
|
145
|
+
auth
|
|
146
|
+
bearer
|
|
147
|
+
cookie
|
|
148
|
+
session
|
|
149
|
+
csrf
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# Merge with user-configured fields
|
|
153
|
+
(default_fields + Findbug.config.scrub_fields.map(&:to_s)).uniq
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Reset cached fields (for testing or config changes)
|
|
157
|
+
def reset!
|
|
158
|
+
@scrub_fields = nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Findbug
|
|
4
|
+
module RailsExt
|
|
5
|
+
# ControllerMethods provides helper methods for Rails controllers.
|
|
6
|
+
#
|
|
7
|
+
# These methods are automatically included in all controllers via the Railtie.
|
|
8
|
+
# They let you add custom context to errors and performance data.
|
|
9
|
+
#
|
|
10
|
+
# WHY CONTROLLER HELPERS?
|
|
11
|
+
# =======================
|
|
12
|
+
#
|
|
13
|
+
# When an error occurs, you often want to know:
|
|
14
|
+
# - Which user was affected?
|
|
15
|
+
# - What were the request params?
|
|
16
|
+
# - What was the user's plan/tier?
|
|
17
|
+
# - What A/B experiment variant were they in?
|
|
18
|
+
#
|
|
19
|
+
# These helpers let you attach this context easily:
|
|
20
|
+
#
|
|
21
|
+
# class ApplicationController < ActionController::Base
|
|
22
|
+
# before_action :set_findbug_context
|
|
23
|
+
#
|
|
24
|
+
# def set_findbug_context
|
|
25
|
+
# findbug_set_user(current_user)
|
|
26
|
+
# findbug_set_context(
|
|
27
|
+
# plan: current_user&.plan,
|
|
28
|
+
# experiment: session[:ab_variant]
|
|
29
|
+
# )
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# Then when an error occurs, all this context is captured automatically.
|
|
34
|
+
#
|
|
35
|
+
module ControllerMethods
|
|
36
|
+
extend ActiveSupport::Concern
|
|
37
|
+
|
|
38
|
+
included do
|
|
39
|
+
# Store context in a thread-local variable
|
|
40
|
+
# Thread-local means each request has its own context
|
|
41
|
+
# This is important for thread-safe operation in Puma
|
|
42
|
+
before_action :findbug_clear_context
|
|
43
|
+
after_action :findbug_clear_context
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Set the current user for error context
|
|
47
|
+
#
|
|
48
|
+
# @param user [Object] the user object (any object with id, email, etc.)
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# findbug_set_user(current_user)
|
|
52
|
+
#
|
|
53
|
+
# WHY A SEPARATE USER METHOD?
|
|
54
|
+
# ---------------------------
|
|
55
|
+
# Users are special - they're the most common context and have
|
|
56
|
+
# special handling (we extract id, email, username automatically).
|
|
57
|
+
#
|
|
58
|
+
def findbug_set_user(user)
|
|
59
|
+
return unless user
|
|
60
|
+
|
|
61
|
+
Findbug::Capture::Context.set_user(
|
|
62
|
+
id: user.try(:id),
|
|
63
|
+
email: user.try(:email),
|
|
64
|
+
username: user.try(:username) || user.try(:name)
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Set custom context data
|
|
69
|
+
#
|
|
70
|
+
# @param data [Hash] key-value pairs to attach to errors
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# findbug_set_context(
|
|
74
|
+
# organization_id: current_org.id,
|
|
75
|
+
# feature_flags: current_flags
|
|
76
|
+
# )
|
|
77
|
+
#
|
|
78
|
+
def findbug_set_context(data = {})
|
|
79
|
+
Findbug::Capture::Context.merge(data)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Add a tag (short key-value pair for filtering)
|
|
83
|
+
#
|
|
84
|
+
# @param key [String, Symbol] the tag key
|
|
85
|
+
# @param value [String] the tag value
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# findbug_tag(:environment, "production")
|
|
89
|
+
# findbug_tag(:region, "us-east-1")
|
|
90
|
+
#
|
|
91
|
+
# Tags are optimized for filtering/grouping in the dashboard.
|
|
92
|
+
# Use context for detailed data, tags for filterable attributes.
|
|
93
|
+
#
|
|
94
|
+
def findbug_tag(key, value)
|
|
95
|
+
Findbug::Capture::Context.add_tag(key, value)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add a breadcrumb (for debugging what happened before the error)
|
|
99
|
+
#
|
|
100
|
+
# @param message [String] what happened
|
|
101
|
+
# @param category [String] category for grouping
|
|
102
|
+
# @param data [Hash] additional data
|
|
103
|
+
#
|
|
104
|
+
# @example
|
|
105
|
+
# findbug_breadcrumb("User logged in", category: "auth")
|
|
106
|
+
# findbug_breadcrumb("Loaded products", category: "query", data: { count: 50 })
|
|
107
|
+
#
|
|
108
|
+
# Breadcrumbs help you understand the sequence of events leading to an error.
|
|
109
|
+
# Think of them like a trail of breadcrumbs Hansel & Gretel left.
|
|
110
|
+
#
|
|
111
|
+
def findbug_breadcrumb(message, category: "default", data: {})
|
|
112
|
+
Findbug::Capture::Context.add_breadcrumb(
|
|
113
|
+
message: message,
|
|
114
|
+
category: category,
|
|
115
|
+
data: data,
|
|
116
|
+
timestamp: Time.now.utc.iso8601(3)
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Capture an exception manually with current context
|
|
121
|
+
#
|
|
122
|
+
# @param exception [Exception] the exception to capture
|
|
123
|
+
# @param extra [Hash] additional context for this specific error
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# begin
|
|
127
|
+
# external_api.call
|
|
128
|
+
# rescue ExternalAPIError => e
|
|
129
|
+
# findbug_capture(e, api: "payment_gateway")
|
|
130
|
+
# # handle gracefully
|
|
131
|
+
# end
|
|
132
|
+
#
|
|
133
|
+
def findbug_capture(exception, extra = {})
|
|
134
|
+
Findbug.capture_exception(exception, extra)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Clear context between requests
|
|
140
|
+
#
|
|
141
|
+
# WHY CLEAR CONTEXT?
|
|
142
|
+
# ------------------
|
|
143
|
+
# Without clearing, context from one request could leak into another.
|
|
144
|
+
# This is especially important in threaded servers like Puma where
|
|
145
|
+
# threads are reused across requests.
|
|
146
|
+
#
|
|
147
|
+
def findbug_clear_context
|
|
148
|
+
Findbug::Capture::Context.clear!
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module Findbug
|
|
6
|
+
# Railtie hooks Findbug into the Rails boot process.
|
|
7
|
+
#
|
|
8
|
+
# WHAT IS A RAILTIE?
|
|
9
|
+
# ==================
|
|
10
|
+
#
|
|
11
|
+
# When Rails boots, it looks for classes that inherit from Rails::Railtie
|
|
12
|
+
# and calls their initializers in order. This is how gems integrate with Rails.
|
|
13
|
+
#
|
|
14
|
+
# Common things Railties do:
|
|
15
|
+
# - Insert middleware into the stack
|
|
16
|
+
# - Subscribe to ActiveSupport::Notifications
|
|
17
|
+
# - Add rake tasks
|
|
18
|
+
# - Configure the Rails app
|
|
19
|
+
#
|
|
20
|
+
# WHY USE A RAILTIE?
|
|
21
|
+
# ==================
|
|
22
|
+
#
|
|
23
|
+
# Instead of making users add Findbug to 5 different places:
|
|
24
|
+
#
|
|
25
|
+
# # application.rb
|
|
26
|
+
# config.middleware.use Findbug::Capture::Middleware
|
|
27
|
+
#
|
|
28
|
+
# # initializer
|
|
29
|
+
# ActiveSupport::Notifications.subscribe(...)
|
|
30
|
+
#
|
|
31
|
+
# # routes
|
|
32
|
+
# mount Findbug::Web::Engine => "/findbug"
|
|
33
|
+
#
|
|
34
|
+
# We do it all automatically in the Railtie. User just adds the gem
|
|
35
|
+
# and creates a config file. Zero setup!
|
|
36
|
+
#
|
|
37
|
+
# THE INITIALIZATION ORDER
|
|
38
|
+
# ========================
|
|
39
|
+
#
|
|
40
|
+
# Rails runs initializers in stages:
|
|
41
|
+
#
|
|
42
|
+
# 1. before_configuration - Before config is read
|
|
43
|
+
# 2. before_initialize - Before Rails.initialize!
|
|
44
|
+
# 3. to_prepare - Before each request (dev) or once (prod)
|
|
45
|
+
# 4. after_initialize - After Rails is fully loaded
|
|
46
|
+
#
|
|
47
|
+
# We use after_initialize because we need:
|
|
48
|
+
# - Rails.env to be set
|
|
49
|
+
# - Database connections to exist
|
|
50
|
+
# - All models to be loaded
|
|
51
|
+
#
|
|
52
|
+
class Railtie < Rails::Railtie
|
|
53
|
+
# Register our middleware to catch exceptions
|
|
54
|
+
#
|
|
55
|
+
# MIDDLEWARE ORDER MATTERS!
|
|
56
|
+
# -------------------------
|
|
57
|
+
#
|
|
58
|
+
# We insert AFTER ActionDispatch::ShowExceptions because:
|
|
59
|
+
# 1. ShowExceptions converts exceptions to HTTP responses
|
|
60
|
+
# 2. We want to capture the raw exception BEFORE that happens
|
|
61
|
+
# 3. We also want to capture exceptions that ShowExceptions misses
|
|
62
|
+
#
|
|
63
|
+
# Stack (simplified):
|
|
64
|
+
# Rails::Rack::Logger
|
|
65
|
+
# ActionDispatch::RequestId
|
|
66
|
+
# ActionDispatch::ShowExceptions ← Converts exceptions to 500 pages
|
|
67
|
+
# Findbug::Capture::Middleware ← WE GO HERE (sees raw exceptions)
|
|
68
|
+
# ActionDispatch::Routing
|
|
69
|
+
# YourController#action
|
|
70
|
+
#
|
|
71
|
+
initializer "findbug.middleware" do
|
|
72
|
+
require_relative "capture/middleware"
|
|
73
|
+
|
|
74
|
+
Rails.application.config.middleware.use(Findbug::Capture::Middleware)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Set up Rails error reporting integration (Rails 7+)
|
|
78
|
+
#
|
|
79
|
+
# Rails 7 introduced ErrorReporter for centralized error handling.
|
|
80
|
+
# We subscribe to it so we capture ALL errors, even those handled
|
|
81
|
+
# gracefully by the app.
|
|
82
|
+
#
|
|
83
|
+
initializer "findbug.error_reporter" do |app|
|
|
84
|
+
require_relative "capture/exception_subscriber"
|
|
85
|
+
|
|
86
|
+
app.config.after_initialize do
|
|
87
|
+
if defined?(Rails.error) && Rails.error.respond_to?(:subscribe)
|
|
88
|
+
Rails.error.subscribe(Findbug::Capture::ExceptionSubscriber.new)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Set up performance instrumentation
|
|
94
|
+
#
|
|
95
|
+
# Rails uses ActiveSupport::Notifications for internal events:
|
|
96
|
+
# - sql.active_record (database queries)
|
|
97
|
+
# - process_action.action_controller (requests)
|
|
98
|
+
# - render_template.action_view (view rendering)
|
|
99
|
+
#
|
|
100
|
+
# We subscribe to these to capture performance data.
|
|
101
|
+
#
|
|
102
|
+
initializer "findbug.instrumentation" do |app|
|
|
103
|
+
app.config.after_initialize do
|
|
104
|
+
next unless Findbug.config.performance_enabled
|
|
105
|
+
|
|
106
|
+
require_relative "performance/instrumentation"
|
|
107
|
+
Findbug::Performance::Instrumentation.setup!
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Mount the web dashboard engine
|
|
112
|
+
#
|
|
113
|
+
# This adds routes for the /findbug dashboard.
|
|
114
|
+
# We only mount if authentication is configured (security!).
|
|
115
|
+
#
|
|
116
|
+
initializer "findbug.routes" do |app|
|
|
117
|
+
app.config.after_initialize do
|
|
118
|
+
next unless Findbug.config.web_enabled?
|
|
119
|
+
|
|
120
|
+
require_relative "engine"
|
|
121
|
+
|
|
122
|
+
# Add routes programmatically
|
|
123
|
+
# This is equivalent to `mount Findbug::Engine => "/findbug"` in routes.rb
|
|
124
|
+
# but automatic!
|
|
125
|
+
app.routes.append do
|
|
126
|
+
mount Findbug::Engine => Findbug.config.web_path
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Set up default configuration values that depend on Rails
|
|
132
|
+
#
|
|
133
|
+
initializer "findbug.defaults" do |app|
|
|
134
|
+
app.config.after_initialize do
|
|
135
|
+
config = Findbug.config
|
|
136
|
+
|
|
137
|
+
# Use Rails.env if environment not explicitly set
|
|
138
|
+
config.environment ||= Rails.env
|
|
139
|
+
|
|
140
|
+
# Disable in test environment by default
|
|
141
|
+
if Rails.env.test? && config.enabled
|
|
142
|
+
Findbug.logger.debug(
|
|
143
|
+
"[Findbug] Running in test environment. Set `config.enabled = true` to enable."
|
|
144
|
+
)
|
|
145
|
+
# Note: We don't force disable here. User might want it enabled for integration tests.
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Try to auto-detect release from common sources
|
|
149
|
+
config.release ||= detect_release
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Add Findbug helpers to ActionController
|
|
154
|
+
#
|
|
155
|
+
# This adds methods like `findbug_context` that controllers can use
|
|
156
|
+
# to add custom context to errors.
|
|
157
|
+
#
|
|
158
|
+
initializer "findbug.controller_methods" do
|
|
159
|
+
ActiveSupport.on_load(:action_controller) do
|
|
160
|
+
require_relative "rails/controller_methods"
|
|
161
|
+
include Findbug::RailsExt::ControllerMethods
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Start background persister
|
|
166
|
+
#
|
|
167
|
+
# This runs a thread that periodically moves events from Redis to the database.
|
|
168
|
+
# Users don't need to set up Sidekiq or any job system - it works out of the box.
|
|
169
|
+
#
|
|
170
|
+
initializer "findbug.background_persister" do |app|
|
|
171
|
+
app.config.after_initialize do
|
|
172
|
+
next unless Findbug.enabled?
|
|
173
|
+
next unless Findbug.config.auto_persist
|
|
174
|
+
|
|
175
|
+
require_relative "background_persister"
|
|
176
|
+
Findbug::BackgroundPersister.start!(
|
|
177
|
+
interval: Findbug.config.persist_interval
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Register cleanup for application shutdown
|
|
183
|
+
#
|
|
184
|
+
# When the app shuts down (e.g., during deploys), we want to:
|
|
185
|
+
# 1. Stop the background persister thread
|
|
186
|
+
# 2. Flush any pending events
|
|
187
|
+
# 3. Close Redis connections cleanly
|
|
188
|
+
#
|
|
189
|
+
initializer "findbug.shutdown" do |app|
|
|
190
|
+
at_exit do
|
|
191
|
+
Findbug::BackgroundPersister.stop! if defined?(Findbug::BackgroundPersister)
|
|
192
|
+
Findbug::Storage::ConnectionPool.shutdown! if defined?(Findbug::Storage::ConnectionPool)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Add rake tasks
|
|
197
|
+
rake_tasks do
|
|
198
|
+
load File.expand_path("tasks/findbug.rake", __dir__)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# Try to detect the release/version from environment
|
|
204
|
+
def detect_release
|
|
205
|
+
# Common environment variables for release tracking
|
|
206
|
+
ENV["FINDBUG_RELEASE"] ||
|
|
207
|
+
ENV["HEROKU_SLUG_COMMIT"] ||
|
|
208
|
+
ENV["RENDER_GIT_COMMIT"] ||
|
|
209
|
+
ENV["GIT_COMMIT"] ||
|
|
210
|
+
ENV["SOURCE_VERSION"] ||
|
|
211
|
+
git_sha
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Get current git SHA (if in a git repo)
|
|
215
|
+
def git_sha
|
|
216
|
+
sha = `git rev-parse --short HEAD 2>/dev/null`.strip
|
|
217
|
+
sha.empty? ? nil : sha
|
|
218
|
+
rescue StandardError
|
|
219
|
+
nil
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|