chronos-ruby 0.3.0.pre.1 → 0.5.0.pre.1
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/CHANGELOG.md +23 -0
- data/README.md +55 -17
- data/contracts/event-v1.schema.json +25 -11
- data/contracts/rack-context-v1.schema.json +43 -0
- data/docs/adr/ADR-012-rack-context-isolation.md +27 -0
- data/docs/adr/ADR-013-legacy-rails-notifications.md +27 -0
- data/docs/architecture.md +17 -3
- data/docs/compatibility.md +6 -6
- data/docs/configuration.md +13 -2
- data/docs/data-collected.md +15 -2
- data/docs/modules/rack-context.md +59 -0
- data/docs/modules/rails-legacy.md +60 -0
- data/docs/modules/telemetry-events.md +9 -0
- data/docs/performance.md +28 -3
- data/docs/privacy-lgpd.md +13 -3
- data/docs/troubleshooting.md +18 -2
- data/lib/chronos/adapters/thread_local_context_store.rb +52 -0
- data/lib/chronos/agent.rb +97 -6
- data/lib/chronos/application/capture_exception.rb +2 -2
- data/lib/chronos/application/capture_telemetry.rb +37 -0
- data/lib/chronos/application/remote_configuration.rb +1 -1
- data/lib/chronos/configuration/snapshot.rb +53 -0
- data/lib/chronos/configuration/validation.rb +122 -0
- data/lib/chronos/configuration.rb +22 -134
- data/lib/chronos/core/breadcrumb.rb +126 -0
- data/lib/chronos/core/telemetry_event.rb +106 -0
- data/lib/chronos/integrations/rack/middleware.rb +156 -0
- data/lib/chronos/integrations/rack.rb +12 -0
- data/lib/chronos/integrations.rb +10 -0
- data/lib/chronos/ports/context_store.rb +22 -0
- data/lib/chronos/rails/installer.rb +70 -0
- data/lib/chronos/rails/notifications_subscriber.rb +203 -0
- data/lib/chronos/rails/railtie.rb +21 -0
- data/lib/chronos/rails.rb +16 -0
- data/lib/chronos/version.rb +1 -1
- data/lib/chronos.rb +46 -1
- data/lib/generators/chronos/install/install_generator.rb +25 -0
- data/lib/generators/chronos/install/templates/chronos.rb +20 -0
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8475c8f2a946a1c0e512d37b01d9931a47df75c2446c6c898a8d8a795db4aff3
|
|
4
|
+
data.tar.gz: 533465780682cdbfad58087cae0458c513d7086828350587728b970093be5264
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 456b339239a6fdd7b11706e2cd2c4be59dbf11e015354e95f60a7ebffb07305f66df1bdc79a77c7b8f9a776b9ba66325f288af6d954b5027808d1e85d1992097
|
|
7
|
+
data.tar.gz: 29126344b372211a3940eb7b58a66ae8e10f5769090081a9dfefaedee4001fdc949a03b0511b3492ae783e1ba0d99b24f1adbfba6b9c8775123e1b03f88e6fa2
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,29 @@ All notable changes are documented here. The project follows Semantic Versioning
|
|
|
11
11
|
- legacy CI now resolves Bundler 1.17.3 through `Gem.bin_path` on RubyGems versions that do not support the `_version_` executable selector.
|
|
12
12
|
- documentation verification now reads source and Markdown files explicitly as UTF-8 on legacy container locales.
|
|
13
13
|
|
|
14
|
+
## [0.4.0.pre.1] - 2026-07-20
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Rack-protocol middleware for automatic exception capture with propagation of the original failure;
|
|
19
|
+
- request context for method, normalized route, status, duration, request ID, optional user agent, host, path, controller/action, approximate response size, user, and sanitized parameters;
|
|
20
|
+
- selectable context-store port with a thread-local legacy adapter and guaranteed scope cleanup;
|
|
21
|
+
- bounded circular breadcrumbs for custom, log, request, query, external HTTP, cache, and job categories;
|
|
22
|
+
- public `Chronos.with_context` and `Chronos.add_breadcrumb` APIs;
|
|
23
|
+
- Rack context contract, concurrency tests, module documentation, ADR, executable example, and request-overhead benchmark.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- manual notifications now inherit the current execution context and breadcrumbs;
|
|
28
|
+
- version advanced to `0.4.0.pre.1`.
|
|
29
|
+
|
|
30
|
+
### Known limitations
|
|
31
|
+
|
|
32
|
+
- generic route normalization cannot identify every application-specific dynamic segment;
|
|
33
|
+
- thread-local context does not propagate into application-created threads or fibers;
|
|
34
|
+
- deferred exceptions raised only while enumerating a streaming response body are not captured;
|
|
35
|
+
- Rails-specific installation and route discovery remain planned for version 0.5.
|
|
36
|
+
|
|
14
37
|
## [0.3.0.pre.1] - 2026-07-20
|
|
15
38
|
|
|
16
39
|
### Added
|
data/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# Chronos Ruby
|
|
2
2
|
|
|
3
|
-
Chronos Ruby is the framework-independent client for sending Ruby application errors to Chronos. Version 0.
|
|
3
|
+
Chronos Ruby is the framework-independent client for sending Ruby application errors and bounded telemetry to Chronos. Version 0.5 adds legacy Rails 4.2 and 5.2 installation, controller exception deduplication, and allowlisted request, query, job, and cache events to the Rack, privacy, and resilience foundations from earlier releases.
|
|
4
4
|
|
|
5
5
|
## What the gem collects
|
|
6
6
|
|
|
7
|
-
For each exception, version 0.
|
|
7
|
+
For each exception, version 0.5 can collect:
|
|
8
8
|
|
|
9
9
|
- exception class, message, structured backtrace, and chained causes;
|
|
10
10
|
- timestamp, severity, tags, and an optional fingerprint;
|
|
11
11
|
- application-supplied context, parameters, session, and user fields;
|
|
12
12
|
- Ruby version, engine, platform, process ID, opaque thread ID, and hostname;
|
|
13
13
|
- application version, environment, and service name.
|
|
14
|
+
- Rack method, normalized route, status, duration, request ID, host, query-free path, optional user agent, controller/action, response size, trace ID, and already-parsed parameters when the middleware is used;
|
|
15
|
+
- bounded breadcrumbs explicitly supplied by the application or integration.
|
|
14
16
|
|
|
15
17
|
See [Data collected](docs/data-collected.md) for the complete field table.
|
|
16
18
|
|
|
@@ -20,7 +22,7 @@ Chronos Ruby does not inspect environment variables, request bodies, cookies, HT
|
|
|
20
22
|
|
|
21
23
|
## Supported Ruby and Rails versions
|
|
22
24
|
|
|
23
|
-
Version 0.x targets Ruby 2.2.10 through Ruby 2.6. Version 0.
|
|
25
|
+
Version 0.x targets Ruby 2.2.10 through Ruby 2.6. Version 0.5 provides best-effort Rails 4.2 through 5.2 integration through public framework APIs and feature detection. All supported combinations must pass dedicated CI before being listed as supported.
|
|
24
26
|
|
|
25
27
|
See [Compatibility](docs/compatibility.md).
|
|
26
28
|
|
|
@@ -29,7 +31,7 @@ See [Compatibility](docs/compatibility.md).
|
|
|
29
31
|
The current public build is a pre-release. Add its exact version to the application's `Gemfile`:
|
|
30
32
|
|
|
31
33
|
```ruby
|
|
32
|
-
gem "chronos-ruby", "0.
|
|
34
|
+
gem "chronos-ruby", "0.5.0.pre.1"
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
Install with a Bundler version compatible with the application. For the oldest supported runtime:
|
|
@@ -47,7 +49,19 @@ gem install chronos-ruby --pre
|
|
|
47
49
|
|
|
48
50
|
## Rails installation
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
Version 0.5 exposes Rails support explicitly, keeping Rails and ActiveSupport out of plain Ruby applications:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
gem "chronos-ruby", "0.5.0.pre.1", :require => "chronos/rails"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Generate the initializer with:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
rails generate chronos:install
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The Railtie installs the Rack middleware and notification subscribers idempotently. Automatic integration is disabled in test and console by default and can be controlled with `rails_enabled`, `rails_capture_in_test`, `rails_capture_in_console`, and `rails_capture_user_agent`. See [Rails 4.2 and 5.2 integration](docs/modules/rails-legacy.md).
|
|
51
65
|
|
|
52
66
|
## Minimum configuration
|
|
53
67
|
|
|
@@ -70,7 +84,14 @@ HTTPS verification is enabled by default. HTTP requires explicitly setting `ssl_
|
|
|
70
84
|
|
|
71
85
|
## Automatic capture
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
Rack applications can capture unhandled exceptions automatically and preserve the application error semantics:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
use Chronos::Integrations::Rack::Middleware,
|
|
91
|
+
:include_user_agent => false
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The middleware notifies asynchronously and re-raises the same exception. It never reads the request body, raw query string, cookies, authorization headers, or response body. See [Rack integration and context](docs/modules/rack-context.md).
|
|
74
95
|
|
|
75
96
|
## Manual capture
|
|
76
97
|
|
|
@@ -101,15 +122,25 @@ User data is opt-in and must contain only values your application is allowed to
|
|
|
101
122
|
Chronos.notify(error, :user => {"id" => "customer-42", "role" => "operator"})
|
|
102
123
|
```
|
|
103
124
|
|
|
104
|
-
Version 0.
|
|
125
|
+
Version 0.4 sanitizes this context before delivery and before it can enter retry storage. Data minimization remains the application's responsibility.
|
|
105
126
|
|
|
106
127
|
## Breadcrumbs
|
|
107
128
|
|
|
108
|
-
Breadcrumbs
|
|
129
|
+
Breadcrumbs use a fixed circular buffer scoped to the current execution:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
Chronos.add_breadcrumb(
|
|
133
|
+
:category => "custom",
|
|
134
|
+
:message => "payment started",
|
|
135
|
+
:metadata => {"provider" => "example"}
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
No log, SQL, HTTP, cache, job, request body, or response body payload is collected automatically. Unknown categories become `custom`, and metadata is bounded and sanitized before queueing.
|
|
109
140
|
|
|
110
141
|
## Filters and LGPD
|
|
111
142
|
|
|
112
|
-
Version 0.
|
|
143
|
+
Version 0.4 recursively redacts sensitive keys and detects Bearer tokens, JWTs, e-mail addresses, CPF, CNPJ, and valid payment-card candidates in free text. IPv4 addresses are anonymized by default. Applications can add blocklist matchers, hash selected identifiers, or install custom filters:
|
|
113
144
|
|
|
114
145
|
```ruby
|
|
115
146
|
Chronos.configure do |config|
|
|
@@ -133,19 +164,19 @@ Chronos.configure do |config|
|
|
|
133
164
|
end
|
|
134
165
|
```
|
|
135
166
|
|
|
136
|
-
Local exception-specific ignore callbacks are not available in version 0.
|
|
167
|
+
Local exception-specific ignore callbacks are not available in version 0.4. The server may provide a bounded list of exact fingerprints to ignore; it cannot provide regular expressions or executable rules.
|
|
137
168
|
|
|
138
169
|
## Performance monitoring
|
|
139
170
|
|
|
140
|
-
|
|
171
|
+
Version 0.5 emits bounded `request`, `query`, `job`, and `cache` events from public Rails notifications. SQL text and binds, cache keys and values, job arguments, mail content and recipients, and request or response bodies are never copied. Aggregation, percentiles, query fingerprints, and external HTTP monitoring are not implemented in this version.
|
|
141
172
|
|
|
142
173
|
## Sidekiq and Active Job
|
|
143
174
|
|
|
144
|
-
|
|
175
|
+
Version 0.5 records bounded Active Job execution telemetry when Active Job is available. It includes the job class, queue, and duration, but excludes job IDs and arguments. Sidekiq-specific integration is not implemented.
|
|
145
176
|
|
|
146
177
|
## Deploy tracking
|
|
147
178
|
|
|
148
|
-
Deploy notifications are not implemented in version 0.
|
|
179
|
+
Deploy notifications are not implemented in version 0.4. `app_version` may be included in exception events for release correlation.
|
|
149
180
|
|
|
150
181
|
## Asynchronous queue
|
|
151
182
|
|
|
@@ -168,7 +199,7 @@ Use `Chronos.flush(timeout)` to wait for accepted events and `Chronos.close(time
|
|
|
168
199
|
|
|
169
200
|
## Retry and backlog
|
|
170
201
|
|
|
171
|
-
|
|
202
|
+
The resilience layer introduced in version 0.3 retries network errors, HTTP `408`, `429`, and `5xx` responses with exponential backoff, bounded jitter, and a finite attempt count. Other `4xx` responses are permanent and are not retried. A circuit breaker pauses requests after repeated failures, preventing retry storms.
|
|
172
203
|
|
|
173
204
|
After retries are exhausted, the already sanitized `SerializedEvent` may enter a fixed-capacity memory backlog. The backlog drops new items when full, is lost when the process exits, and never writes to disk. A later successful half-open probe drains backlog items as new events arrive.
|
|
174
205
|
|
|
@@ -182,7 +213,9 @@ The code follows hexagonal boundaries:
|
|
|
182
213
|
- `Chronos::Application` coordinates capture;
|
|
183
214
|
- `Chronos::Application::DeliveryPipeline` owns bounded retry and remote policy;
|
|
184
215
|
- `Chronos::Ports` defines delivery behavior;
|
|
185
|
-
- `Chronos::Adapters` implements Net::HTTP delivery;
|
|
216
|
+
- `Chronos::Adapters` implements Net::HTTP delivery and thread-local context;
|
|
217
|
+
- `Chronos::Integrations::Rack` implements optional automatic Rack capture;
|
|
218
|
+
- `Chronos::Rails` implements the optional Railtie, installer, generator, and public-notification adapters;
|
|
186
219
|
- `Chronos::Internal` owns bounded queueing, workers, and defensive logging.
|
|
187
220
|
|
|
188
221
|
The core has no dependency on Rails, Rack, Sidekiq, or ActiveSupport. See [Architecture](docs/architecture.md).
|
|
@@ -210,6 +243,9 @@ Chronos.configure do |config|
|
|
|
210
243
|
config.circuit_failure_threshold = 5
|
|
211
244
|
config.circuit_reset_timeout = 30.0
|
|
212
245
|
config.remote_configuration = true
|
|
246
|
+
config.context_store = :thread_local
|
|
247
|
+
config.breadcrumb_capacity = 20
|
|
248
|
+
config.breadcrumb_max_bytes = 2048
|
|
213
249
|
end
|
|
214
250
|
```
|
|
215
251
|
|
|
@@ -221,7 +257,7 @@ Configuration errors are raised during `Chronos.configure`. Capture and delivery
|
|
|
221
257
|
|
|
222
258
|
## Benchmark
|
|
223
259
|
|
|
224
|
-
Run the version 0.
|
|
260
|
+
Run the version 0.5 benchmarks with:
|
|
225
261
|
|
|
226
262
|
```bash
|
|
227
263
|
bundle _1.17.3_ exec ruby benchmarks/capture_exception.rb
|
|
@@ -229,13 +265,15 @@ bundle _1.17.3_ exec ruby benchmarks/serialization.rb
|
|
|
229
265
|
bundle _1.17.3_ exec ruby benchmarks/filtering.rb
|
|
230
266
|
bundle _1.17.3_ exec ruby benchmarks/queue.rb
|
|
231
267
|
bundle _1.17.3_ exec ruby benchmarks/retry_backlog.rb
|
|
268
|
+
bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
269
|
+
bundle _1.17.3_ exec ruby benchmarks/rails_notifications.rb
|
|
232
270
|
```
|
|
233
271
|
|
|
234
272
|
Results depend on runtime, hardware, and payload. No performance comparison is claimed until repeatable measurements are published.
|
|
235
273
|
|
|
236
274
|
## Migration from Airbrake
|
|
237
275
|
|
|
238
|
-
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.
|
|
276
|
+
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.5 does not claim API compatibility or automatic replacement.
|
|
239
277
|
|
|
240
278
|
## Local development
|
|
241
279
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"properties": {
|
|
21
21
|
"schema_version": {"const": "1.0"},
|
|
22
22
|
"event_id": {"type": "string", "minLength": 1},
|
|
23
|
-
"event_type": {"enum": ["exception"]},
|
|
23
|
+
"event_type": {"enum": ["exception", "request", "query", "job", "cache"]},
|
|
24
24
|
"occurred_at": {"type": "string"},
|
|
25
25
|
"sent_at": {"type": "string"},
|
|
26
26
|
"project_key": {"type": "string", "minLength": 1},
|
|
@@ -36,16 +36,30 @@
|
|
|
36
36
|
},
|
|
37
37
|
"runtime": {"type": "object"},
|
|
38
38
|
"context": {"type": "object"},
|
|
39
|
-
"payload": {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
"payload": {"type": "object"}
|
|
40
|
+
},
|
|
41
|
+
"allOf": [
|
|
42
|
+
{
|
|
43
|
+
"if": {
|
|
44
|
+
"required": ["event_type"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"event_type": {"const": "exception"}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"then": {
|
|
50
|
+
"properties": {
|
|
51
|
+
"payload": {
|
|
52
|
+
"required": ["exception", "severity"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"exception": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"required": ["class", "message", "backtrace", "causes"]
|
|
57
|
+
},
|
|
58
|
+
"severity": {"type": "string"}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
}
|
|
50
|
-
|
|
64
|
+
]
|
|
51
65
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://chronos.example/schemas/rack-context-v1.schema.json",
|
|
4
|
+
"title": "Chronos Rack exception context v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["request", "trace_id", "breadcrumbs"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"trace_id": {"type": "string", "minLength": 1},
|
|
10
|
+
"request": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"required": ["method", "route", "status", "duration_ms", "request_id", "host", "path"],
|
|
14
|
+
"properties": {
|
|
15
|
+
"method": {"type": "string"},
|
|
16
|
+
"route": {"type": "string"},
|
|
17
|
+
"status": {"type": "integer"},
|
|
18
|
+
"duration_ms": {"type": "number", "minimum": 0},
|
|
19
|
+
"request_id": {"type": ["string", "null"]},
|
|
20
|
+
"user_agent": {"type": ["string", "null"]},
|
|
21
|
+
"host": {"type": "string"},
|
|
22
|
+
"path": {"type": "string"},
|
|
23
|
+
"controller": {"type": ["string", "null"]},
|
|
24
|
+
"action": {"type": ["string", "null"]},
|
|
25
|
+
"response_size": {"type": ["integer", "null"], "minimum": 0}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"breadcrumbs": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"required": ["category", "message", "metadata", "timestamp"],
|
|
34
|
+
"properties": {
|
|
35
|
+
"category": {"enum": ["custom", "log", "request", "query", "external_http", "cache", "job"]},
|
|
36
|
+
"message": {"type": "string"},
|
|
37
|
+
"metadata": {"type": "object"},
|
|
38
|
+
"timestamp": {"type": "string"}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ADR-012 — Rack capture with selectable execution context
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted for version 0.4.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Automatic Rack capture needs request-scoped state, but the legacy line cannot rely on modern fiber-local APIs. Loading the plain Ruby gem must not force Rack into applications that do not use it. Request and response bodies also carry disproportionate privacy and performance risk.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Implement a Rack-protocol middleware without a runtime Rack dependency. Catch failures from the downstream application call, notify asynchronously, and re-raise the same exception. Never read `rack.input`, raw query strings, cookies, authorization headers, or response body enumeration. Use already-parsed parameter hashes only.
|
|
14
|
+
|
|
15
|
+
Define a `ContextStore` port and use a selectable thread-local adapter by default. Restore nested scopes in `ensure`. Keep breadcrumbs in a fixed circular per-execution buffer and sanitize them with the rest of the event before queueing.
|
|
16
|
+
|
|
17
|
+
## Alternatives
|
|
18
|
+
|
|
19
|
+
Requiring Rack from the core was rejected because it would add dependency and resolution risk to plain Ruby legacy applications. Global or process-wide context was rejected because concurrent requests would leak data. Automatic body parsing and body proxies were deferred because they change I/O behavior and complicate streaming lifecycle ownership.
|
|
20
|
+
|
|
21
|
+
## Positive consequences
|
|
22
|
+
|
|
23
|
+
Plain Ruby loading remains dependency-free, concurrent request context is isolated, middleware failure semantics are preserved, and diagnostic memory is bounded.
|
|
24
|
+
|
|
25
|
+
## Negative consequences
|
|
26
|
+
|
|
27
|
+
Thread-local context does not propagate to application-created threads or fibers. Generic route normalization is less precise than router-specific integration. Exceptions raised only during deferred response-body enumeration are outside version 0.4 capture.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ADR-013 — Legacy Rails integration through public notifications
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted for version 0.5.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Rails 4.2 and 5.2 predate Zeitwerk as a universal loader and expose different optional components. Automatic capture must not couple the Ruby core to Rails, duplicate middleware or subscribers during reload, or collect high-risk framework payloads.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Load Rails integration explicitly through `chronos/rails`. Use a small Railtie initializer after application configuration, an idempotent installer, and public `ActiveSupport::Notifications.subscribe` callbacks. Detect constants and methods before using optional components. Emit request, query, job, and cache envelopes through the same sanitization and delivery pipeline as exceptions.
|
|
14
|
+
|
|
15
|
+
Allowlist metadata per notification. Never copy SQL text, binds, cache keys, mail bodies, job arguments, request bodies, response bodies, cookies, or authorization headers. Deduplicate controller and Rack exception hooks within request context.
|
|
16
|
+
|
|
17
|
+
## Alternatives
|
|
18
|
+
|
|
19
|
+
ActiveSupport method patching and controller monkey patches were rejected because public notifications cover the required lifecycle with less version risk. Modeling timings as exception breadcrumbs was rejected because successful requests and SQL need independently deliverable metrics. Requiring Rails from the core was rejected because plain Ruby applications must remain framework-independent.
|
|
20
|
+
|
|
21
|
+
## Positive consequences
|
|
22
|
+
|
|
23
|
+
The core remains independent, installation is reload-safe, optional components degrade cleanly, and Rails telemetry retains existing privacy and outage boundaries.
|
|
24
|
+
|
|
25
|
+
## Negative consequences
|
|
26
|
+
|
|
27
|
+
Legacy notification payloads are less uniform than modern tracing APIs. Individual events add delivery volume until aggregation arrives in version 0.7. Synthetic Rails 4.2 controller exceptions may not retain the original exception class when only the public tuple is available.
|
data/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Chronos Ruby 0.
|
|
3
|
+
Chronos Ruby 0.5 uses hexagonal boundaries so the legacy core remains independent of frameworks and delivery infrastructure.
|
|
4
4
|
|
|
5
5
|
```mermaid
|
|
6
6
|
flowchart TB
|
|
@@ -11,22 +11,36 @@ flowchart TB
|
|
|
11
11
|
Application --> Delivery[Application / DeliveryPipeline]
|
|
12
12
|
Delivery --> Internal[Internal / BoundedQueue, WorkerPool, and MemoryBacklog]
|
|
13
13
|
Internal --> Ports
|
|
14
|
+
Rack[Integrations / Rack middleware] --> Facade
|
|
15
|
+
Facade --> Context[Ports / ContextStore]
|
|
16
|
+
Context --> ThreadLocal[Adapters / ThreadLocalContextStore]
|
|
17
|
+
Rails[Chronos Rails / Railtie and subscribers] --> Facade
|
|
18
|
+
Rails --> Notifications[ActiveSupport Notifications]
|
|
19
|
+
Application --> Telemetry[Core / TelemetryEvent]
|
|
14
20
|
```
|
|
15
21
|
|
|
16
22
|
## Boundaries
|
|
17
23
|
|
|
18
24
|
- Domain/Core owns immutable event values and Ruby normalization.
|
|
19
25
|
- Application owns use-case ordering and failure containment.
|
|
20
|
-
- Ports define behavior expected from infrastructure.
|
|
21
|
-
- Adapters contain Net::HTTP and
|
|
26
|
+
- Ports define behavior expected from transport and execution-context infrastructure.
|
|
27
|
+
- Adapters contain Net::HTTP, TLS, and thread-local context behavior.
|
|
22
28
|
- Internal contains private concurrency and diagnostic mechanisms.
|
|
29
|
+
- Integrations contain optional framework entry points and never enter the domain boundary.
|
|
30
|
+
- `Chronos::Rails` contains the optional Railtie, installer, generator, and public-notification adapters.
|
|
23
31
|
|
|
24
32
|
The `Chronos` module is a thin facade. Rails, Rack, ActiveSupport, Sidekiq, and job libraries must not be required by the core.
|
|
25
33
|
|
|
34
|
+
## Rack capture flow
|
|
35
|
+
|
|
36
|
+
The Rack middleware establishes a context-store scope, adds a bounded request breadcrumb, and calls the downstream application. An unhandled exception is enriched with request duration and status, captured through the normal notice pipeline, then re-raised unchanged. The context-store adapter restores or clears its value in `ensure`. The middleware implements the Rack protocol without requiring the Rack library.
|
|
37
|
+
|
|
26
38
|
## Capture flow
|
|
27
39
|
|
|
28
40
|
An exception becomes an immutable notice. `Sanitizer` removes sensitive values before `SafeSerializer` creates a bounded JSON envelope. Asynchronous capture inserts only that sanitized serialized event into the queue. A fixed worker sends it through `DeliveryPipeline`, which applies finite retry, a circuit breaker, and a fixed memory backlog. Synchronous capture bypasses the queue but uses the same privacy and resilience boundaries.
|
|
29
41
|
|
|
42
|
+
Rails timings become immutable `TelemetryEvent` values. `CaptureTelemetry` applies the remote/local event policy, `TelemetrySerializer` sanitizes the allowlisted payload, and the resulting `SerializedEvent` enters the same delivery pipeline. Rails classes are loaded only through `chronos/rails`; the core never requires Rails or ActiveSupport.
|
|
43
|
+
|
|
30
44
|
## Failure policy
|
|
31
45
|
|
|
32
46
|
Explicit invalid configuration raises `Chronos::ConfigurationError`. Capture, serialization, logger, worker, retry, circuit, TLS, network, HTTP, and remote-policy failures do not escape into the host application. They produce `false`, a classified transport result, a bounded state transition, or a bounded logger diagnostic.
|
data/docs/compatibility.md
CHANGED
|
@@ -4,14 +4,14 @@ Chronos Ruby 0.x is the legacy line. Technical compatibility does not make an en
|
|
|
4
4
|
|
|
5
5
|
| Ruby | Rails integration | Status | Evidence |
|
|
6
6
|
|---|---|---|---|
|
|
7
|
-
| 2.2.10 |
|
|
8
|
-
| 2.3.8 |
|
|
9
|
-
| 2.4.10 |
|
|
10
|
-
| 2.5.9 |
|
|
11
|
-
| 2.6.10 |
|
|
7
|
+
| 2.2.10 | Rails 4.2 | Best effort | Core CI, Rails 4.2 example, dedicated smoke gate |
|
|
8
|
+
| 2.3.8 | Rails 4.2 / 5.0 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
|
|
9
|
+
| 2.4.10 | Rails 4.2 / 5.0 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
|
|
10
|
+
| 2.5.9 | Rails 5.2 | Best effort | Core CI, Rails 5.2 example, dedicated smoke gate |
|
|
11
|
+
| 2.6.10 | Rails 5.2 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
|
|
12
12
|
| 2.7 and newer | None in 0.x | Unsupported | Belongs to transitional or modern lines |
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Version 0.5 includes Rails 4.2 and 5.2 applications plus a dedicated matrix, but this document conservatively keeps the combinations at `Best effort` until all release-gate evidence, including fake-server payload validation, is green. Rails 5.0 uses the same feature-detected public APIs but does not yet have its own example application.
|
|
15
15
|
|
|
16
16
|
Status meanings:
|
|
17
17
|
|
data/docs/configuration.md
CHANGED
|
@@ -38,9 +38,16 @@
|
|
|
38
38
|
| `circuit_reset_timeout` | Optional | `30.0` | Seconds before one half-open delivery probe |
|
|
39
39
|
| `remote_configuration` | Optional | `true` | Accepts only the documented bounded remote policy fields |
|
|
40
40
|
| `remote_config_max_bytes` | Optional | `4096` | Maximum remote policy response-header bytes |
|
|
41
|
-
| `sampling_rate` | Optional | `1.0` | Local upper bound for
|
|
42
|
-
| `enabled_event_types` | Optional |
|
|
41
|
+
| `sampling_rate` | Optional | `1.0` | Local upper bound for event sampling |
|
|
42
|
+
| `enabled_event_types` | Optional | exception, request, query, job, cache | Local allowlist for supported event envelopes |
|
|
43
43
|
| `max_remote_send_interval` | Optional | `60.0` | Local upper bound for remotely requested send spacing |
|
|
44
|
+
| `context_store` | Optional | `:thread_local` | `:thread_local` or an object implementing `get`, `set`, `clear`, and `with_context` |
|
|
45
|
+
| `breadcrumb_capacity` | Optional | `20` | Positive count of newest breadcrumbs retained per execution |
|
|
46
|
+
| `breadcrumb_max_bytes` | Optional | `2048` | Maximum bytes per normalized breadcrumb; minimum `128` |
|
|
47
|
+
| `rails_enabled` | Optional | `true` | Enables automatic Rails middleware and subscribers |
|
|
48
|
+
| `rails_capture_in_console` | Optional | `false` | Enables automatic integration while Rails console is loaded |
|
|
49
|
+
| `rails_capture_in_test` | Optional | `false` | Enables automatic integration in the Rails test environment |
|
|
50
|
+
| `rails_capture_user_agent` | Optional | `false` | Adds the Rack user agent to request context |
|
|
44
51
|
|
|
45
52
|
```ruby
|
|
46
53
|
Chronos.configure do |config|
|
|
@@ -56,6 +63,10 @@ Chronos.configure do |config|
|
|
|
56
63
|
config.max_retries = 3
|
|
57
64
|
config.backlog_size = 100
|
|
58
65
|
config.circuit_failure_threshold = 5
|
|
66
|
+
config.context_store = :thread_local
|
|
67
|
+
config.breadcrumb_capacity = 20
|
|
68
|
+
config.rails_capture_in_test = false
|
|
69
|
+
config.rails_capture_in_console = false
|
|
59
70
|
end
|
|
60
71
|
```
|
|
61
72
|
|
data/docs/data-collected.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Data collected
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.5 emits exception, request, query, job, and cache events. All fields pass through the privacy sanitizer and bounded safe serializer before queueing, retry storage, or delivery.
|
|
4
4
|
|
|
5
5
|
| Data | Default | Source |
|
|
6
6
|
|---|---|---|
|
|
@@ -15,8 +15,21 @@ Version 0.3 emits only manually submitted exception events. All fields pass thro
|
|
|
15
15
|
| Context, parameters, session, user | Optional and sanitized | Explicit capture arguments |
|
|
16
16
|
| Tags and fingerprint | Optional and sanitized | Explicit capture arguments |
|
|
17
17
|
| IPv4 address in supplied text | Anonymized | Explicit capture arguments |
|
|
18
|
+
| Rack method, normalized route, status, duration, request ID, host, query-free path, trace ID | Collected with Rack middleware | Rack environment and local clock |
|
|
19
|
+
| Rack user agent | Disabled by default | `HTTP_USER_AGENT` when middleware option is enabled |
|
|
20
|
+
| Rack controller/action | Collected when already supplied | Explicit Chronos or Action Dispatch environment values |
|
|
21
|
+
| Rack parameters | Collected only when already parsed | Explicit, Rack, or Action Dispatch parameter hashes |
|
|
22
|
+
| Response size | `Content-Length` when present | Rack response headers |
|
|
23
|
+
| Breadcrumbs | Explicit and bounded | Application and Chronos integration |
|
|
24
|
+
| Rails controller/action, status, method, query-free path, normalized route, duration | Collected with Rails integration | `process_action.action_controller` |
|
|
25
|
+
| Sanitized Rails parameters | Collected with Rails integration | Public controller notification payload |
|
|
26
|
+
| View template basename and duration | Collected with Rails integration | `render_template.action_view` |
|
|
27
|
+
| SQL operation name, cached flag, duration | Collected; SQL text omitted | `sql.active_record` |
|
|
28
|
+
| Mailer/action and duration | Collected; message content omitted | `deliver.action_mailer` |
|
|
29
|
+
| Active Job class, queue, duration | Collected when available; arguments omitted | `perform.active_job` |
|
|
30
|
+
| Cache operation, store, hit flag, duration | Collected; key/value omitted | ActiveSupport cache notifications |
|
|
18
31
|
|
|
19
|
-
The gem never collects request bodies, response bodies, cookies, HTTP authorization headers, environment variables in bulk, source code, SQL bind values, database rows, or installed gems in version 0.
|
|
32
|
+
The gem never collects request bodies, response bodies, raw query strings, cookies, HTTP authorization headers, environment variables in bulk, source code, raw SQL, SQL bind values, database rows, cache keys/values, mail bodies/recipients, job IDs/arguments, or installed gems in version 0.5.
|
|
20
33
|
|
|
21
34
|
The secret `project_key` is an authentication header and is excluded from the JSON payload and logger diagnostics. The envelope field named `project_key` contains the public `project_id` required by the current v1 server contract.
|
|
22
35
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Rack integration, execution context, and breadcrumbs
|
|
2
|
+
|
|
3
|
+
Version 0.4 adds automatic capture through `Chronos::Integrations::Rack::Middleware`. The integration implements the Rack protocol directly and does not require Rack when `chronos-ruby` is loaded. Add the middleware after configuring Chronos:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
use Chronos::Integrations::Rack::Middleware,
|
|
7
|
+
:include_user_agent => false
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Capture behavior
|
|
11
|
+
|
|
12
|
+
The middleware catches an exception raised by the downstream application, submits it asynchronously, and re-raises the same exception object. A notification or logger failure cannot replace the application failure. An `ensure` boundary clears request context in every outcome.
|
|
13
|
+
|
|
14
|
+
Captured request fields are method, normalized route, status, duration in milliseconds, request ID, optional user agent, host, query-free path, controller/action when already supplied, approximate response size from `Content-Length`, a generated or supplied trace ID, already-parsed parameters, and application-supplied user context. The final event serializer sanitizes every field before queueing.
|
|
15
|
+
|
|
16
|
+
The middleware never reads `rack.input`, copies `QUERY_STRING`, parses a body, enumerates a response body, or reads cookies and authorization headers. Parameters are collected only from already-materialized hashes at `rack.request.query_hash`, `action_dispatch.request.query_parameters`, `action_dispatch.request.path_parameters`, or the explicit `chronos.parameters` key. User context is opt-in through `chronos.user`.
|
|
17
|
+
|
|
18
|
+
Without an explicit `chronos.route` or `action_dispatch.route_uri_pattern`, numeric and UUID path segments are replaced with `:id`. This is a conservative cardinality guard, not a complete router-aware normalizer.
|
|
19
|
+
|
|
20
|
+
## Context store
|
|
21
|
+
|
|
22
|
+
`Chronos::Ports::ContextStore` defines `get`, `set`, `clear`, and `with_context`. The legacy default, `Chronos::Adapters::ThreadLocalContextStore`, isolates each thread and restores nested scopes in `ensure`. It deliberately does not propagate state into newly created threads or fibers.
|
|
23
|
+
|
|
24
|
+
A custom strategy can be selected at configuration time if it implements the complete port:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
Chronos.configure do |config|
|
|
28
|
+
# credentials omitted
|
|
29
|
+
config.context_store = MyContextStore.new
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Application code can add scoped values. Manual notifications issued inside the block inherit them:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
Chronos.with_context(:user => {"id" => "customer-42"}) do
|
|
37
|
+
Chronos.notify(error)
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Breadcrumbs
|
|
42
|
+
|
|
43
|
+
`Chronos.add_breadcrumb` records an explicit marker in the current execution buffer:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
Chronos.add_breadcrumb(
|
|
47
|
+
:category => "custom",
|
|
48
|
+
:message => "payment authorization started",
|
|
49
|
+
:metadata => {"provider" => "example"}
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Allowed categories are `custom`, `log`, `request`, `query`, `external_http`, `cache`, and `job`; an unknown value becomes `custom`. No log, SQL, body, cache, job, or external HTTP payload is captured automatically in 0.4. Metadata exists only when the application or a later integration explicitly supplies it.
|
|
54
|
+
|
|
55
|
+
The default circular buffer keeps the newest 20 entries. Each breadcrumb is normalized to JSON primitives with bounded depth, collections, strings, nodes, and serialized bytes. The entire exception payload still passes through the privacy sanitizer before it can enter the queue or retry backlog.
|
|
56
|
+
|
|
57
|
+
## Concurrency and limitations
|
|
58
|
+
|
|
59
|
+
Concurrent Rack threads receive distinct user, parameter, breadcrumb, and trace values. Context does not cross threads automatically, so applications that start their own thread must establish a new scope explicitly. Version 0.4 captures exceptions raised during the initial Rack application call; deferred failures raised only while a server enumerates a streaming response body are not intercepted.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Rails 4.2 and 5.2 legacy integration
|
|
2
|
+
|
|
3
|
+
Version 0.5 adds an explicit Rails entry point:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
require "chronos/rails"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`Chronos::Rails::Railtie` registers one initializer after `load_config_initializers`. It requires no Zeitwerk and delegates to an idempotent installer. The installer uses feature detection, installs `Chronos::Integrations::Rack::Middleware` once per application, and registers notification subscribers once per `ActiveSupport::Notifications` bus.
|
|
10
|
+
|
|
11
|
+
## Installation generator
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
rails generate chronos:install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The generator creates `config/initializers/chronos.rb`. The template reads only explicitly named `CHRONOS_*` variables, uses `Rails.env`, adopts `Rails.logger` when available, and disables automatic integration in test and console by default. It invokes the idempotent installer as a fallback for applications that disabled Bundler auto-require, so the Railtie and initializer paths cannot create duplicate hooks. It never scans all environment variables or modifies routes and application classes.
|
|
20
|
+
|
|
21
|
+
## Captured integrations
|
|
22
|
+
|
|
23
|
+
The public `ActiveSupport::Notifications.subscribe` API is used for:
|
|
24
|
+
|
|
25
|
+
| Notification | Chronos event | Allowlisted data |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `process_action.action_controller` | `request` | controller, action, status, method, query-free path, normalized route, duration, sanitized parameters |
|
|
28
|
+
| `render_template.action_view` | `request` with `kind=view` | template basename and duration |
|
|
29
|
+
| `sql.active_record` | `query` | operation name, cached flag, and duration |
|
|
30
|
+
| `deliver.action_mailer` | `job` with `kind=mailer` | mailer, action, and duration |
|
|
31
|
+
| `perform.active_job` | `job` | job class, queue, and duration, when Active Job is available |
|
|
32
|
+
| cache read/write/hit notifications | `cache` | operation, store, hit flag, and duration |
|
|
33
|
+
|
|
34
|
+
Raw SQL, binds, cache keys, mail recipients and bodies, job IDs and arguments, request/response bodies, cookies, and authorization headers are not copied. Template identifiers are reduced to their basename. Every event passes through `Sanitizer`, `SafeSerializer`, the bounded queue, retry policy, circuit breaker, and memory backlog.
|
|
35
|
+
|
|
36
|
+
## Controller exceptions and deduplication
|
|
37
|
+
|
|
38
|
+
When `process_action.action_controller` exposes `exception_object`, the original exception is captured. Rails versions that expose only the public exception tuple produce a bounded synthetic `RuntimeError` containing the supplied message. A request-scoped deduplicator prevents the same error from being reported again by the Rack middleware. The host application continues to receive its original exception semantics.
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
Chronos.configure do |config|
|
|
44
|
+
# connection settings omitted
|
|
45
|
+
config.rails_enabled = true
|
|
46
|
+
config.rails_capture_in_test = false
|
|
47
|
+
config.rails_capture_in_console = false
|
|
48
|
+
config.rails_capture_user_agent = false
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Setting `rails_enabled = false` prevents middleware and subscribers from being installed. Test and console switches affect only automatic Rails integration; explicit manual Chronos calls retain their normal configuration behavior.
|
|
53
|
+
|
|
54
|
+
## Compatibility evidence
|
|
55
|
+
|
|
56
|
+
The repository contains independent applications under `examples/rails-4.2` and `examples/rails-5.2`. Their smoke scripts execute a successful request, controller exception, SQL query, view, cache access, inline Active Job, Action Mailer, flush, and shutdown. The dedicated legacy Rails workflow is the release gate; a framework/runtime pair must not be labeled `Supported` until that job and fake-server payload validation pass.
|
|
57
|
+
|
|
58
|
+
## Limits
|
|
59
|
+
|
|
60
|
+
Version 0.5 emits individual bounded timings. It does not aggregate APM metrics, capture SQL text, calculate query fingerprints, or provide Sidekiq integration. Those belong to later versions in the development order.
|