chronos-ruby 0.4.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/README.md +23 -9
- data/contracts/event-v1.schema.json +25 -11
- data/docs/adr/ADR-013-legacy-rails-notifications.md +27 -0
- data/docs/architecture.md +7 -1
- data/docs/compatibility.md +6 -6
- data/docs/configuration.md +8 -2
- data/docs/data-collected.md +9 -2
- data/docs/modules/rails-legacy.md +60 -0
- data/docs/modules/telemetry-events.md +9 -0
- data/docs/performance.md +16 -3
- data/docs/privacy-lgpd.md +7 -3
- data/docs/troubleshooting.md +9 -1
- data/lib/chronos/agent.rb +43 -1
- 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 +15 -153
- data/lib/chronos/core/telemetry_event.rb +106 -0
- data/lib/chronos/integrations/rack/middleware.rb +5 -1
- data/lib/chronos/integrations.rb +1 -1
- 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 +26 -1
- data/lib/generators/chronos/install/install_generator.rb +25 -0
- data/lib/generators/chronos/install/templates/chronos.rb +20 -0
- metadata +14 -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/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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;
|
|
@@ -22,7 +22,7 @@ Chronos Ruby does not inspect environment variables, request bodies, cookies, HT
|
|
|
22
22
|
|
|
23
23
|
## Supported Ruby and Rails versions
|
|
24
24
|
|
|
25
|
-
Version 0.x targets Ruby 2.2.10 through Ruby 2.6. Version 0.4
|
|
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.
|
|
26
26
|
|
|
27
27
|
See [Compatibility](docs/compatibility.md).
|
|
28
28
|
|
|
@@ -31,7 +31,7 @@ See [Compatibility](docs/compatibility.md).
|
|
|
31
31
|
The current public build is a pre-release. Add its exact version to the application's `Gemfile`:
|
|
32
32
|
|
|
33
33
|
```ruby
|
|
34
|
-
gem "chronos-ruby", "0.
|
|
34
|
+
gem "chronos-ruby", "0.5.0.pre.1"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Install with a Bundler version compatible with the application. For the oldest supported runtime:
|
|
@@ -49,7 +49,19 @@ gem install chronos-ruby --pre
|
|
|
49
49
|
|
|
50
50
|
## Rails installation
|
|
51
51
|
|
|
52
|
-
|
|
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).
|
|
53
65
|
|
|
54
66
|
## Minimum configuration
|
|
55
67
|
|
|
@@ -156,11 +168,11 @@ Local exception-specific ignore callbacks are not available in version 0.4. The
|
|
|
156
168
|
|
|
157
169
|
## Performance monitoring
|
|
158
170
|
|
|
159
|
-
|
|
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.
|
|
160
172
|
|
|
161
173
|
## Sidekiq and Active Job
|
|
162
174
|
|
|
163
|
-
|
|
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.
|
|
164
176
|
|
|
165
177
|
## Deploy tracking
|
|
166
178
|
|
|
@@ -203,6 +215,7 @@ The code follows hexagonal boundaries:
|
|
|
203
215
|
- `Chronos::Ports` defines delivery behavior;
|
|
204
216
|
- `Chronos::Adapters` implements Net::HTTP delivery and thread-local context;
|
|
205
217
|
- `Chronos::Integrations::Rack` implements optional automatic Rack capture;
|
|
218
|
+
- `Chronos::Rails` implements the optional Railtie, installer, generator, and public-notification adapters;
|
|
206
219
|
- `Chronos::Internal` owns bounded queueing, workers, and defensive logging.
|
|
207
220
|
|
|
208
221
|
The core has no dependency on Rails, Rack, Sidekiq, or ActiveSupport. See [Architecture](docs/architecture.md).
|
|
@@ -244,7 +257,7 @@ Configuration errors are raised during `Chronos.configure`. Capture and delivery
|
|
|
244
257
|
|
|
245
258
|
## Benchmark
|
|
246
259
|
|
|
247
|
-
Run the version 0.
|
|
260
|
+
Run the version 0.5 benchmarks with:
|
|
248
261
|
|
|
249
262
|
```bash
|
|
250
263
|
bundle _1.17.3_ exec ruby benchmarks/capture_exception.rb
|
|
@@ -253,13 +266,14 @@ bundle _1.17.3_ exec ruby benchmarks/filtering.rb
|
|
|
253
266
|
bundle _1.17.3_ exec ruby benchmarks/queue.rb
|
|
254
267
|
bundle _1.17.3_ exec ruby benchmarks/retry_backlog.rb
|
|
255
268
|
bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
269
|
+
bundle _1.17.3_ exec ruby benchmarks/rails_notifications.rb
|
|
256
270
|
```
|
|
257
271
|
|
|
258
272
|
Results depend on runtime, hardware, and payload. No performance comparison is claimed until repeatable measurements are published.
|
|
259
273
|
|
|
260
274
|
## Migration from Airbrake
|
|
261
275
|
|
|
262
|
-
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.
|
|
263
277
|
|
|
264
278
|
## Local development
|
|
265
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,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
|
|
@@ -14,6 +14,9 @@ flowchart TB
|
|
|
14
14
|
Rack[Integrations / Rack middleware] --> Facade
|
|
15
15
|
Facade --> Context[Ports / ContextStore]
|
|
16
16
|
Context --> ThreadLocal[Adapters / ThreadLocalContextStore]
|
|
17
|
+
Rails[Chronos Rails / Railtie and subscribers] --> Facade
|
|
18
|
+
Rails --> Notifications[ActiveSupport Notifications]
|
|
19
|
+
Application --> Telemetry[Core / TelemetryEvent]
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
## Boundaries
|
|
@@ -24,6 +27,7 @@ flowchart TB
|
|
|
24
27
|
- Adapters contain Net::HTTP, TLS, and thread-local context behavior.
|
|
25
28
|
- Internal contains private concurrency and diagnostic mechanisms.
|
|
26
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.
|
|
27
31
|
|
|
28
32
|
The `Chronos` module is a thin facade. Rails, Rack, ActiveSupport, Sidekiq, and job libraries must not be required by the core.
|
|
29
33
|
|
|
@@ -35,6 +39,8 @@ The Rack middleware establishes a context-store scope, adds a bounded request br
|
|
|
35
39
|
|
|
36
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.
|
|
37
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
|
+
|
|
38
44
|
## Failure policy
|
|
39
45
|
|
|
40
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,12 +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
44
|
| `context_store` | Optional | `:thread_local` | `:thread_local` or an object implementing `get`, `set`, `clear`, and `with_context` |
|
|
45
45
|
| `breadcrumb_capacity` | Optional | `20` | Positive count of newest breadcrumbs retained per execution |
|
|
46
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 |
|
|
47
51
|
|
|
48
52
|
```ruby
|
|
49
53
|
Chronos.configure do |config|
|
|
@@ -61,6 +65,8 @@ Chronos.configure do |config|
|
|
|
61
65
|
config.circuit_failure_threshold = 5
|
|
62
66
|
config.context_store = :thread_local
|
|
63
67
|
config.breadcrumb_capacity = 20
|
|
68
|
+
config.rails_capture_in_test = false
|
|
69
|
+
config.rails_capture_in_console = false
|
|
64
70
|
end
|
|
65
71
|
```
|
|
66
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
|
|---|---|---|
|
|
@@ -21,8 +21,15 @@ Version 0.4 emits exception events submitted manually or captured by the optiona
|
|
|
21
21
|
| Rack parameters | Collected only when already parsed | Explicit, Rack, or Action Dispatch parameter hashes |
|
|
22
22
|
| Response size | `Content-Length` when present | Rack response headers |
|
|
23
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 |
|
|
24
31
|
|
|
25
|
-
The gem never collects request bodies, response bodies, raw query strings, 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.
|
|
26
33
|
|
|
27
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.
|
|
28
35
|
|
|
@@ -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.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Framework telemetry events
|
|
2
|
+
|
|
3
|
+
`Chronos::Core::TelemetryEvent` and `Chronos::Core::TelemetrySerializer` extend the v1 envelope to `request`, `query`, `job`, and `cache` events. These values exist so framework adapters do not model operational timings as fake exceptions.
|
|
4
|
+
|
|
5
|
+
Telemetry has the same `schema_version`, event ID, timestamps, project, environment, service, runtime, context, sanitization, payload-size limit, idempotency header, asynchronous queue, retry, circuit breaker, and memory backlog used by exception events. Unsupported types are rejected locally.
|
|
6
|
+
|
|
7
|
+
`Chronos.record_event` is the narrow integration entry point. Application code should prefer documented higher-level APIs; its payload is allowlisted by each bundled integration and then sanitized. Remote configuration can reduce or disable any locally enabled telemetry type but cannot enable a type excluded by local configuration.
|
|
8
|
+
|
|
9
|
+
Version 0.5 intentionally sends individual framework events. Aggregation, percentiles, slow-query analysis, and transaction breakdowns remain part of version 0.7.
|
data/docs/performance.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Performance
|
|
2
2
|
|
|
3
|
-
Performance is a functional requirement, but version 0.
|
|
3
|
+
Performance is a functional requirement, but version 0.5 makes no unverified speed claim.
|
|
4
4
|
|
|
5
5
|
Current controls:
|
|
6
6
|
|
|
@@ -15,11 +15,24 @@ Current controls:
|
|
|
15
15
|
- retry count, delay, jitter, and `Retry-After` are capped;
|
|
16
16
|
- the circuit breaker suppresses requests during sustained failure;
|
|
17
17
|
- retry backlog capacity is fixed and may be disabled;
|
|
18
|
-
- shutdown and flush have caller-controlled timeouts
|
|
18
|
+
- shutdown and flush have caller-controlled timeouts;
|
|
19
19
|
- request context and breadcrumbs have fixed structural and byte limits;
|
|
20
20
|
- Rack middleware never consumes request or response bodies.
|
|
21
|
+
- Rails subscribers copy only small allowlisted field sets and never copy raw SQL or job arguments.
|
|
21
22
|
|
|
22
|
-
Run the scripts under `benchmarks/` and record Ruby version, operating system, CPU, warmup, iteration count, median, and dispersion before publishing results. `benchmarks/filtering.rb` measures privacy filtering, `benchmarks/retry_backlog.rb` measures fixed-memory outage behavior,
|
|
23
|
+
Run the scripts under `benchmarks/` and record Ruby version, operating system, CPU, warmup, iteration count, median, and dispersion before publishing results. `benchmarks/filtering.rb` measures privacy filtering, `benchmarks/retry_backlog.rb` measures fixed-memory outage behavior, `benchmarks/request_overhead.rb` compares Rack-protocol calls, and `benchmarks/rails_notifications.rb` isolates subscriber normalization overhead.
|
|
24
|
+
|
|
25
|
+
## Version 0.5 Rails subscriber benchmark
|
|
26
|
+
|
|
27
|
+
Run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ITERATIONS=100000 bundle _1.17.3_ exec ruby benchmarks/rails_notifications.rb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This synthetic benchmark invokes the SQL notification normalizer with a no-op notifier. It verifies that raw SQL and binds are not copied and measures local allowlist normalization only; it does not include Rails dispatch, serialization, queueing, or network delivery.
|
|
34
|
+
|
|
35
|
+
A local diagnostic run on 2026-07-20 used Ruby 2.2.10 on macOS arm64 and 10,000 iterations. It completed in 0.041232 seconds, or approximately 4.123 microseconds per normalization. This single run has no warmup, median, or dispersion and is not a production performance claim.
|
|
23
36
|
|
|
24
37
|
## Version 0.2 development measurement
|
|
25
38
|
|
data/docs/privacy-lgpd.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Privacy and LGPD
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.5 sanitizes exception and framework telemetry before JSON serialization, queueing, retry backlog, or transport. This reduces accidental exposure, but the host application remains responsible for lawful purpose, minimization, access control, retention, and responses to data-subject requests.
|
|
4
4
|
|
|
5
5
|
## Default policy
|
|
6
6
|
|
|
@@ -13,9 +13,13 @@ Version 0.4 sanitizes every exception event before JSON serialization, queueing,
|
|
|
13
13
|
| Payment-card candidates that pass the Luhn check | Replaced with `[FILTERED_CARD]` |
|
|
14
14
|
| IPv4 addresses | Last octet replaced with `0` |
|
|
15
15
|
| Unknown Ruby objects | Represented by class name without calling application serialization |
|
|
16
|
-
| Request/response bodies, raw query strings, cookies, authorization headers, SQL
|
|
16
|
+
| Request/response bodies, raw query strings, cookies, authorization headers, raw SQL/binds, cache values, mail bodies, job arguments, environment variables | Never collected automatically in version 0.5 |
|
|
17
17
|
|
|
18
|
-
The retry backlog exists only in process memory, accepts only `SerializedEvent`, has a fixed capacity, and disappears on process exit. Version 0.
|
|
18
|
+
The retry backlog exists only in process memory, accepts only `SerializedEvent`, has a fixed capacity, and disappears on process exit. Version 0.5 does not persist telemetry to disk.
|
|
19
|
+
|
|
20
|
+
## Rails telemetry
|
|
21
|
+
|
|
22
|
+
Rails subscribers use per-notification allowlists. SQL events retain only the operation name, cached flag, and duration; cache events omit key and value; mailer events omit addresses and content; Active Job events omit job ID and arguments; view identifiers are reduced to basenames. Controller parameters are sanitized by the normal payload pipeline before queueing.
|
|
19
23
|
|
|
20
24
|
## Rack context and breadcrumbs
|
|
21
25
|
|
data/docs/troubleshooting.md
CHANGED
|
@@ -14,7 +14,15 @@ Check DNS, TLS certificates, credentials, HTTP status, proxy configuration, and
|
|
|
14
14
|
|
|
15
15
|
## Rack exception is not captured
|
|
16
16
|
|
|
17
|
-
Confirm that `Chronos.configure` runs before the middleware handles requests and that the middleware wraps the application component that raises. Version 0.
|
|
17
|
+
Confirm that `Chronos.configure` runs before the middleware handles requests and that the middleware wraps the application component that raises. Version 0.5 captures exceptions raised by the initial downstream Rack call; an exception raised later while a server enumerates a streaming response body is outside this release. The original exception is always re-raised, so the server log should still show it.
|
|
18
|
+
|
|
19
|
+
## Rails middleware or telemetry is missing
|
|
20
|
+
|
|
21
|
+
Require `chronos/rails` from the generated initializer and confirm it runs before requests. Automatic integration is disabled by default in Rails test and console; set `rails_capture_in_test` or `rails_capture_in_console` explicitly when validating those environments. `rails_enabled = false` disables all automatic Rails hooks. Active Job is subscribed only when its constant is available during installation.
|
|
22
|
+
|
|
23
|
+
## Rails telemetry contains no SQL or cache key
|
|
24
|
+
|
|
25
|
+
This is intentional. Version 0.5 records SQL operation name/duration and cache operation/store/hit status without raw statements, binds, keys, or values. These omissions are privacy and cardinality boundaries, not capture failures.
|
|
18
26
|
|
|
19
27
|
## Context appears missing
|
|
20
28
|
|
data/lib/chronos/agent.rb
CHANGED
|
@@ -36,7 +36,7 @@ module Chronos
|
|
|
36
36
|
@logger,
|
|
37
37
|
pipeline_options
|
|
38
38
|
)
|
|
39
|
-
|
|
39
|
+
initialize_capture(options)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def notify(exception, context = {})
|
|
@@ -63,6 +63,31 @@ module Chronos
|
|
|
63
63
|
false
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
def record_event(event_type, payload = {}, context = {})
|
|
67
|
+
@telemetry.call(event_type, payload, telemetry_context(context))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def notify_once(exception, context = {})
|
|
71
|
+
execution = @context_store.get
|
|
72
|
+
captured = execution[:__chronos_captured_exceptions] || {}
|
|
73
|
+
keys = [[:object, exception.object_id], [:message, exception.message.to_s]]
|
|
74
|
+
return false if keys.any? { |key| captured[key] }
|
|
75
|
+
|
|
76
|
+
keys.each { |key| captured[key] = true }
|
|
77
|
+
@context_store.set(execution.merge(:__chronos_captured_exceptions => captured))
|
|
78
|
+
notify(exception, context)
|
|
79
|
+
rescue StandardError
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def rails_integration_options(environment = nil, console = false)
|
|
84
|
+
current_environment = (environment || @config.environment).to_s
|
|
85
|
+
enabled = @config.rails_enabled
|
|
86
|
+
enabled = false if console && !@config.rails_capture_in_console
|
|
87
|
+
enabled = false if current_environment == "test" && !@config.rails_capture_in_test
|
|
88
|
+
{:enabled => enabled, :include_user_agent => @config.rails_capture_user_agent}
|
|
89
|
+
end
|
|
90
|
+
|
|
66
91
|
def flush(timeout = DEFAULT_FLUSH_TIMEOUT)
|
|
67
92
|
@delivery_pipeline.flush(timeout)
|
|
68
93
|
rescue StandardError => error
|
|
@@ -84,6 +109,11 @@ module Chronos
|
|
|
84
109
|
|
|
85
110
|
private
|
|
86
111
|
|
|
112
|
+
def initialize_capture(options)
|
|
113
|
+
@capture = options[:capture] || Application::CaptureException.new(@config, @delivery_pipeline, @logger)
|
|
114
|
+
@telemetry = options[:telemetry] || Application::CaptureTelemetry.new(@config, @delivery_pipeline, @logger)
|
|
115
|
+
end
|
|
116
|
+
|
|
87
117
|
def build_context_store(strategy)
|
|
88
118
|
return Adapters::ThreadLocalContextStore.new if strategy == :thread_local
|
|
89
119
|
|
|
@@ -92,6 +122,8 @@ module Chronos
|
|
|
92
122
|
|
|
93
123
|
def context_for_capture(additional)
|
|
94
124
|
merged = deep_merge(context_hash(@context_store.get), context_hash(additional))
|
|
125
|
+
merged.delete(:__chronos_captured_exceptions)
|
|
126
|
+
merged.delete("__chronos_captured_exceptions")
|
|
95
127
|
buffer = merged.delete(:__chronos_breadcrumbs) || merged.delete("__chronos_breadcrumbs")
|
|
96
128
|
if buffer.respond_to?(:to_a)
|
|
97
129
|
merged[:context] = context_hash(merged[:context]).merge("breadcrumbs" => buffer.to_a)
|
|
@@ -101,6 +133,16 @@ module Chronos
|
|
|
101
133
|
context_hash(additional)
|
|
102
134
|
end
|
|
103
135
|
|
|
136
|
+
def telemetry_context(additional)
|
|
137
|
+
merged = context_for_capture(additional)
|
|
138
|
+
context = context_hash(merged.delete(:context) || merged.delete("context"))
|
|
139
|
+
parameters = merged.delete(:parameters) || merged.delete("parameters")
|
|
140
|
+
user = merged.delete(:user) || merged.delete("user")
|
|
141
|
+
context["parameters"] = parameters if parameters.is_a?(Hash) && !parameters.empty?
|
|
142
|
+
context["user"] = user if user.is_a?(Hash) && !user.empty?
|
|
143
|
+
context.merge(merged)
|
|
144
|
+
end
|
|
145
|
+
|
|
104
146
|
def deep_merge(left, right)
|
|
105
147
|
left.merge(right) do |_key, old_value, new_value|
|
|
106
148
|
old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value) : new_value
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Application
|
|
3
|
+
# Coordinates bounded non-exception telemetry capture.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Build, sanitize, serialize, and enqueue integration events.
|
|
6
|
+
# @motivation Keep Rails notification policy outside transport and domain objects.
|
|
7
|
+
# @limits It handles request, query, job, and cache events only.
|
|
8
|
+
# @collaborators TelemetryEvent, TelemetrySerializer, and DeliveryPipeline.
|
|
9
|
+
# @thread_safety Calls own event state and may execute concurrently.
|
|
10
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6; independent of Rails.
|
|
11
|
+
# @example
|
|
12
|
+
# capture.call("query", {"duration_ms" => 3.2})
|
|
13
|
+
# @errors Internal StandardError failures are contained and logged.
|
|
14
|
+
# @performance Local normalization is bounded before asynchronous queue insertion.
|
|
15
|
+
class CaptureTelemetry
|
|
16
|
+
def initialize(config, delivery_pipeline, logger = nil, options = {})
|
|
17
|
+
@config = config
|
|
18
|
+
@delivery_pipeline = delivery_pipeline
|
|
19
|
+
@logger = logger || Internal::SafeLogger.new(config.logger)
|
|
20
|
+
@serializer = options[:serializer] || Core::TelemetrySerializer.new(
|
|
21
|
+
config, nil, :max_payload_size => proc { @delivery_pipeline.max_payload_size }
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call(event_type, payload = {}, context = {})
|
|
26
|
+
return false unless @config.enabled_for_environment?
|
|
27
|
+
return false unless @delivery_pipeline.capture_allowed?(event_type)
|
|
28
|
+
|
|
29
|
+
event = Core::TelemetryEvent.new(event_type, payload, context)
|
|
30
|
+
@delivery_pipeline.enqueue(@serializer.call(event))
|
|
31
|
+
rescue StandardError => error
|
|
32
|
+
@logger.warn("Chronos telemetry capture failed: #{error.class}")
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -13,7 +13,7 @@ module Chronos
|
|
|
13
13
|
# @errors Invalid documents are ignored and return false.
|
|
14
14
|
# @performance Validation is bounded by configured document and list limits.
|
|
15
15
|
class RemoteConfiguration
|
|
16
|
-
SUPPORTED_EVENT_TYPES = ["exception"].freeze
|
|
16
|
+
SUPPORTED_EVENT_TYPES = ["exception", "request", "query", "job", "cache"].freeze
|
|
17
17
|
MAX_IGNORED_FINGERPRINTS = 100
|
|
18
18
|
MAX_FINGERPRINT_BYTES = 256
|
|
19
19
|
MIN_PAYLOAD_SIZE = 256
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
# Immutable configuration shared by all runtime components.
|
|
3
|
+
#
|
|
4
|
+
# @responsibility Expose validated settings without mutable containers.
|
|
5
|
+
# @motivation Keep capture behavior stable while multiple threads run.
|
|
6
|
+
# @limits It cannot be edited after creation.
|
|
7
|
+
# @collaborators Configuration and runtime services.
|
|
8
|
+
# @thread_safety Safe to share between threads after construction.
|
|
9
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
10
|
+
# @example
|
|
11
|
+
# snapshot.enabled_for_environment? #=> true
|
|
12
|
+
# @errors Construction occurs only after Configuration validation.
|
|
13
|
+
# @performance Deep freezing is paid once during configuration.
|
|
14
|
+
Configuration::Snapshot = Class.new do
|
|
15
|
+
attr_reader(*Configuration::ATTRIBUTES)
|
|
16
|
+
|
|
17
|
+
def initialize(values)
|
|
18
|
+
Configuration::ATTRIBUTES.each do |attribute|
|
|
19
|
+
value = values[attribute]
|
|
20
|
+
deep_freeze(value)
|
|
21
|
+
instance_variable_set("@#{attribute}", value)
|
|
22
|
+
end
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def enabled_for_environment?
|
|
27
|
+
enabled && !ignored_environments.map(&:to_s).include?(environment.to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private # rubocop:disable Layout/AccessModifierIndentation
|
|
31
|
+
|
|
32
|
+
def deep_freeze(value)
|
|
33
|
+
return value if value.respond_to?(:call) || context_store?(value)
|
|
34
|
+
|
|
35
|
+
case value
|
|
36
|
+
when Hash
|
|
37
|
+
value.each do |key, child|
|
|
38
|
+
deep_freeze(key)
|
|
39
|
+
deep_freeze(child)
|
|
40
|
+
end
|
|
41
|
+
when Array
|
|
42
|
+
value.each { |child| deep_freeze(child) }
|
|
43
|
+
end
|
|
44
|
+
value.freeze
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def context_store?(value)
|
|
48
|
+
Configuration::CONTEXT_STORE_METHODS.all? { |method_name| value.respond_to?(method_name) }
|
|
49
|
+
rescue StandardError
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|