hawk-ruby 0.1.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/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/docs/configuration.md +160 -0
- data/docs/integrations.md +149 -0
- data/docs/releasing.md +23 -0
- data/docs/verification.md +85 -0
- data/lib/hawk/boundary.rb +26 -0
- data/lib/hawk/breadcrumbs/logger.rb +64 -0
- data/lib/hawk/breadcrumbs/net_http.rb +63 -0
- data/lib/hawk/breadcrumbs/rails.rb +24 -0
- data/lib/hawk/breadcrumbs.rb +92 -0
- data/lib/hawk/client.rb +175 -0
- data/lib/hawk/configuration.rb +138 -0
- data/lib/hawk/delayed_job.rb +61 -0
- data/lib/hawk/delivery.rb +113 -0
- data/lib/hawk/diagnostics.rb +53 -0
- data/lib/hawk/event.rb +136 -0
- data/lib/hawk/filter.rb +36 -0
- data/lib/hawk/filtered_fields.rb +56 -0
- data/lib/hawk/framework_filter.rb +16 -0
- data/lib/hawk/hook.rb +12 -0
- data/lib/hawk/job_context.rb +89 -0
- data/lib/hawk/json_value.rb +77 -0
- data/lib/hawk/process_hooks.rb +24 -0
- data/lib/hawk/rack/body.rb +76 -0
- data/lib/hawk/rack.rb +71 -0
- data/lib/hawk/rails/action_cable.rb +79 -0
- data/lib/hawk/rails/active_job.rb +74 -0
- data/lib/hawk/rails/error_subscriber.rb +38 -0
- data/lib/hawk/rails/integration.rb +51 -0
- data/lib/hawk/rails/payload_filter.rb +34 -0
- data/lib/hawk/rails.rb +5 -0
- data/lib/hawk/rake.rb +29 -0
- data/lib/hawk/release.rb +31 -0
- data/lib/hawk/resque.rb +77 -0
- data/lib/hawk/scope.rb +108 -0
- data/lib/hawk/scope_store.rb +55 -0
- data/lib/hawk/sidekiq.rb +80 -0
- data/lib/hawk/source.rb +83 -0
- data/lib/hawk/token.rb +53 -0
- data/lib/hawk/transport.rb +111 -0
- data/lib/hawk/version.rb +5 -0
- data/lib/hawk.rb +85 -0
- data/sig/hawk/boundary.rbs +7 -0
- data/sig/hawk/breadcrumbs/logger.rbs +17 -0
- data/sig/hawk/breadcrumbs/net_http.rbs +16 -0
- data/sig/hawk/breadcrumbs/rails.rbs +7 -0
- data/sig/hawk/breadcrumbs.rbs +13 -0
- data/sig/hawk/client.rbs +41 -0
- data/sig/hawk/configuration.rbs +61 -0
- data/sig/hawk/delayed_job.rbs +24 -0
- data/sig/hawk/delivery.rbs +27 -0
- data/sig/hawk/diagnostics.rbs +24 -0
- data/sig/hawk/event.rbs +24 -0
- data/sig/hawk/filter.rbs +13 -0
- data/sig/hawk/filtered_fields.rbs +11 -0
- data/sig/hawk/framework_filter.rbs +5 -0
- data/sig/hawk/hook.rbs +5 -0
- data/sig/hawk/job_context.rbs +15 -0
- data/sig/hawk/json_value.rbs +17 -0
- data/sig/hawk/process_hooks.rbs +7 -0
- data/sig/hawk/rack.rbs +74 -0
- data/sig/hawk/rails/action_cable.rbs +40 -0
- data/sig/hawk/rails/active_job.rbs +34 -0
- data/sig/hawk/rails/error_subscriber.rbs +9 -0
- data/sig/hawk/rails/payload_filter.rbs +16 -0
- data/sig/hawk/rails.rbs +26 -0
- data/sig/hawk/rake.rbs +13 -0
- data/sig/hawk/release.rbs +6 -0
- data/sig/hawk/resque.rbs +25 -0
- data/sig/hawk/scope.rbs +31 -0
- data/sig/hawk/scope_store.rbs +15 -0
- data/sig/hawk/sidekiq.rbs +29 -0
- data/sig/hawk/source.rbs +18 -0
- data/sig/hawk/token.rbs +14 -0
- data/sig/hawk/transport.rbs +32 -0
- data/sig/hawk.rbs +16 -0
- metadata +223 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 917927100fe40addf2591185b4a00783f4bc53cee3311f521c8c1aeb7633ecc6
|
|
4
|
+
data.tar.gz: 83af5651bb2fa4f112faf997dcda6c01982112ecc6de2113bd5564c46c2a1288
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f0d2a287b931372819ddf3d8ae6f14e2e3284ecdb2475d235f58dbe77776b171398fdfcd3cf815b2fae0691c5cdd3b68d395aa1c17f504b7f78497fff77e68f2
|
|
7
|
+
data.tar.gz: db9adaf226efcdea53862e25c0f184d5e53fd918afd084e88333f11d586c195a516dc0f21b49b71c0559c72698735f84b9035a7155458d0f9ac2b6b3d3fc9172
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-10-29
|
|
4
|
+
|
|
5
|
+
Initial release of the Hawk Ruby error reporting gem.
|
|
6
|
+
|
|
7
|
+
- Report exceptions and messages with backtraces, source snippets, breadcrumbs, user data, context, severity, and application release identifiers.
|
|
8
|
+
- Add optional Rack, Rails, Rails error reporter, Action Cable, Active Job, Solid Queue, Sidekiq, Resque, Delayed Job, and Rake integration.
|
|
9
|
+
- Provide bounded asynchronous delivery, execution-local scopes, duplicate suppression, fork recovery, privacy filters, and event hooks.
|
|
10
|
+
- Ship RBS signatures and enforce complete type coverage, 100% line and branch coverage, and measured CRAP scores no greater than 10.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leonid Svyatov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Hawk Ruby
|
|
2
|
+
|
|
3
|
+
Ruby error reporting for [Hawk](https://hawk.so), with optional Rails and background job integrations.
|
|
4
|
+
|
|
5
|
+
The gem reports exceptions, messages, backtraces, source snippets, breadcrumbs, users, context, and application releases. It uses Hawk's native collector format.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Add the gem to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "hawk-ruby", "~> 0.1.0", require: "hawk"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
To test a built artifact locally:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
gem build hawk-ruby.gemspec
|
|
23
|
+
gem install ./hawk-ruby-0.1.0.gem
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The core requires CRuby 3.3 or later and packaged standard libraries. Rails and job frameworks are optional.
|
|
27
|
+
|
|
28
|
+
## Configure and capture
|
|
29
|
+
|
|
30
|
+
Copy the integration token from your Hawk project into `HAWK_TOKEN`.
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "hawk"
|
|
34
|
+
|
|
35
|
+
Hawk.init do |config|
|
|
36
|
+
config.token = ENV.fetch("HAWK_TOKEN")
|
|
37
|
+
config.release = ENV["HAWK_RELEASE"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Hawk.set_context(component: "checkout")
|
|
41
|
+
Hawk.set_user(id: "customer-42")
|
|
42
|
+
Hawk.add_breadcrumb(message: "Payment started", category: "checkout")
|
|
43
|
+
|
|
44
|
+
Hawk.capture do
|
|
45
|
+
perform_checkout
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Hawk.capture_message("Checkout recovered", level: :warning)
|
|
49
|
+
Hawk.flush(timeout: 5.0)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`Hawk.capture` reports an escaping exception and raises the same exception again. You can also call `Hawk.capture_exception(error)` from a rescue handler.
|
|
53
|
+
|
|
54
|
+
A missing or empty token disables reporting. A malformed nonempty token raises `Hawk::ConfigurationError` during initialization.
|
|
55
|
+
|
|
56
|
+
## Rails and background jobs
|
|
57
|
+
|
|
58
|
+
Use `require: "hawk/rails"` in your Gemfile for Rails. Initialize Hawk in `config/initializers/hawk.rb` before workers start.
|
|
59
|
+
|
|
60
|
+
Rails enables request capture, `Rails.error`, Action Cable, Active Job, and safe notification breadcrumbs. Solid Queue works through Active Job.
|
|
61
|
+
|
|
62
|
+
Load each additional adapter explicitly:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require "hawk/rack"
|
|
66
|
+
require "hawk/rake"
|
|
67
|
+
require "hawk/sidekiq"
|
|
68
|
+
require "hawk/resque"
|
|
69
|
+
require "hawk/delayed_job"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Rails also detects loaded job backends during initialization. The adapters preserve native retries, arguments, and failure storage.
|
|
73
|
+
|
|
74
|
+
See [integrations](docs/integrations.md) for placement, supported versions, propagation limits, and exclusions.
|
|
75
|
+
|
|
76
|
+
## Delivery and privacy
|
|
77
|
+
|
|
78
|
+
The default client uses one background thread and a queue of 100 events. Capture never waits for queue space. Each delivery makes one bounded HTTP attempt.
|
|
79
|
+
|
|
80
|
+
The queue is held in memory. An accepted capture ID confirms local acceptance. `flush` confirms that pending attempts finished, including failed attempts.
|
|
81
|
+
|
|
82
|
+
Request bodies, headers, cookies, and job arguments are excluded by default. Sensitive keys are filtered before hooks run. Review [configuration](docs/configuration.md) before enabling additional data.
|
|
83
|
+
|
|
84
|
+
Source snippets are enabled within configured source roots. Set `send_source = false` to exclude them.
|
|
85
|
+
|
|
86
|
+
## Development checks
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
bundle exec rake check:fast
|
|
90
|
+
bundle exec rake check:coverage
|
|
91
|
+
bundle exec rake check:crap
|
|
92
|
+
bundle exec rake check:audit
|
|
93
|
+
bundle exec rake test:package
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Coverage runs the 13 compatibility lanes for the current Ruby. Install their bundles first with `bundle install --gemfile gemfiles/NAME.gemfile`.
|
|
97
|
+
|
|
98
|
+
The CI workflow defines 39 runtime combinations across Ruby 3.3, 3.4, and 4.0. It enforces complete types, 100% line and branch coverage, and measured CRAP scores at most 10.
|
|
99
|
+
|
|
100
|
+
Use `bundle exec rake check:complexity` for local complexity feedback without coverage. See [verification](docs/verification.md) for observed results and remaining acceptance gaps.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Configure the default client once, before requests or jobs begin. Reinitialization closes the previous default client.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Hawk.init do |config|
|
|
7
|
+
config.token = ENV.fetch("HAWK_TOKEN")
|
|
8
|
+
config.environment = "production"
|
|
9
|
+
config.enabled_environments = ["production", "staging"]
|
|
10
|
+
config.source_roots = [File.expand_path("..", __dir__)]
|
|
11
|
+
config.filter_parameters = ["session_id", /private_/i]
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Identity and capture policy
|
|
16
|
+
|
|
17
|
+
| Setting | Default | Meaning |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `token` | `ENV['HAWK_TOKEN']` | Hawk project integration token |
|
|
20
|
+
| `endpoint` | Derived HTTPS collector | Custom HTTPS endpoint; HTTP permits loopback IP literals only |
|
|
21
|
+
| `environment` | `RAILS_ENV`, `RACK_ENV`, or `development` | Event environment |
|
|
22
|
+
| `enabled_environments` | `nil` | All environments; an array restricts capture |
|
|
23
|
+
| `release` | `HAWK_RELEASE` or `REVISION` | Application revision attached to events |
|
|
24
|
+
| `detect_release` | `false` | Detect Git revision once during initialization, with a one-second deadline |
|
|
25
|
+
| `sample_rate` | `1.0` | Capture probability between zero and one |
|
|
26
|
+
| `ignored_exceptions` | `[]` | Exception ancestor names to exclude |
|
|
27
|
+
| `global_errors` | `true` | Capture uncaught main-process exceptions and drain at exit |
|
|
28
|
+
| `context`, `user` | `{}`, `nil` | Initial explicit context and user |
|
|
29
|
+
|
|
30
|
+
Automatic reporting excludes `SystemExit`, `SignalException`, and `Interrupt`. Manual exception capture remains available for those classes.
|
|
31
|
+
|
|
32
|
+
Automatic request reporting also excludes routing errors, missing actions, and missing Active Record records. Explicit `Rails.error.report` calls remain reportable.
|
|
33
|
+
|
|
34
|
+
## Delivery
|
|
35
|
+
|
|
36
|
+
| Setting | Default | Meaning |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `delivery_mode` | `:async` | Use one queue worker; `:sync` performs the attempt in the caller |
|
|
39
|
+
| `queue_capacity` | `100` | Maximum waiting events; a full queue rejects new captures |
|
|
40
|
+
| `connect_timeout` | `1.0` | Connection timeout in seconds |
|
|
41
|
+
| `read_timeout`, `write_timeout` | `2.0` | Socket timeouts in seconds |
|
|
42
|
+
| `flush_timeout` | `5.0` | Default flush and close deadline |
|
|
43
|
+
| `limit_cooldown` | `60.0` | Default cooldown after collector limits |
|
|
44
|
+
|
|
45
|
+
Each HTTP attempt has an additional five-second total deadline. Redirects and ambiguous transport failures are not retried.
|
|
46
|
+
|
|
47
|
+
Collector responses must contain a successful HTTP status of 200 and a JSON body with `code: 200` and `error: false`.
|
|
48
|
+
|
|
49
|
+
Responses with status 402 or 429 pause sends. Valid `Retry-After` seconds or dates are bounded to 300 seconds.
|
|
50
|
+
|
|
51
|
+
`Hawk.flush(timeout: 5.0)` waits for pending attempts. `Hawk.close(timeout: 5.0)` also stops accepting new events.
|
|
52
|
+
|
|
53
|
+
A false return means the operation could not finish within its contract. A true flush does not establish successful receipt by Hawk.
|
|
54
|
+
|
|
55
|
+
Forked children discard inherited queues and recreate synchronization before use. Parents retain their own queues. Abrupt termination can lose pending events.
|
|
56
|
+
|
|
57
|
+
## Scope and independent clients
|
|
58
|
+
|
|
59
|
+
Scopes are local to each client, thread, and fiber. `with_scope` copies current values and restores the parent scope after the block.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Hawk.with_scope do |scope|
|
|
63
|
+
scope.set_context(order_id: "order-42")
|
|
64
|
+
scope.set_user(id: "customer-42")
|
|
65
|
+
Hawk.capture_message("Order accepted")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
configuration = Hawk::Configuration.new
|
|
69
|
+
configuration.token = ENV.fetch("OTHER_HAWK_TOKEN")
|
|
70
|
+
other = Hawk::Client.build(configuration)
|
|
71
|
+
other.capture_exception(RuntimeError.new("Independent project"))
|
|
72
|
+
other.close
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Automatic request, channel, and job boundaries start fresh scopes. Explicit global configuration supplies their initial context.
|
|
76
|
+
|
|
77
|
+
Events receive an anonymous user identifier when no explicit identifier exists. That identifier is scoped to the execution and is not propagated to jobs.
|
|
78
|
+
|
|
79
|
+
Manual captures can repeat. Automatic captures suppress repeated propagation of the same exception object within one execution.
|
|
80
|
+
|
|
81
|
+
Owned request and job boundaries retain at most 256 exception identities and reject additional automatic reports within that boundary. Outside those boundaries, the ledger evicts its oldest identity so long-running reporting continues. An evicted exception can be reported again.
|
|
82
|
+
|
|
83
|
+
## Privacy and limits
|
|
84
|
+
|
|
85
|
+
| Setting | Default |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `include_headers`, `include_cookies` | `false` |
|
|
88
|
+
| `include_request_body`, `include_job_arguments` | `false` |
|
|
89
|
+
| `propagate_job_context` | `true` |
|
|
90
|
+
| `filter_parameters` | `[]` |
|
|
91
|
+
| `send_source` | `true` |
|
|
92
|
+
| `source_roots` | `[Dir.pwd]` |
|
|
93
|
+
|
|
94
|
+
Built-in filtering covers sensitive key names, including passwords, secrets, authorization, API keys, and payment cards. Add application-specific names to `filter_parameters`.
|
|
95
|
+
|
|
96
|
+
Rails also applies `config.filter_parameters`. Opted-in raw bodies and cookie strings need explicit filtering or exclusion when their contents contain secrets.
|
|
97
|
+
|
|
98
|
+
Cookie headers are excluded from generic header capture. Only `include_cookies` enables the dedicated cookie field. Rails filters receive each original parameter or context tree once; stored breadcrumbs and propagated job values retain their filtered state.
|
|
99
|
+
|
|
100
|
+
The SDK uses parsed parameters already present in the request environment. It does not read or rewind the request input stream.
|
|
101
|
+
|
|
102
|
+
Source files must resolve inside a configured root. Source extraction excludes missing files, symlink escapes, nonregular files, and files over 1 MiB.
|
|
103
|
+
|
|
104
|
+
Events contain at most 100 backtrace frames, 10 causes, and 50 breadcrumbs. Source excerpts contain at most 11 lines.
|
|
105
|
+
|
|
106
|
+
JSON copying bounds depth, collection width, strings, and total nodes. It does not call application serializers or `inspect`.
|
|
107
|
+
|
|
108
|
+
Encoded events are limited to 256 KiB. Size reduction removes source snippets and optional detail before reducing backtraces. Events that remain oversized are dropped.
|
|
109
|
+
|
|
110
|
+
## Hooks and breadcrumbs
|
|
111
|
+
|
|
112
|
+
`before_send` receives detached, filtered event data without the integration token. Return a hash to send, or `nil` to drop.
|
|
113
|
+
|
|
114
|
+
`before_breadcrumb` receives a detached, filtered breadcrumb. Return a hash to retain it, or `nil` to drop.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
Hawk.init do |config|
|
|
118
|
+
config.before_send = ->(event) { event.merge("title" => "Application failure") }
|
|
119
|
+
config.before_breadcrumb = ->(entry) { entry }
|
|
120
|
+
config.breadcrumbs_logger = true
|
|
121
|
+
config.breadcrumbs_http = true
|
|
122
|
+
config.logger_error_level = Logger::ERROR
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Hook errors or invalid results drop only the affected event or breadcrumb. Hooks can add data, so their output needs the same privacy review as application logging.
|
|
127
|
+
|
|
128
|
+
Automatic Logger, HTTP, and Rails breadcrumb instrumentation is suppressed while SDK hooks run. Application logging and message-block evaluation continue normally.
|
|
129
|
+
|
|
130
|
+
Logger and Net::HTTP breadcrumbs require opt-in. Logger messages use normal Logger evaluation. HTTP breadcrumbs omit queries, credentials, headers, and bodies.
|
|
131
|
+
|
|
132
|
+
Rails notifications record operation names and timing. SQL text, bindings, mail content, and job arguments are excluded by default.
|
|
133
|
+
|
|
134
|
+
`breadcrumbs_rails` defaults to true with Rails. Set `logger_error_level` to convert qualifying logger messages into events; it defaults to disabled.
|
|
135
|
+
|
|
136
|
+
## Integration controls and diagnostics
|
|
137
|
+
|
|
138
|
+
Set entries in `integrations` to false to disable `:rails`, `:rack`, `:rake`, `:action_cable`, `:active_job`, `:sidekiq`, `:resque`, or `:delayed_job`.
|
|
139
|
+
|
|
140
|
+
The `:rails` switch also controls Rails error reports, Cable, Active Job, and Rails breadcrumbs after reinitialization. Direct backend adapters retain their own switches.
|
|
141
|
+
|
|
142
|
+
`diagnostic_callback` receives counter snapshots. `diagnostic_logger` receives sanitized warning messages with the same counters.
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
Hawk.init do |config|
|
|
146
|
+
config.diagnostic_callback = ->(counts) { report_delivery_counters(counts) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
Hawk.client.diagnostics.snapshot
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Counters include `sent`, `failed`, `overflow`, `filtered`, and `duplicate_overflow`. Only observed counters appear. Diagnostic failures do not replace application results.
|
|
153
|
+
|
|
154
|
+
## Releases
|
|
155
|
+
|
|
156
|
+
Set `HAWK_RELEASE` to the revision deployed with the application. Use the same identifier when uploading commit metadata through [Hawk's release uploader](https://github.com/codex-team/hawk.releases).
|
|
157
|
+
|
|
158
|
+
The uploader documents the `codex-team/hawk.releases@v1` action with `release` and `token` inputs. Run it during deployment.
|
|
159
|
+
|
|
160
|
+
[Hawk associates events and releases by matching release names](https://docs.hawk.so/releases). This gem attaches the identifier; deployment tooling uploads the commit metadata.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Integrations
|
|
2
|
+
|
|
3
|
+
Load optional adapters before workers start. Keep their framework gems in the application's Gemfile.
|
|
4
|
+
|
|
5
|
+
## Supported versions
|
|
6
|
+
|
|
7
|
+
| Component | Tested versions |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| CRuby | 3.3.11, 3.4.9, 4.0.9 |
|
|
10
|
+
| Rails, Active Job, Action Cable | 7.2.4, 8.0.5.1, 8.1.4 |
|
|
11
|
+
| Rack | 2.2.24, 3.2.7 |
|
|
12
|
+
| Sidekiq | 7.3.10, 8.1.7 |
|
|
13
|
+
| Resque | 2.7.0 |
|
|
14
|
+
| Delayed Job, Active Record backend | 4.2.0, 4.1.11 |
|
|
15
|
+
| Solid Queue | 1.7.0 |
|
|
16
|
+
| Rake | 13.4.2 |
|
|
17
|
+
|
|
18
|
+
Sidekiq 8.1.7 and Rails 7.2.4 cannot share a bundle: their `connection_pool` requirements conflict. Sidekiq 7 runs with Rails 7.2 and 8.1.
|
|
19
|
+
|
|
20
|
+
The adapters guard their supported framework versions. Resque activation requires version 2.7.0. The core uses normal RubyGems or Bundler activation.
|
|
21
|
+
|
|
22
|
+
## Rack
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "hawk/rack"
|
|
26
|
+
|
|
27
|
+
Hawk.init { |config| config.token = ENV.fetch("HAWK_TOKEN") }
|
|
28
|
+
use Hawk::Rack
|
|
29
|
+
run Application.new
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Place Hawk inside middleware that consumes application exceptions. It must receive the escaping error to report it.
|
|
33
|
+
|
|
34
|
+
The middleware retains request scope through response iteration, callable streaming bodies, and close. File bodies retain their `to_path` protocol.
|
|
35
|
+
|
|
36
|
+
Request metadata includes method, path, available parsed parameters, request ID, controller, and action. Raw query strings and request input streams are excluded.
|
|
37
|
+
|
|
38
|
+
Use `Hawk::Rack.new(app, client: other_client)` for an independent project client.
|
|
39
|
+
|
|
40
|
+
## Rails requests and framework reports
|
|
41
|
+
|
|
42
|
+
Use `require: "hawk/rails"` in your Gemfile. The Railtie inserts Hawk inside `ActionDispatch::DebugExceptions`.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# config/initializers/hawk.rb
|
|
46
|
+
Hawk.init do |config|
|
|
47
|
+
config.token = ENV.fetch("HAWK_TOKEN")
|
|
48
|
+
config.environment = Rails.env
|
|
49
|
+
config.release = ENV["HAWK_RELEASE"]
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If Rails is already loaded, requiring `hawk` also discovers its Railtie. Rails filtering runs before the application's `before_send` hook.
|
|
54
|
+
|
|
55
|
+
The subscriber records explicit `Rails.error.report` calls, including handled errors and severity. Request dispatch reports share ownership with middleware to prevent duplicates.
|
|
56
|
+
|
|
57
|
+
Automatic requests exclude `ActionController::RoutingError`, `AbstractController::ActionNotFound`, `ActionController::UnknownAction`, and `ActiveRecord::RecordNotFound`.
|
|
58
|
+
|
|
59
|
+
Explicit framework reports and manual captures can still report those exceptions.
|
|
60
|
+
|
|
61
|
+
## Action Cable
|
|
62
|
+
|
|
63
|
+
The Rails adapter wraps channel actions, subscription changes, and connection open and close operations. Each invocation receives a fresh scope.
|
|
64
|
+
|
|
65
|
+
Application rescue handlers keep their normal return values and behavior. Hawk observes errors consumed by Cable's handler path.
|
|
66
|
+
|
|
67
|
+
Metadata includes the channel or connection class and operation. Message bodies and inferred application identities are excluded.
|
|
68
|
+
|
|
69
|
+
## Active Job and Solid Queue
|
|
70
|
+
|
|
71
|
+
Rails activates the Active Job adapter. It covers direct execution, backend execution, deserialization errors, terminal discards, and exhausted retries.
|
|
72
|
+
|
|
73
|
+
Native retry schedules and application rescue handlers remain unchanged. Intermediate handled retries generate events only when Rails explicitly reports them.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
Hawk.with_scope do
|
|
77
|
+
Hawk.set_context(section: "checkout")
|
|
78
|
+
Hawk.set_user(id: "customer-42")
|
|
79
|
+
ReceiptJob.perform_later("order-42")
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The serialized job receives a separate `hawk` field containing versioned explicit context and user data. Job arguments remain unchanged.
|
|
84
|
+
|
|
85
|
+
Metadata is filtered, JSON-only, and limited to 8 KiB. Malformed, oversized, or unknown-version metadata is ignored.
|
|
86
|
+
|
|
87
|
+
Version 1 metadata is filtered by its producer. Consumers preserve that state, including when a job enqueues another job. Use the same custom Rails privacy filters on producers and consumers; a consumer-only filter change does not transform previously queued values. New explicit scope values and event overrides receive filtering normally.
|
|
88
|
+
|
|
89
|
+
Automatic request metadata, breadcrumbs, and anonymous users are not propagated. Set `propagate_job_context = false` to disable propagation.
|
|
90
|
+
|
|
91
|
+
Solid Queue uses this same Active Job contract. Tests exercise its database-backed claim and execution paths.
|
|
92
|
+
|
|
93
|
+
## Sidekiq
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
require "hawk/sidekiq"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Client middleware adds versioned context under a separate top-level `hawk` field. Server middleware starts an execution scope.
|
|
100
|
+
|
|
101
|
+
The backend handler covers failures before server middleware. A failed native attempt produces one automatic event across both paths.
|
|
102
|
+
|
|
103
|
+
Each failed retry attempt is independently reportable. Active Job retries that Rails handles internally follow the Active Job policy.
|
|
104
|
+
|
|
105
|
+
The adapter retains native payload arguments and retry behavior. Raw arguments require `include_job_arguments = true`.
|
|
106
|
+
|
|
107
|
+
## Resque
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
require "hawk/resque"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The adapter wraps job execution and delegates failure creation to the configured backend. Failed constantization and other failures before execution reach that backend path.
|
|
114
|
+
|
|
115
|
+
Forked children reset inherited delivery state and flush their own events before Resque exits. A parent does not resend its queued events from a child.
|
|
116
|
+
|
|
117
|
+
Direct Resque arguments and queue payloads remain unchanged. Direct jobs start fresh explicit scopes because this adapter adds no arbitrary queue metadata.
|
|
118
|
+
|
|
119
|
+
Active Job wrappers retain their own serialized context. Raw direct arguments require opt-in.
|
|
120
|
+
|
|
121
|
+
## Delayed Job
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require "hawk/delayed_job"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Load the adapter before starting a worker. Its plugin observes perform, error, and failure callbacks, including handler deserialization failures.
|
|
128
|
+
|
|
129
|
+
Native attempts, retry times, and terminal failure state remain unchanged. Direct job YAML and database columns remain unchanged.
|
|
130
|
+
|
|
131
|
+
Active Job wrappers carry their own versioned context. Direct jobs start fresh scopes without adding attributes to application payload objects.
|
|
132
|
+
|
|
133
|
+
## Rake and process shutdown
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
require "hawk/rake"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The Rake adapter reports escaping task failures and retains original task behavior. Nested task failures share automatic-report ownership.
|
|
140
|
+
|
|
141
|
+
Core initialization installs one process exit hook when `global_errors` is enabled. It reports eligible uncaught main-process errors and attempts a bounded drain.
|
|
142
|
+
|
|
143
|
+
Handled application errors require an explicit capture. For independently managed clients, call `close` during application shutdown.
|
|
144
|
+
|
|
145
|
+
## Scope of this SDK
|
|
146
|
+
|
|
147
|
+
This SDK implements Hawk error reporting. Tracing, profiling, metrics, cron monitoring, and standalone log ingestion are outside its scope.
|
|
148
|
+
|
|
149
|
+
Delivery uses an in-memory queue and one attempt per event. Job backends own retries. Use delivery counters to distinguish accepted captures from successful collector responses.
|
data/docs/releasing.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
The `release.yml` workflow publishes version tags after the complete CI matrix passes. The tag must match `Hawk::VERSION` and belong to `main`.
|
|
4
|
+
|
|
5
|
+
RubyGems trusts the `svyatov/hawk.ruby` repository, the `release.yml` workflow, and the `release` environment. The environment permits `v*` tags.
|
|
6
|
+
|
|
7
|
+
The workflow builds the gem once, generates its checksum and GitHub provenance attestation, and stores the artifact for 14 days. Publishing downloads those exact bytes.
|
|
8
|
+
|
|
9
|
+
To cut a version:
|
|
10
|
+
|
|
11
|
+
1. Update the version, changelog, and installation example. Run the checks and merge to `main`.
|
|
12
|
+
2. Create an annotated version tag, for example `git tag -a v0.1.0 -m 'Release 0.1.0'`.
|
|
13
|
+
3. Push the tag with `git push origin v0.1.0` and monitor the Release workflow.
|
|
14
|
+
|
|
15
|
+
## Recover a partial publication
|
|
16
|
+
|
|
17
|
+
Use GitHub's failed-jobs rerun, for example `gh run rerun RUN_ID --failed --repo svyatov/hawk.ruby`. This preserves the successful build and its archived artifact.
|
|
18
|
+
|
|
19
|
+
If RubyGems already has the version, publication continues only when its SHA256 matches the archived gem. A mismatch or failed registry lookup stops the release.
|
|
20
|
+
|
|
21
|
+
GitHub releases start as drafts. A retry verifies the tag and commit, uploads missing assets, and publishes the draft. Existing assets must have matching checksums; they are never replaced.
|
|
22
|
+
|
|
23
|
+
Do not rebuild a published version or move its tag. If the archived artifact has expired, recover and verify the original published bytes before completing the GitHub release.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Verification
|
|
2
|
+
|
|
3
|
+
## Local acceptance
|
|
4
|
+
|
|
5
|
+
The implementation was tested on macOS with CRuby 3.3.11, 3.4.9, and 4.0.9.
|
|
6
|
+
|
|
7
|
+
All 39 supported runtime combinations passed. Each Ruby's 13 lanes reached 100% line and branch coverage after merging fresh subprocess and fork reports.
|
|
8
|
+
|
|
9
|
+
The matrix includes separate Rack 2 and 3 bundles, all three Rails versions, Cable, Active Job, and real job backend fixtures.
|
|
10
|
+
|
|
11
|
+
Redis fixtures start isolated servers. Database fixtures use temporary SQLite files. Tests do not reuse application queues or databases.
|
|
12
|
+
|
|
13
|
+
| Gate | Observed result |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| RuboCop 1.92.2 | No offenses |
|
|
16
|
+
| Steep 2.1.0 and Ruby Signature (RBS) 4.2.0 | 37 runtime files, zero untyped calls, valid signatures |
|
|
17
|
+
| SimpleCov 1.3.2 | 100% executable lines and branches |
|
|
18
|
+
| crap4ruby 0.2.1 | All method scores measured, maximum CRAP 7, limit 10 |
|
|
19
|
+
| bundler-audit 0.9.3 | 13 lockfiles, no reported vulnerabilities after database refresh |
|
|
20
|
+
|
|
21
|
+
Negative gate fixtures reject omitted files, duplicate statistics, failed analysis, untyped calls, invalid counts, unchecked casts, and blanket suppressions.
|
|
22
|
+
|
|
23
|
+
Coverage fixtures reject stale revisions, changed sources, missing lanes, missing runtime files, and uncovered lines or branches. CRAP checks reject unknown method scores.
|
|
24
|
+
|
|
25
|
+
Steep internal crashes fail the gate even when Steep returns a successful exit status. Coverage receipts bind CRAP input to the verified source hashes and report hash.
|
|
26
|
+
|
|
27
|
+
## Resolved framework fixtures
|
|
28
|
+
|
|
29
|
+
| Family | Resolved versions |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Rails | 7.2.4, 8.0.5.1, 8.1.4 |
|
|
32
|
+
| Rack | 2.2.24, 3.2.7 |
|
|
33
|
+
| Sidekiq | 7.3.10, 8.1.7 |
|
|
34
|
+
| Resque | 2.7.0 |
|
|
35
|
+
| Delayed Job and its Active Record backend | 4.2.0, 4.1.11 |
|
|
36
|
+
| Solid Queue and SQLite | 1.7.0, 2.10.2 |
|
|
37
|
+
| Rake | 13.4.2 |
|
|
38
|
+
|
|
39
|
+
Sidekiq 7 uses Rails 7.2 and 8.1 fixtures. Sidekiq 8 uses Rails 8.1. Bundler rejects Sidekiq 8.1.7 with Rails 7.2.4 because their `connection_pool` requirements conflict.
|
|
40
|
+
|
|
41
|
+
The matrix is defined in `script/test_lanes.rb`. GitHub Actions consumes that definition and requires all 39 lanes before coverage merging.
|
|
42
|
+
|
|
43
|
+
## Installed artifact acceptance
|
|
44
|
+
|
|
45
|
+
Independent runtime QA passed 44 public-interface scenarios on Ruby 4.0.9, including Rails reports and Active Job propagation.
|
|
46
|
+
|
|
47
|
+
The pass reproduced two defects and verified their fixes: request metadata timing and nonstandard hook exceptions. Regression tests cover both paths.
|
|
48
|
+
|
|
49
|
+
Final-review regressions and focused public-interface QA also verify cookie controls, Rails parameter roots, bounded duplicate retention, hook logging, integration reinitialization, and filtered job propagation.
|
|
50
|
+
|
|
51
|
+
`bundle exec rake test:package` builds the gem and installs it into a temporary gem home with only runtime dependencies.
|
|
52
|
+
|
|
53
|
+
The fixture loads the installed artifact in a core-only Bundler environment. It checks plain-load thread behavior and sends one event to a loopback collector.
|
|
54
|
+
|
|
55
|
+
The checker compares every packaged runtime file with its checked source, validates installed RBS signatures, and tests isolation from missing optional dependencies.
|
|
56
|
+
|
|
57
|
+
The package excludes development dependencies, external framework signatures, test fixtures, execution ledgers, coverage reports, and planning documents.
|
|
58
|
+
|
|
59
|
+
## Reproduce
|
|
60
|
+
|
|
61
|
+
Install the core bundle and each framework bundle first. Run these checks from the repository root:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bundle exec rake check:fast
|
|
65
|
+
bundle exec rake check:coverage
|
|
66
|
+
bundle exec rake check:crap
|
|
67
|
+
bundle exec rake check:audit
|
|
68
|
+
bundle exec rake test:package
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use `mise exec ruby@3.3.11 --` or `mise exec ruby@3.4.9 --` before the commands to select another installed Ruby.
|
|
72
|
+
|
|
73
|
+
Coverage manifests contain a run identifier, commit, lane, and hashes of all runtime sources. Old runs cannot satisfy the merge gate after sources change.
|
|
74
|
+
|
|
75
|
+
## Remaining acceptance gaps
|
|
76
|
+
|
|
77
|
+
Remote Linux results are recorded in the [CI and release workflow runs](https://github.com/svyatov/hawk.ruby/actions). Each release requires the complete CI matrix before publication.
|
|
78
|
+
|
|
79
|
+
Live Hawk ingestion and UI rendering are unverified. No dedicated live-test token was supplied or used.
|
|
80
|
+
|
|
81
|
+
Live acceptance must inspect backtraces, source snippets, users, context, breadcrumbs, severity, and grouping in a dedicated Hawk project.
|
|
82
|
+
|
|
83
|
+
Release association also requires deployment-time commit metadata uploaded with the same release identifier. Local collector tests do not verify that association.
|
|
84
|
+
|
|
85
|
+
The collector protocol and Ruby event routing were checked against Hawk's documentation and server sources. Live verification remains necessary before a production rollout.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hawk
|
|
4
|
+
# Adapters share report ownership while retaining their own execution scopes.
|
|
5
|
+
module Boundary
|
|
6
|
+
def self.run(client, kind:, metadata:, inherit: false, &block)
|
|
7
|
+
client.with_boundary(kind: kind, metadata: metadata, inherit: inherit) do
|
|
8
|
+
protect(client, kind, &block)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.resume(client, scope, &block)
|
|
13
|
+
client.using_scope(scope) { protect(client, :request, &block) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.protect(client, kind)
|
|
17
|
+
yield
|
|
18
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- Preserve original errors across execution boundaries.
|
|
19
|
+
client.report(e, mechanism: kind.to_s)
|
|
20
|
+
client.remember_escape(e)
|
|
21
|
+
raise
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private_class_method :protect
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module Hawk
|
|
6
|
+
module Breadcrumbs
|
|
7
|
+
# Logger keeps control of level filtering and lazy block evaluation.
|
|
8
|
+
module Logger
|
|
9
|
+
LEVELS = %i[debug info warning error fatal unknown].freeze
|
|
10
|
+
|
|
11
|
+
def self.install
|
|
12
|
+
::Logger.prepend(Methods) unless ::Logger.ancestors.include?(Methods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.record(client, severity, message)
|
|
16
|
+
normalized = Hawk::JSONValue.normalize(message)
|
|
17
|
+
text = normalized.is_a?(String) ? normalized : JSON.generate(normalized)
|
|
18
|
+
level = LEVELS.fetch(severity, :unknown)
|
|
19
|
+
if client.configuration.breadcrumbs_logger
|
|
20
|
+
client.add_breadcrumb(category: "logger", message: text,
|
|
21
|
+
level: level.to_s)
|
|
22
|
+
end
|
|
23
|
+
threshold = client.configuration.logger_error_level
|
|
24
|
+
client.capture_message(text, level: level) if threshold && severity >= threshold
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.empty_message?(message)
|
|
30
|
+
case message
|
|
31
|
+
when nil then true
|
|
32
|
+
else false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.message(value, progname)
|
|
37
|
+
empty_message?(value) ? progname : value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Calls through Logger's public add entry point, including its log alias.
|
|
41
|
+
module Methods
|
|
42
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
43
|
+
client = Hawk::Breadcrumbs.client(:logger)
|
|
44
|
+
severity ||= ::Logger::UNKNOWN
|
|
45
|
+
return super unless client && severity >= level
|
|
46
|
+
|
|
47
|
+
if block && Logger.empty_message?(message)
|
|
48
|
+
# Explicit arguments avoid Steep 2.1's crash with a replacement block.
|
|
49
|
+
super(severity, message, progname) do # rubocop:disable Style/SuperArguments
|
|
50
|
+
value = block.call
|
|
51
|
+
Logger.record(client, severity, value)
|
|
52
|
+
value
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
result = super
|
|
56
|
+
Logger.record(client, severity, Logger.message(message, progname))
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
alias log add
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|