snitch-rails 0.3.1 → 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/README.md +54 -16
- data/app/controllers/snitch/webhooks_controller.rb +27 -0
- data/config/routes.rb +1 -0
- data/lib/generators/snitch/install/templates/snitch.rb +3 -0
- data/lib/snitch/configuration.rb +2 -1
- data/lib/snitch/exception_handler.rb +5 -3
- data/lib/snitch/github_client.rb +5 -0
- data/lib/snitch/jobs/resolve_event_job.rb +14 -0
- data/lib/snitch/middleware.rb +1 -1
- data/lib/snitch/version.rb +1 -1
- data/lib/snitch.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a33dc497c1b82d6d45ebc526e83f27eb51cb683fcf7eb3634d0b94aa020be386
|
|
4
|
+
data.tar.gz: 1302300c77ed110f702cb2f6b5817ce71e87ad1cfe35845a1935776a2f6d830c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7bbd6b45dc3f1ed501940526360912793e60b7b18eae32c10b32b48f5e90ebb0497cb450e8581fa99e6d14144eabce3c5a03d61069ab37fc3968c5024e573a2
|
|
7
|
+
data.tar.gz: e7996ecc883c3a112211c31a174a3281f1a03898bb8af2c994aef9df21648619dac8e442099becc6223398dcae402fd1e69a07c83552b506cb61562c106f5b7a
|
data/README.md
CHANGED
|
@@ -12,7 +12,6 @@ Snitch catches unhandled exceptions in your Rails application, persists them to
|
|
|
12
12
|
- **Dashboard** at `/snitches` with tabbed views: Open, Closed, and Ignored
|
|
13
13
|
- Ignore exceptions via config or directly from the dashboard
|
|
14
14
|
- Manual exception reporting for rescued exceptions
|
|
15
|
-
- Upgrade generator for existing installations
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
@@ -28,26 +27,19 @@ Run bundle install:
|
|
|
28
27
|
bundle install
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
Run the install generator
|
|
30
|
+
Run the install generator:
|
|
32
31
|
|
|
33
32
|
```bash
|
|
34
33
|
rails generate snitch:install
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Run the migration:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
34
|
rails db:migrate
|
|
41
35
|
```
|
|
42
36
|
|
|
43
|
-
The installer
|
|
37
|
+
The installer mounts the dashboard to `config/routes.rb`:
|
|
44
38
|
|
|
45
39
|
```ruby
|
|
46
40
|
mount Snitch::Engine, at: "/snitches"
|
|
47
41
|
```
|
|
48
42
|
|
|
49
|
-
If you need to mount it manually or at a different path, add the above line to your `config/routes.rb`.
|
|
50
|
-
|
|
51
43
|
### Upgrading from a previous version
|
|
52
44
|
|
|
53
45
|
If you're upgrading from a version prior to 0.3.0, run the update generator to add the `status` column to the `snitch_errors` table:
|
|
@@ -83,6 +75,9 @@ Snitch.configure do |config|
|
|
|
83
75
|
|
|
84
76
|
# Exceptions to ignore (default: ActiveRecord::RecordNotFound, ActionController::RoutingError)
|
|
85
77
|
config.ignored_exceptions += [YourCustomError]
|
|
78
|
+
|
|
79
|
+
# GitHub webhook secret for auto-closing events when issues are closed (optional)
|
|
80
|
+
config.github_webhook_secret = ENV["SNITCH_GITHUB_WEBHOOK_SECRET"]
|
|
86
81
|
end
|
|
87
82
|
```
|
|
88
83
|
|
|
@@ -92,7 +87,11 @@ Create a [personal access token](https://github.com/settings/tokens) with the `r
|
|
|
92
87
|
|
|
93
88
|
## Dashboard
|
|
94
89
|
|
|
95
|
-
Visit `/snitches` in your browser to view the Snitch dashboard.
|
|
90
|
+
Visit `/snitches` in your browser to view the Snitch dashboard.
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
The dashboard provides three tabs:
|
|
96
95
|
|
|
97
96
|
- **Open** — Exceptions that need attention. Shows the most recently occurred first.
|
|
98
97
|
- **Closed** — Exceptions that have been resolved. You can reopen them if they recur.
|
|
@@ -104,10 +103,12 @@ From the dashboard you can change the status of any exception:
|
|
|
104
103
|
|
|
105
104
|
### Ignoring Exceptions
|
|
106
105
|
|
|
107
|
-
There are two ways to ignore exceptions:
|
|
106
|
+
There are two ways to ignore exceptions, and Snitch honors both:
|
|
107
|
+
|
|
108
|
+
1. **Via the initializer** — Add exception classes to `config.ignored_exceptions`. These are filtered at the middleware level and never captured or persisted.
|
|
109
|
+
2. **Via the dashboard** — Mark individual exceptions as "Ignored" from the Open tab. Future occurrences of that exception class will be silently skipped — no new records, no GitHub issues.
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
2. **Via the dashboard** — Mark individual exceptions as "Ignored" from the Open tab. These exceptions are already captured but future occurrences won't create new GitHub issues.
|
|
111
|
+
Both sources are checked every time an exception is caught. If an exception class appears in either the initializer config or has a record with status "ignored" in the database, it will be ignored.
|
|
111
112
|
|
|
112
113
|
## Manual Reporting
|
|
113
114
|
|
|
@@ -126,6 +127,42 @@ This is useful for exceptions you want to recover from gracefully but still want
|
|
|
126
127
|
|
|
127
128
|
The same fingerprinting and deduplication rules apply. If the same exception is reported multiple times, Snitch will increment the occurrence count and comment on the existing GitHub issue rather than creating a new one.
|
|
128
129
|
|
|
130
|
+
## GitHub Webhook (Auto-Close)
|
|
131
|
+
|
|
132
|
+
When a developer fixes a bug and closes the GitHub issue, Snitch can automatically close the matching event record in your database — no manual dashboard cleanup required.
|
|
133
|
+
|
|
134
|
+
### Setup
|
|
135
|
+
|
|
136
|
+
1. Generate a webhook secret:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
ruby -rsecurerandom -e "puts SecureRandom.hex(32)"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. Add the secret to your environment and Snitch config:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
export SNITCH_GITHUB_WEBHOOK_SECRET="your-generated-secret"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
# config/initializers/snitch.rb
|
|
150
|
+
config.github_webhook_secret = ENV["SNITCH_GITHUB_WEBHOOK_SECRET"]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
3. Add the same secret to your GitHub repository. Go to **Settings > Webhooks > Add webhook** and configure:
|
|
154
|
+
|
|
155
|
+
| Field | Value |
|
|
156
|
+
|----------------|------------------------------------------------|
|
|
157
|
+
| Payload URL | `https://yourapp.com/snitches/webhooks/github` |
|
|
158
|
+
| Content type | `application/json` |
|
|
159
|
+
| Secret | The secret you generated in step 1 |
|
|
160
|
+
| Events | Select **Issues** only |
|
|
161
|
+
|
|
162
|
+
The secret must match on both sides — your app uses it to verify that incoming webhooks are genuinely from GitHub.
|
|
163
|
+
|
|
164
|
+
Once configured, closing a GitHub issue will automatically close the corresponding Snitch event.
|
|
165
|
+
|
|
129
166
|
## How It Works
|
|
130
167
|
|
|
131
168
|
1. Rack middleware catches any unhandled exception (and re-raises it so normal error handling still applies)
|
|
@@ -136,8 +173,9 @@ The same fingerprinting and deduplication rules apply. If the same exception is
|
|
|
136
173
|
|
|
137
174
|
## Roadmap
|
|
138
175
|
|
|
139
|
-
- [
|
|
140
|
-
- [ ]
|
|
176
|
+
- [x] Dashboard to view and manage captured exceptions
|
|
177
|
+
- [ ] Multi-db support
|
|
178
|
+
- [x] Webhook to resolve snitch records when GitHub issues close
|
|
141
179
|
|
|
142
180
|
## Requirements
|
|
143
181
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Snitch
|
|
4
|
+
class WebhooksController < ActionController::Base
|
|
5
|
+
skip_forgery_protection
|
|
6
|
+
|
|
7
|
+
def github
|
|
8
|
+
secret = Snitch.configuration.github_webhook_secret
|
|
9
|
+
return head :unauthorized unless secret.present?
|
|
10
|
+
|
|
11
|
+
body = request.body.read
|
|
12
|
+
signature = request.headers["X-Hub-Signature-256"]
|
|
13
|
+
expected = "sha256=#{OpenSSL::HMAC.hexdigest("SHA256", secret, body)}"
|
|
14
|
+
return head :unauthorized unless signature.present? && Rack::Utils.secure_compare(expected, signature)
|
|
15
|
+
|
|
16
|
+
return head :ok unless request.headers["X-GitHub-Event"] == "issues"
|
|
17
|
+
|
|
18
|
+
payload = JSON.parse(body)
|
|
19
|
+
return head :ok unless payload["action"] == "closed"
|
|
20
|
+
|
|
21
|
+
issue_number = payload.dig("issue", "number")
|
|
22
|
+
ResolveEventJob.perform_later(issue_number)
|
|
23
|
+
|
|
24
|
+
head :ok
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/config/routes.rb
CHANGED
|
@@ -13,4 +13,7 @@ Snitch.configure do |config|
|
|
|
13
13
|
|
|
14
14
|
# Exceptions to ignore (default: RecordNotFound, RoutingError)
|
|
15
15
|
# config.ignored_exceptions += [CustomError]
|
|
16
|
+
|
|
17
|
+
# GitHub webhook secret for auto-closing events when issues are closed
|
|
18
|
+
# config.github_webhook_secret = ENV["SNITCH_GITHUB_WEBHOOK_SECRET"]
|
|
16
19
|
end
|
data/lib/snitch/configuration.rb
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Snitch
|
|
4
4
|
class Configuration
|
|
5
|
-
attr_accessor :github_token, :github_repo, :mention, :enabled, :ignored_exceptions
|
|
5
|
+
attr_accessor :github_token, :github_repo, :github_webhook_secret, :mention, :enabled, :ignored_exceptions
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
8
|
@github_token = nil
|
|
9
9
|
@github_repo = nil
|
|
10
|
+
@github_webhook_secret = nil
|
|
10
11
|
@mention = "@claude"
|
|
11
12
|
@enabled = true
|
|
12
13
|
@ignored_exceptions = [
|
|
@@ -45,12 +45,14 @@ module Snitch
|
|
|
45
45
|
existing = Event.find_by(fingerprint: fingerprint)
|
|
46
46
|
|
|
47
47
|
if existing
|
|
48
|
-
|
|
48
|
+
attrs = {
|
|
49
49
|
occurrence_count: existing.occurrence_count + 1,
|
|
50
50
|
last_occurred_at: Time.current,
|
|
51
51
|
message: exception.message,
|
|
52
52
|
backtrace: exception.backtrace
|
|
53
|
-
|
|
53
|
+
}
|
|
54
|
+
attrs[:status] = "open" if existing.status == "closed"
|
|
55
|
+
existing.update!(**attrs)
|
|
54
56
|
existing
|
|
55
57
|
else
|
|
56
58
|
Event.create!(
|
|
@@ -69,7 +71,7 @@ module Snitch
|
|
|
69
71
|
def enqueue_report(record)
|
|
70
72
|
ReportExceptionJob.perform_later(record.id)
|
|
71
73
|
rescue => e
|
|
72
|
-
Rails.logger.error("[Snitch] Failed to enqueue report job: #{e.message}") if defined?(Rails)
|
|
74
|
+
Rails.logger.error("[Snitch] Failed to enqueue report job: #{e.message}") if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
end
|
data/lib/snitch/github_client.rb
CHANGED
|
@@ -24,6 +24,11 @@ module Snitch
|
|
|
24
24
|
def comment_on_issue(event)
|
|
25
25
|
body = build_comment_body(event)
|
|
26
26
|
@client.add_comment(@repo, event.github_issue_number, body)
|
|
27
|
+
reopen_issue(event) if event.status == "open"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reopen_issue(event)
|
|
31
|
+
@client.update_issue(@repo, event.github_issue_number, state: "open")
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
private
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Snitch
|
|
4
|
+
class ResolveEventJob < ActiveJob::Base
|
|
5
|
+
queue_as :default
|
|
6
|
+
|
|
7
|
+
retry_on StandardError, wait: :polynomially_later, attempts: 3
|
|
8
|
+
|
|
9
|
+
def perform(github_issue_number)
|
|
10
|
+
Event.where(github_issue_number: github_issue_number, status: "open")
|
|
11
|
+
.update_all(status: "closed")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/snitch/middleware.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Snitch
|
|
|
10
10
|
begin
|
|
11
11
|
ExceptionHandler.handle(e, env)
|
|
12
12
|
rescue => handler_error
|
|
13
|
-
Rails.logger.error("[Snitch] Handler error: #{handler_error.message}") if defined?(Rails)
|
|
13
|
+
Rails.logger.error("[Snitch] Handler error: #{handler_error.message}") if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
14
14
|
end
|
|
15
15
|
raise e
|
|
16
16
|
end
|
data/lib/snitch/version.rb
CHANGED
data/lib/snitch.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "snitch/exception_handler"
|
|
|
8
8
|
require "snitch/github_client"
|
|
9
9
|
require "snitch/models/event" if defined?(ActiveRecord)
|
|
10
10
|
require "snitch/jobs/report_exception_job" if defined?(ActiveJob)
|
|
11
|
+
require "snitch/jobs/resolve_event_job" if defined?(ActiveJob)
|
|
11
12
|
require "snitch/engine" if defined?(Rails)
|
|
12
13
|
|
|
13
14
|
module Snitch
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: snitch-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- RiseKit
|
|
@@ -132,6 +132,7 @@ files:
|
|
|
132
132
|
- app/assets/builds/snitch/application.css
|
|
133
133
|
- app/assets/stylesheets/snitch/application.css
|
|
134
134
|
- app/controllers/snitch/snitches_controller.rb
|
|
135
|
+
- app/controllers/snitch/webhooks_controller.rb
|
|
135
136
|
- app/views/layouts/snitch/application.html.erb
|
|
136
137
|
- app/views/snitch/snitches/index.html.erb
|
|
137
138
|
- app/views/snitch/snitches/show.html.erb
|
|
@@ -148,6 +149,7 @@ files:
|
|
|
148
149
|
- lib/snitch/fingerprint.rb
|
|
149
150
|
- lib/snitch/github_client.rb
|
|
150
151
|
- lib/snitch/jobs/report_exception_job.rb
|
|
152
|
+
- lib/snitch/jobs/resolve_event_job.rb
|
|
151
153
|
- lib/snitch/middleware.rb
|
|
152
154
|
- lib/snitch/models/event.rb
|
|
153
155
|
- lib/snitch/version.rb
|