chronos-ruby 0.3.0.pre.1 → 0.4.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 +41 -17
- data/contracts/rack-context-v1.schema.json +43 -0
- data/docs/adr/ADR-012-rack-context-isolation.md +27 -0
- data/docs/architecture.md +11 -3
- data/docs/compatibility.md +6 -6
- data/docs/configuration.md +6 -1
- data/docs/data-collected.md +8 -2
- data/docs/modules/rack-context.md +59 -0
- data/docs/performance.md +14 -2
- data/docs/privacy-lgpd.md +9 -3
- data/docs/troubleshooting.md +10 -2
- data/lib/chronos/adapters/thread_local_context_store.rb +52 -0
- data/lib/chronos/agent.rb +54 -5
- data/lib/chronos/application/capture_exception.rb +2 -2
- data/lib/chronos/configuration.rb +29 -3
- data/lib/chronos/core/breadcrumb.rb +126 -0
- data/lib/chronos/integrations/rack/middleware.rb +152 -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/version.rb +1 -1
- data/lib/chronos.rb +21 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ab10ffdc0cff4274241465904b18e4bceb4fa230ea9fddb4b8be419c08e3463
|
|
4
|
+
data.tar.gz: 2d6d778012088192efed14e0ab64958716c8a6b4c3e697127c52573dc4dcd47b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a8bb789313b2b7d360b6a8c08e5b5cf731aa1325912be7b560a1869b3d8717e4e779610e92591c1a2c436f5d14cd39e28f1e5db837d954def1db232788284d2d
|
|
7
|
+
data.tar.gz: 93a311d6905d83dee031a88cb5887854a32e60d26eceb0dc0446a8244c77a2c9fa763089bc5ec5fa25d265cb5881c3775dafe6361b1e84a1c6044aa89df220ee
|
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 to Chronos. Version 0.4 adds automatic Rack exception capture, isolated request context, and bounded breadcrumbs to the 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.4 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.4 implements the Rack protocol but does not yet declare Rails support. 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.4.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,7 @@ gem install chronos-ruby --pre
|
|
|
47
49
|
|
|
48
50
|
## Rails installation
|
|
49
51
|
|
|
50
|
-
Rails automatic
|
|
52
|
+
Rails automatic installation is not part of version 0.4. A Rails application may install the Rack middleware manually, but this does not constitute declared Rails support. Railtie and generator support are planned for version 0.5.
|
|
51
53
|
|
|
52
54
|
## Minimum configuration
|
|
53
55
|
|
|
@@ -70,7 +72,14 @@ HTTPS verification is enabled by default. HTTP requires explicitly setting `ssl_
|
|
|
70
72
|
|
|
71
73
|
## Automatic capture
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
Rack applications can capture unhandled exceptions automatically and preserve the application error semantics:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
use Chronos::Integrations::Rack::Middleware,
|
|
79
|
+
:include_user_agent => false
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
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
83
|
|
|
75
84
|
## Manual capture
|
|
76
85
|
|
|
@@ -101,15 +110,25 @@ User data is opt-in and must contain only values your application is allowed to
|
|
|
101
110
|
Chronos.notify(error, :user => {"id" => "customer-42", "role" => "operator"})
|
|
102
111
|
```
|
|
103
112
|
|
|
104
|
-
Version 0.
|
|
113
|
+
Version 0.4 sanitizes this context before delivery and before it can enter retry storage. Data minimization remains the application's responsibility.
|
|
105
114
|
|
|
106
115
|
## Breadcrumbs
|
|
107
116
|
|
|
108
|
-
Breadcrumbs
|
|
117
|
+
Breadcrumbs use a fixed circular buffer scoped to the current execution:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
Chronos.add_breadcrumb(
|
|
121
|
+
:category => "custom",
|
|
122
|
+
:message => "payment started",
|
|
123
|
+
:metadata => {"provider" => "example"}
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
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
128
|
|
|
110
129
|
## Filters and LGPD
|
|
111
130
|
|
|
112
|
-
Version 0.
|
|
131
|
+
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
132
|
|
|
114
133
|
```ruby
|
|
115
134
|
Chronos.configure do |config|
|
|
@@ -133,19 +152,19 @@ Chronos.configure do |config|
|
|
|
133
152
|
end
|
|
134
153
|
```
|
|
135
154
|
|
|
136
|
-
Local exception-specific ignore callbacks are not available in version 0.
|
|
155
|
+
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
156
|
|
|
138
157
|
## Performance monitoring
|
|
139
158
|
|
|
140
|
-
Request, SQL, cache, job, and external HTTP monitoring are not implemented in version 0.
|
|
159
|
+
Request timing events, SQL, cache, job, and external HTTP monitoring are not implemented in version 0.4. Rack exception context is bounded, and HTTP delivery runs outside the caller thread when `Chronos.notify` is used.
|
|
141
160
|
|
|
142
161
|
## Sidekiq and Active Job
|
|
143
162
|
|
|
144
|
-
Sidekiq and Active Job integrations are not implemented in version 0.
|
|
163
|
+
Sidekiq and Active Job integrations are not implemented in version 0.4. Calling the manual API from a job is possible, but automatic capture and deduplication are not yet guaranteed.
|
|
145
164
|
|
|
146
165
|
## Deploy tracking
|
|
147
166
|
|
|
148
|
-
Deploy notifications are not implemented in version 0.
|
|
167
|
+
Deploy notifications are not implemented in version 0.4. `app_version` may be included in exception events for release correlation.
|
|
149
168
|
|
|
150
169
|
## Asynchronous queue
|
|
151
170
|
|
|
@@ -168,7 +187,7 @@ Use `Chronos.flush(timeout)` to wait for accepted events and `Chronos.close(time
|
|
|
168
187
|
|
|
169
188
|
## Retry and backlog
|
|
170
189
|
|
|
171
|
-
|
|
190
|
+
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
191
|
|
|
173
192
|
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
193
|
|
|
@@ -182,7 +201,8 @@ The code follows hexagonal boundaries:
|
|
|
182
201
|
- `Chronos::Application` coordinates capture;
|
|
183
202
|
- `Chronos::Application::DeliveryPipeline` owns bounded retry and remote policy;
|
|
184
203
|
- `Chronos::Ports` defines delivery behavior;
|
|
185
|
-
- `Chronos::Adapters` implements Net::HTTP delivery;
|
|
204
|
+
- `Chronos::Adapters` implements Net::HTTP delivery and thread-local context;
|
|
205
|
+
- `Chronos::Integrations::Rack` implements optional automatic Rack capture;
|
|
186
206
|
- `Chronos::Internal` owns bounded queueing, workers, and defensive logging.
|
|
187
207
|
|
|
188
208
|
The core has no dependency on Rails, Rack, Sidekiq, or ActiveSupport. See [Architecture](docs/architecture.md).
|
|
@@ -210,6 +230,9 @@ Chronos.configure do |config|
|
|
|
210
230
|
config.circuit_failure_threshold = 5
|
|
211
231
|
config.circuit_reset_timeout = 30.0
|
|
212
232
|
config.remote_configuration = true
|
|
233
|
+
config.context_store = :thread_local
|
|
234
|
+
config.breadcrumb_capacity = 20
|
|
235
|
+
config.breadcrumb_max_bytes = 2048
|
|
213
236
|
end
|
|
214
237
|
```
|
|
215
238
|
|
|
@@ -221,7 +244,7 @@ Configuration errors are raised during `Chronos.configure`. Capture and delivery
|
|
|
221
244
|
|
|
222
245
|
## Benchmark
|
|
223
246
|
|
|
224
|
-
Run the version 0.
|
|
247
|
+
Run the version 0.4 benchmarks with:
|
|
225
248
|
|
|
226
249
|
```bash
|
|
227
250
|
bundle _1.17.3_ exec ruby benchmarks/capture_exception.rb
|
|
@@ -229,13 +252,14 @@ bundle _1.17.3_ exec ruby benchmarks/serialization.rb
|
|
|
229
252
|
bundle _1.17.3_ exec ruby benchmarks/filtering.rb
|
|
230
253
|
bundle _1.17.3_ exec ruby benchmarks/queue.rb
|
|
231
254
|
bundle _1.17.3_ exec ruby benchmarks/retry_backlog.rb
|
|
255
|
+
bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
232
256
|
```
|
|
233
257
|
|
|
234
258
|
Results depend on runtime, hardware, and payload. No performance comparison is claimed until repeatable measurements are published.
|
|
235
259
|
|
|
236
260
|
## Migration from Airbrake
|
|
237
261
|
|
|
238
|
-
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.
|
|
262
|
+
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.4 does not claim API compatibility or automatic replacement.
|
|
239
263
|
|
|
240
264
|
## Local development
|
|
241
265
|
|
|
@@ -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.
|
data/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Chronos Ruby 0.
|
|
3
|
+
Chronos Ruby 0.4 uses hexagonal boundaries so the legacy core remains independent of frameworks and delivery infrastructure.
|
|
4
4
|
|
|
5
5
|
```mermaid
|
|
6
6
|
flowchart TB
|
|
@@ -11,18 +11,26 @@ 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]
|
|
14
17
|
```
|
|
15
18
|
|
|
16
19
|
## Boundaries
|
|
17
20
|
|
|
18
21
|
- Domain/Core owns immutable event values and Ruby normalization.
|
|
19
22
|
- Application owns use-case ordering and failure containment.
|
|
20
|
-
- Ports define behavior expected from infrastructure.
|
|
21
|
-
- Adapters contain Net::HTTP and
|
|
23
|
+
- Ports define behavior expected from transport and execution-context infrastructure.
|
|
24
|
+
- Adapters contain Net::HTTP, TLS, and thread-local context behavior.
|
|
22
25
|
- Internal contains private concurrency and diagnostic mechanisms.
|
|
26
|
+
- Integrations contain optional framework entry points and never enter the domain boundary.
|
|
23
27
|
|
|
24
28
|
The `Chronos` module is a thin facade. Rails, Rack, ActiveSupport, Sidekiq, and job libraries must not be required by the core.
|
|
25
29
|
|
|
30
|
+
## Rack capture flow
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
26
34
|
## Capture flow
|
|
27
35
|
|
|
28
36
|
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.
|
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 | Rack protocol in 0.4; no Rails | Best effort | Dedicated legacy core CI and Rack contract tests |
|
|
8
|
+
| 2.3.8 | Rack protocol in 0.4; no Rails | Best effort | Dedicated legacy core CI and Rack contract tests |
|
|
9
|
+
| 2.4.10 | Rack protocol in 0.4; no Rails | Best effort | Dedicated legacy core CI and Rack contract tests |
|
|
10
|
+
| 2.5.9 | Rack protocol in 0.4; no Rails | Best effort | Dedicated legacy core CI and Rack contract tests |
|
|
11
|
+
| 2.6.10 | Rack protocol in 0.4; no Rails | Best effort | Dedicated legacy core CI and Rack contract tests |
|
|
12
12
|
| 2.7 and newer | None in 0.x | Unsupported | Belongs to transitional or modern lines |
|
|
13
13
|
|
|
14
|
-
No Rails version is declared supported by version 0.
|
|
14
|
+
No Rails version is declared supported by version 0.4. The middleware is tested against the Rack protocol shape without adding a Rack runtime dependency. Rails support requires a real example application, Railtie integration tests, and a successful dedicated matrix job.
|
|
15
15
|
|
|
16
16
|
Status meanings:
|
|
17
17
|
|
data/docs/configuration.md
CHANGED
|
@@ -39,8 +39,11 @@
|
|
|
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
41
|
| `sampling_rate` | Optional | `1.0` | Local upper bound for exception sampling |
|
|
42
|
-
| `enabled_event_types` | Optional | `["exception"]` | Local event-type allowlist; only exception exists in version 0.
|
|
42
|
+
| `enabled_event_types` | Optional | `["exception"]` | Local event-type allowlist; only exception exists in version 0.4 |
|
|
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` |
|
|
44
47
|
|
|
45
48
|
```ruby
|
|
46
49
|
Chronos.configure do |config|
|
|
@@ -56,6 +59,8 @@ Chronos.configure do |config|
|
|
|
56
59
|
config.max_retries = 3
|
|
57
60
|
config.backlog_size = 100
|
|
58
61
|
config.circuit_failure_threshold = 5
|
|
62
|
+
config.context_store = :thread_local
|
|
63
|
+
config.breadcrumb_capacity = 20
|
|
59
64
|
end
|
|
60
65
|
```
|
|
61
66
|
|
data/docs/data-collected.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Data collected
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.4 emits exception events submitted manually or captured by the optional Rack middleware. 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,14 @@ 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 |
|
|
18
24
|
|
|
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.
|
|
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.4.
|
|
20
26
|
|
|
21
27
|
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
28
|
|
|
@@ -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.
|
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.4 makes no unverified speed claim.
|
|
4
4
|
|
|
5
5
|
Current controls:
|
|
6
6
|
|
|
@@ -16,8 +16,10 @@ Current controls:
|
|
|
16
16
|
- the circuit breaker suppresses requests during sustained failure;
|
|
17
17
|
- retry backlog capacity is fixed and may be disabled;
|
|
18
18
|
- shutdown and flush have caller-controlled timeouts.
|
|
19
|
+
- request context and breadcrumbs have fixed structural and byte limits;
|
|
20
|
+
- Rack middleware never consumes request or response bodies.
|
|
19
21
|
|
|
20
|
-
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,
|
|
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, and `benchmarks/request_overhead.rb` compares a successful direct Rack-protocol call with the middleware path.
|
|
21
23
|
|
|
22
24
|
## Version 0.2 development measurement
|
|
23
25
|
|
|
@@ -51,3 +53,13 @@ A local diagnostic run on 2026-07-20 used Ruby 2.2.10 on macOS arm64 with 1,000
|
|
|
51
53
|
| Open-circuit backlog handling | 88,922 operations/second |
|
|
52
54
|
|
|
53
55
|
The outage run retained exactly 100 events and rejected 9,900 additional events without growing the backlog. These are single development runs without median or dispersion and are not comparative performance claims.
|
|
56
|
+
|
|
57
|
+
## Version 0.4 Rack middleware benchmark
|
|
58
|
+
|
|
59
|
+
Run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ITERATIONS=100000 bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A local diagnostic run on 2026-07-20 used Ruby 2.2.10 on macOS arm64 and 10,000 successful Rack-protocol calls. The direct application calls took 0.013272 seconds and middleware calls took 0.539758 seconds, for an estimated 52.649 microseconds of middleware work per request. The fixture uses no network, error capture, or response-body enumeration. This is a single development measurement, not a production latency claim; controlled warmup, median, and dispersion remain required before publishing a performance claim.
|
data/docs/privacy-lgpd.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Privacy and LGPD
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.4 sanitizes every exception event 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,15 @@ Version 0.3 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, cookies,
|
|
16
|
+
| Request/response bodies, raw query strings, cookies, authorization headers, SQL binds, environment variables | Never collected automatically in version 0.4 |
|
|
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.4 does not persist telemetry to disk.
|
|
19
|
+
|
|
20
|
+
## Rack context and breadcrumbs
|
|
21
|
+
|
|
22
|
+
The Rack middleware copies only bounded operational fields and parameter hashes that another component already parsed. It does not read `rack.input` or copy `QUERY_STRING`. User context is opt-in through `chronos.user`, and user agent collection is disabled unless the middleware option enables it.
|
|
23
|
+
|
|
24
|
+
Breadcrumbs contain category, bounded message, bounded metadata, and timestamp. No raw request, response, SQL, cache, job, log, or external HTTP payload is added automatically. Rack context and breadcrumbs pass through the same sanitizer as manual context before queueing.
|
|
19
25
|
|
|
20
26
|
Blocklist matching accepts `String`, `Symbol`, and `Regexp`. String and Symbol matching is case-insensitive after punctuation normalization and also protects namespaced keys such as `user_password`.
|
|
21
27
|
|
data/docs/troubleshooting.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Configuration raises an error
|
|
4
4
|
|
|
5
|
-
Verify `project_id`, `project_key`, and `host`. HTTPS is required while `ssl_verify` is true. Queue size, worker count, payload size, and timeouts must be positive.
|
|
5
|
+
Verify `project_id`, `project_key`, and `host`. HTTPS is required while `ssl_verify` is true. Queue size, worker count, payload size, breadcrumb capacity, and timeouts must be positive. `breadcrumb_max_bytes` must be at least 128.
|
|
6
6
|
|
|
7
7
|
## `Chronos.notify` returns false
|
|
8
8
|
|
|
@@ -10,7 +10,15 @@ The agent may be unconfigured, disabled, ignored in the current environment, una
|
|
|
10
10
|
|
|
11
11
|
## `Chronos.notify_sync` returns false
|
|
12
12
|
|
|
13
|
-
Check DNS, TLS certificates, credentials, HTTP status, proxy configuration, and timeout values.
|
|
13
|
+
Check DNS, TLS certificates, credentials, HTTP status, proxy configuration, and timeout values. The resilience layer retries only network errors, HTTP `408`, `429`, and `5xx` responses. Inspect `agent.diagnostics` when constructing an agent directly to see retry state, backlog usage, and the circuit state.
|
|
14
|
+
|
|
15
|
+
## Rack exception is not captured
|
|
16
|
+
|
|
17
|
+
Confirm that `Chronos.configure` runs before the middleware handles requests and that the middleware wraps the application component that raises. Version 0.4 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
|
+
## Context appears missing
|
|
20
|
+
|
|
21
|
+
The legacy context store is thread-local. A new application-created thread does not inherit context. Establish a new `Chronos.with_context` scope inside that thread, and issue manual notification before the scope exits. For Rack capture, supply user and explicit parameters through the documented environment keys.
|
|
14
22
|
|
|
15
23
|
## Events disappear during shutdown
|
|
16
24
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Adapters
|
|
3
|
+
# Stores execution context in the current Ruby thread.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Isolate request context and restore nested scopes reliably.
|
|
6
|
+
# @motivation Legacy Ruby does not provide a portable fiber-local storage API.
|
|
7
|
+
# @limits Context does not propagate to new threads or across processes.
|
|
8
|
+
# @collaborators ContextStore port and Rack middleware.
|
|
9
|
+
# @thread_safety Each thread owns an independent value; one instance may be shared.
|
|
10
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
11
|
+
# @example
|
|
12
|
+
# store.with_context(:request_id => "r1") { store.get }
|
|
13
|
+
# @errors The previous value is restored in ensure even when the block raises.
|
|
14
|
+
# @performance Reads and writes are constant-time thread-local operations.
|
|
15
|
+
class ThreadLocalContextStore
|
|
16
|
+
def initialize
|
|
17
|
+
@key = "chronos_context_#{object_id}".freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get
|
|
21
|
+
Thread.current[@key] || {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set(context)
|
|
25
|
+
raise ArgumentError, "context must be a Hash" unless context.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
Thread.current[@key] = context
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clear
|
|
31
|
+
Thread.current[@key] = nil
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with_context(context)
|
|
36
|
+
previous = Thread.current[@key]
|
|
37
|
+
set(merge_context(previous || {}, context))
|
|
38
|
+
yield
|
|
39
|
+
ensure
|
|
40
|
+
previous ? Thread.current[@key] = previous : clear
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def merge_context(current, additional)
|
|
46
|
+
raise ArgumentError, "context must be a Hash" unless additional.is_a?(Hash)
|
|
47
|
+
|
|
48
|
+
current.merge(additional)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/chronos/agent.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Chronos
|
|
2
2
|
# Runtime composition root for the framework-independent Chronos agent.
|
|
3
3
|
#
|
|
4
|
-
# @responsibility Own
|
|
4
|
+
# @responsibility Own delivery, capture, execution context, and breadcrumb collaborators.
|
|
5
5
|
# @motivation Keep construction details outside the public module facade.
|
|
6
|
-
# @limits It does not
|
|
7
|
-
# @collaborators
|
|
6
|
+
# @limits It does not install Rack, Rails, or job integrations automatically.
|
|
7
|
+
# @collaborators CaptureException, DeliveryPipeline, ContextStore, and BreadcrumbBuffer.
|
|
8
8
|
# @thread_safety Runtime collaborators synchronize mutable state.
|
|
9
9
|
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
10
10
|
# @example
|
|
@@ -19,6 +19,10 @@ module Chronos
|
|
|
19
19
|
def initialize(config, options = {})
|
|
20
20
|
@config = config
|
|
21
21
|
@logger = options[:logger] || Internal::SafeLogger.new(config.logger)
|
|
22
|
+
@context_store = options[:context_store] || build_context_store(config.context_store)
|
|
23
|
+
unless Ports::ContextStore.compatible?(@context_store)
|
|
24
|
+
raise ArgumentError, "context store does not implement the Chronos context-store port"
|
|
25
|
+
end
|
|
22
26
|
@transport = options[:transport] || Adapters::NetHttpTransport.new(config, @logger)
|
|
23
27
|
unless Ports::Transport.compatible?(@transport)
|
|
24
28
|
raise ArgumentError, "transport does not implement the Chronos transport port"
|
|
@@ -36,11 +40,27 @@ module Chronos
|
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def notify(exception, context = {})
|
|
39
|
-
@capture.call(exception, context)
|
|
43
|
+
@capture.call(exception, context_for_capture(context))
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def notify_sync(exception, context = {})
|
|
43
|
-
@capture.call_sync(exception, context)
|
|
47
|
+
@capture.call_sync(exception, context_for_capture(context))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def with_context(context = {}, &block)
|
|
51
|
+
@context_store.with_context(context, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_breadcrumb(attributes = {})
|
|
55
|
+
context = @context_store.get
|
|
56
|
+
buffer = context[:__chronos_breadcrumbs]
|
|
57
|
+
buffer ||= Core::BreadcrumbBuffer.new(@config.breadcrumb_capacity, @config.breadcrumb_max_bytes)
|
|
58
|
+
buffer.add(attributes)
|
|
59
|
+
@context_store.set(context.merge(:__chronos_breadcrumbs => buffer))
|
|
60
|
+
true
|
|
61
|
+
rescue StandardError => error
|
|
62
|
+
@logger.warn("Chronos breadcrumb failed: #{error.class}")
|
|
63
|
+
false
|
|
44
64
|
end
|
|
45
65
|
|
|
46
66
|
def flush(timeout = DEFAULT_FLUSH_TIMEOUT)
|
|
@@ -61,5 +81,34 @@ module Chronos
|
|
|
61
81
|
details = @delivery_pipeline.diagnostics
|
|
62
82
|
details[:queue].merge(details)
|
|
63
83
|
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def build_context_store(strategy)
|
|
88
|
+
return Adapters::ThreadLocalContextStore.new if strategy == :thread_local
|
|
89
|
+
|
|
90
|
+
strategy
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def context_for_capture(additional)
|
|
94
|
+
merged = deep_merge(context_hash(@context_store.get), context_hash(additional))
|
|
95
|
+
buffer = merged.delete(:__chronos_breadcrumbs) || merged.delete("__chronos_breadcrumbs")
|
|
96
|
+
if buffer.respond_to?(:to_a)
|
|
97
|
+
merged[:context] = context_hash(merged[:context]).merge("breadcrumbs" => buffer.to_a)
|
|
98
|
+
end
|
|
99
|
+
merged
|
|
100
|
+
rescue StandardError
|
|
101
|
+
context_hash(additional)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def deep_merge(left, right)
|
|
105
|
+
left.merge(right) do |_key, old_value, new_value|
|
|
106
|
+
old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value) : new_value
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def context_hash(value)
|
|
111
|
+
value.is_a?(Hash) ? value : {}
|
|
112
|
+
end
|
|
64
113
|
end
|
|
65
114
|
end
|
|
@@ -2,9 +2,9 @@ module Chronos
|
|
|
2
2
|
module Application
|
|
3
3
|
# Orchestrates exception normalization, serialization, queueing, and delivery.
|
|
4
4
|
#
|
|
5
|
-
# @responsibility Execute the
|
|
5
|
+
# @responsibility Execute the exception capture pipeline for manual and integration callers.
|
|
6
6
|
# @motivation Keep the public facade and transport adapters free of use-case logic.
|
|
7
|
-
# @limits
|
|
7
|
+
# @limits Framework hooks remain separate integration adapters.
|
|
8
8
|
# @collaborators NoticeBuilder, PayloadSerializer, and DeliveryPipeline.
|
|
9
9
|
# @thread_safety Collaborators are immutable or synchronized; calls may run concurrently.
|
|
10
10
|
# @compatibility Ruby 2.2.10 through Ruby 2.6; independent of Rails.
|
|
@@ -24,6 +24,7 @@ module Chronos
|
|
|
24
24
|
token access_token refresh_token private_key client_secret cookie set-cookie
|
|
25
25
|
session credit_card card_number cvv cpf cnpj
|
|
26
26
|
).freeze
|
|
27
|
+
CONTEXT_STORE_METHODS = [:get, :set, :clear, :with_context].freeze
|
|
27
28
|
|
|
28
29
|
ATTRIBUTES = [
|
|
29
30
|
:project_id, :project_key, :host, :environment, :app_version,
|
|
@@ -35,7 +36,8 @@ module Chronos
|
|
|
35
36
|
:retry_base_interval, :retry_max_interval, :retry_jitter,
|
|
36
37
|
:backlog_size, :circuit_failure_threshold, :circuit_reset_timeout,
|
|
37
38
|
:remote_configuration, :remote_config_max_bytes, :sampling_rate,
|
|
38
|
-
:enabled_event_types, :max_remote_send_interval
|
|
39
|
+
:enabled_event_types, :max_remote_send_interval, :context_store,
|
|
40
|
+
:breadcrumb_capacity, :breadcrumb_max_bytes
|
|
39
41
|
].freeze
|
|
40
42
|
|
|
41
43
|
attr_accessor(*ATTRIBUTES)
|
|
@@ -57,7 +59,7 @@ module Chronos
|
|
|
57
59
|
validation_errors.empty?
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
def validation_errors
|
|
62
|
+
def validation_errors # rubocop:disable Metrics/AbcSize
|
|
61
63
|
errors = []
|
|
62
64
|
if enabled
|
|
63
65
|
errors << "project_id is required" if blank?(project_id)
|
|
@@ -71,6 +73,7 @@ module Chronos
|
|
|
71
73
|
errors << "max_payload_size must be a positive integer" unless positive_integer?(max_payload_size)
|
|
72
74
|
errors.concat(resilience_errors)
|
|
73
75
|
errors.concat(privacy_errors)
|
|
76
|
+
errors.concat(context_errors)
|
|
74
77
|
errors
|
|
75
78
|
end
|
|
76
79
|
|
|
@@ -97,6 +100,9 @@ module Chronos
|
|
|
97
100
|
@user_agent = "chronos-ruby/#{Chronos::VERSION}"
|
|
98
101
|
@max_payload_size = 1_048_576
|
|
99
102
|
@gzip = false
|
|
103
|
+
@context_store = :thread_local
|
|
104
|
+
@breadcrumb_capacity = 20
|
|
105
|
+
@breadcrumb_max_bytes = 2048
|
|
100
106
|
end
|
|
101
107
|
|
|
102
108
|
def initialize_privacy_defaults
|
|
@@ -187,6 +193,20 @@ module Chronos
|
|
|
187
193
|
errors
|
|
188
194
|
end
|
|
189
195
|
|
|
196
|
+
def context_errors
|
|
197
|
+
errors = []
|
|
198
|
+
unless context_store == :thread_local || CONTEXT_STORE_METHODS.all? do |method_name|
|
|
199
|
+
context_store.respond_to?(method_name)
|
|
200
|
+
end
|
|
201
|
+
errors << "context_store must be :thread_local or implement get, set, clear, and with_context"
|
|
202
|
+
end
|
|
203
|
+
errors << "breadcrumb_capacity must be a positive integer" unless positive_integer?(breadcrumb_capacity)
|
|
204
|
+
unless breadcrumb_max_bytes.is_a?(Integer) && breadcrumb_max_bytes >= 128
|
|
205
|
+
errors << "breadcrumb_max_bytes must be an integer greater than or equal to 128"
|
|
206
|
+
end
|
|
207
|
+
errors
|
|
208
|
+
end
|
|
209
|
+
|
|
190
210
|
def filter_errors
|
|
191
211
|
return ["filters must be an array"] unless filters.is_a?(Array)
|
|
192
212
|
return [] if filters.all? { |filter| filter.respond_to?(:call) }
|
|
@@ -278,7 +298,7 @@ module Chronos
|
|
|
278
298
|
private
|
|
279
299
|
|
|
280
300
|
def deep_freeze(value)
|
|
281
|
-
return value if value.respond_to?(:call)
|
|
301
|
+
return value if value.respond_to?(:call) || context_store?(value)
|
|
282
302
|
|
|
283
303
|
case value
|
|
284
304
|
when Hash
|
|
@@ -291,6 +311,12 @@ module Chronos
|
|
|
291
311
|
end
|
|
292
312
|
value.freeze
|
|
293
313
|
end
|
|
314
|
+
|
|
315
|
+
def context_store?(value)
|
|
316
|
+
CONTEXT_STORE_METHODS.all? { |method_name| value.respond_to?(method_name) }
|
|
317
|
+
rescue StandardError
|
|
318
|
+
false
|
|
319
|
+
end
|
|
294
320
|
end
|
|
295
321
|
end
|
|
296
322
|
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "time"
|
|
3
|
+
|
|
4
|
+
module Chronos
|
|
5
|
+
module Core
|
|
6
|
+
# Immutable, bounded diagnostic marker attached to an execution.
|
|
7
|
+
#
|
|
8
|
+
# @responsibility Normalize one breadcrumb without retaining raw application objects.
|
|
9
|
+
# @motivation Preserve useful events leading to an exception with predictable memory use.
|
|
10
|
+
# @limits It does not capture logs, SQL, bodies, or HTTP calls automatically.
|
|
11
|
+
# @collaborators SafeSerializer and BreadcrumbBuffer.
|
|
12
|
+
# @thread_safety Immutable after construction.
|
|
13
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
14
|
+
# @example
|
|
15
|
+
# breadcrumb.to_h #=> {"category"=>"custom", ...}
|
|
16
|
+
# @errors Unserializable metadata is replaced by SafeSerializer placeholders.
|
|
17
|
+
# @performance Metadata traversal has strict depth, node, item, and byte limits.
|
|
18
|
+
class Breadcrumb
|
|
19
|
+
CATEGORIES = %w(custom log request query external_http cache job).freeze
|
|
20
|
+
|
|
21
|
+
def initialize(attributes, clock = nil, max_bytes = 2048)
|
|
22
|
+
attributes = {} unless attributes.is_a?(Hash)
|
|
23
|
+
clock ||= proc { Time.now }
|
|
24
|
+
serializer = SafeSerializer.new(
|
|
25
|
+
:max_depth => 5, :max_keys => 20, :max_items => 20,
|
|
26
|
+
:max_string_bytes => 512, :max_nodes => 100
|
|
27
|
+
)
|
|
28
|
+
@data = serializer.call(build_data(attributes, clock))
|
|
29
|
+
@data = compact_data(@data, max_bytes) if JSON.generate(@data).bytesize > max_bytes
|
|
30
|
+
deep_freeze(@data)
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_h
|
|
35
|
+
@data
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_data(attributes, clock)
|
|
41
|
+
category = value(attributes, :category).to_s
|
|
42
|
+
category = "custom" unless CATEGORIES.include?(category)
|
|
43
|
+
{
|
|
44
|
+
"category" => category,
|
|
45
|
+
"message" => value(attributes, :message).to_s,
|
|
46
|
+
"metadata" => value(attributes, :metadata) || {},
|
|
47
|
+
"timestamp" => clock.call.utc.iso8601(6)
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def compact_data(data, max_bytes)
|
|
52
|
+
message_limit = [max_bytes / 4, 32].max
|
|
53
|
+
compacted = {
|
|
54
|
+
"category" => data["category"],
|
|
55
|
+
"message" => SafeSerializer.new.call(data["message"], :max_string_bytes => message_limit),
|
|
56
|
+
"metadata" => {"_truncated" => true},
|
|
57
|
+
"timestamp" => data["timestamp"]
|
|
58
|
+
}
|
|
59
|
+
trim_compacted_message(compacted, max_bytes)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def trim_compacted_message(compacted, max_bytes)
|
|
63
|
+
while JSON.generate(compacted).bytesize > max_bytes && !compacted["message"].empty?
|
|
64
|
+
length = [compacted["message"].bytesize - 16, 0].max
|
|
65
|
+
compacted["message"] = SafeSerializer.new.call(
|
|
66
|
+
compacted["message"].byteslice(0, length).to_s,
|
|
67
|
+
:max_string_bytes => length
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
compacted
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def value(attributes, key)
|
|
74
|
+
attributes.key?(key) ? attributes[key] : attributes[key.to_s]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def deep_freeze(value)
|
|
78
|
+
if value.is_a?(Hash)
|
|
79
|
+
value.each do |key, child|
|
|
80
|
+
deep_freeze(key)
|
|
81
|
+
deep_freeze(child)
|
|
82
|
+
end
|
|
83
|
+
elsif value.is_a?(Array)
|
|
84
|
+
value.each { |child| deep_freeze(child) }
|
|
85
|
+
end
|
|
86
|
+
value.freeze
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Fixed-size circular collection of execution breadcrumbs.
|
|
91
|
+
#
|
|
92
|
+
# @responsibility Retain only the newest bounded breadcrumbs for one execution.
|
|
93
|
+
# @motivation Prevent long requests or noisy instrumentation from growing memory indefinitely.
|
|
94
|
+
# @limits It is process memory only and does not collect events by itself.
|
|
95
|
+
# @collaborators Breadcrumb and Agent.
|
|
96
|
+
# @thread_safety Intended for one execution thread; snapshots are immutable values.
|
|
97
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
98
|
+
# @example
|
|
99
|
+
# buffer.add(:category => "custom", :message => "started")
|
|
100
|
+
# @errors Invalid attributes are normalized into a safe breadcrumb.
|
|
101
|
+
# @performance Add is constant time and storage never exceeds capacity.
|
|
102
|
+
class BreadcrumbBuffer
|
|
103
|
+
def initialize(capacity, max_bytes = 2048)
|
|
104
|
+
raise ArgumentError, "capacity must be a positive integer" unless capacity.is_a?(Integer) && capacity > 0
|
|
105
|
+
|
|
106
|
+
@capacity = capacity
|
|
107
|
+
@max_bytes = max_bytes
|
|
108
|
+
@items = []
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def add(attributes)
|
|
112
|
+
@items.shift if @items.length >= @capacity
|
|
113
|
+
@items << Breadcrumb.new(attributes, nil, @max_bytes)
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_a
|
|
118
|
+
@items.map(&:to_h)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def size
|
|
122
|
+
@items.size
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module Chronos
|
|
4
|
+
module Integrations
|
|
5
|
+
module Rack
|
|
6
|
+
# Captures unhandled Rack exceptions while preserving Rack semantics.
|
|
7
|
+
#
|
|
8
|
+
# @responsibility Build bounded request context, notify Chronos, and re-raise the original exception.
|
|
9
|
+
# @motivation Provide automatic error capture without coupling the core to Rack.
|
|
10
|
+
# @limits It never reads rack.input, enumerates response bodies, or infers Rails routes.
|
|
11
|
+
# @collaborators Rack application and the Chronos facade or Agent.
|
|
12
|
+
# @thread_safety Shared instances keep no per-request mutable state.
|
|
13
|
+
# @compatibility Rack 1.x/2.x protocol; Ruby 2.2.10 through Ruby 2.6.
|
|
14
|
+
# @example
|
|
15
|
+
# use Chronos::Integrations::Rack::Middleware
|
|
16
|
+
# @errors Notification failures are contained; the application exception is always re-raised.
|
|
17
|
+
# @performance Adds bounded hash construction and monotonic-clock reads per request.
|
|
18
|
+
class Middleware
|
|
19
|
+
def initialize(app, options = {})
|
|
20
|
+
@app = app
|
|
21
|
+
@notifier = options[:notifier] || Chronos
|
|
22
|
+
@include_user_agent = options[:include_user_agent] || false
|
|
23
|
+
@clock = options[:clock] || proc { monotonic_time }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call(env)
|
|
27
|
+
started_at = @clock.call
|
|
28
|
+
base = request_capture_context(env)
|
|
29
|
+
@notifier.with_context(base) do
|
|
30
|
+
add_request_breadcrumb("request started", base)
|
|
31
|
+
call_application(env, base, started_at)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def call_application(env, base, started_at)
|
|
38
|
+
response = @app.call(env)
|
|
39
|
+
status = response[0]
|
|
40
|
+
headers = response[1]
|
|
41
|
+
add_request_breadcrumb("request completed", dynamic_request_context(base, status, headers, started_at))
|
|
42
|
+
response
|
|
43
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
44
|
+
context = dynamic_request_context(base, 500, nil, started_at)
|
|
45
|
+
notify_safely(error, context)
|
|
46
|
+
raise
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def notify_safely(error, context)
|
|
50
|
+
@notifier.notify(error, context)
|
|
51
|
+
rescue StandardError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def request_capture_context(env)
|
|
56
|
+
request = request_values(env)
|
|
57
|
+
{
|
|
58
|
+
:context => {"request" => request, "trace_id" => trace_id(env)},
|
|
59
|
+
:parameters => parameters(env),
|
|
60
|
+
:user => hash_value(env["chronos.user"])
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def request_values(env)
|
|
65
|
+
values = {
|
|
66
|
+
"method" => env["REQUEST_METHOD"].to_s,
|
|
67
|
+
"route" => normalized_route(env),
|
|
68
|
+
"request_id" => request_id(env),
|
|
69
|
+
"host" => (env["HTTP_HOST"] || env["SERVER_NAME"]).to_s,
|
|
70
|
+
"path" => env["PATH_INFO"].to_s,
|
|
71
|
+
"controller" => controller_action(env, "controller"),
|
|
72
|
+
"action" => controller_action(env, "action")
|
|
73
|
+
}
|
|
74
|
+
values["user_agent"] = env["HTTP_USER_AGENT"].to_s if @include_user_agent
|
|
75
|
+
values
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def dynamic_request_context(base, status, headers, started_at)
|
|
79
|
+
request = base[:context]["request"].merge(
|
|
80
|
+
"status" => status.to_i,
|
|
81
|
+
"duration_ms" => ((@clock.call - started_at) * 1000.0).round(3),
|
|
82
|
+
"response_size" => response_size(headers)
|
|
83
|
+
)
|
|
84
|
+
{:context => base[:context].merge("request" => request)}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parameters(env)
|
|
88
|
+
result = {}
|
|
89
|
+
[env["rack.request.query_hash"], env["action_dispatch.request.query_parameters"],
|
|
90
|
+
env["action_dispatch.request.path_parameters"], env["chronos.parameters"]].each do |candidate|
|
|
91
|
+
result.merge!(candidate) if candidate.is_a?(Hash)
|
|
92
|
+
end
|
|
93
|
+
result
|
|
94
|
+
rescue StandardError
|
|
95
|
+
{}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def normalized_route(env)
|
|
99
|
+
explicit = env["chronos.route"] || env["action_dispatch.route_uri_pattern"]
|
|
100
|
+
return explicit.to_s unless explicit.to_s.empty?
|
|
101
|
+
|
|
102
|
+
env["PATH_INFO"].to_s.split("/").map { |part| dynamic_segment?(part) ? ":id" : part }.join("/")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def dynamic_segment?(segment)
|
|
106
|
+
segment =~ /\A\d+\z/ || segment =~ /\A[0-9a-f]{8}-[0-9a-f-]{27,}\z/i
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def controller_action(env, key)
|
|
110
|
+
explicit = env["chronos.#{key}"]
|
|
111
|
+
paths = env["action_dispatch.request.path_parameters"]
|
|
112
|
+
explicit || (paths[key] if paths.is_a?(Hash)) || (paths[key.to_sym] if paths.is_a?(Hash))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def request_id(env)
|
|
116
|
+
env["chronos.request_id"] || env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def trace_id(env)
|
|
120
|
+
env["chronos.trace_id"] || SecureRandom.uuid
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def response_size(headers)
|
|
124
|
+
return nil unless headers.respond_to?(:each)
|
|
125
|
+
|
|
126
|
+
pair = headers.find { |key, _value| key.to_s.casecmp("content-length").zero? }
|
|
127
|
+
pair ? pair[1].to_i : nil
|
|
128
|
+
rescue StandardError
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def add_request_breadcrumb(message, context)
|
|
133
|
+
request = context[:context]["request"]
|
|
134
|
+
@notifier.add_breadcrumb(
|
|
135
|
+
:category => "request", :message => message,
|
|
136
|
+
:metadata => {"method" => request["method"], "route" => request["route"], "status" => request["status"]}
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def hash_value(value)
|
|
141
|
+
value.is_a?(Hash) ? value : {}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def monotonic_time
|
|
145
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
146
|
+
rescue StandardError
|
|
147
|
+
Time.now.to_f
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Integrations
|
|
3
|
+
# Rack protocol integrations that do not require Rack at gem load time.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Namespace Rack-compatible middleware.
|
|
6
|
+
# @motivation Permit optional use without adding a runtime Rack dependency.
|
|
7
|
+
# @limits It does not provide Rails-specific route discovery.
|
|
8
|
+
# @thread_safety Middleware instances may be shared by concurrent Rack threads.
|
|
9
|
+
# @compatibility Rack 1.x and 2.x protocol shapes on Ruby 2.2.10 through Ruby 2.6.
|
|
10
|
+
module Rack; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
# Optional framework entry points kept outside the framework-independent core.
|
|
3
|
+
#
|
|
4
|
+
# @responsibility Namespace integration adapters such as Rack middleware.
|
|
5
|
+
# @motivation Keep framework loading optional for plain Ruby applications.
|
|
6
|
+
# @limits Integrations may depend only on documented public agent behavior.
|
|
7
|
+
# @thread_safety Each integration documents its own guarantees.
|
|
8
|
+
# @compatibility Version 0.4 supports Rack protocol behavior on Ruby 2.2.10 through 2.6.
|
|
9
|
+
module Integrations; end
|
|
10
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Ports
|
|
3
|
+
# Conceptual storage port for execution-scoped context.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Define get, set, clear, and scoped context behavior.
|
|
6
|
+
# @motivation Let integrations isolate request state without depending on a thread implementation.
|
|
7
|
+
# @limits The port does not prescribe thread, fiber, or distributed propagation semantics.
|
|
8
|
+
# @collaborators Agent and context-store adapters.
|
|
9
|
+
# @thread_safety Implementations must isolate concurrent executions.
|
|
10
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
11
|
+
# @example
|
|
12
|
+
# Chronos::Ports::ContextStore.compatible?(store) #=> true
|
|
13
|
+
# @errors Compatibility checks never invoke application methods.
|
|
14
|
+
module ContextStore
|
|
15
|
+
REQUIRED_METHODS = [:get, :set, :clear, :with_context].freeze
|
|
16
|
+
|
|
17
|
+
def self.compatible?(object)
|
|
18
|
+
REQUIRED_METHODS.all? { |method_name| object.respond_to?(method_name) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/chronos/version.rb
CHANGED
data/lib/chronos.rb
CHANGED
|
@@ -16,23 +16,29 @@ require "chronos/core/sanitizer"
|
|
|
16
16
|
require "chronos/core/safe_serializer"
|
|
17
17
|
require "chronos/core/payload_serializer"
|
|
18
18
|
require "chronos/ports/transport"
|
|
19
|
+
require "chronos/ports/context_store"
|
|
19
20
|
require "chronos/internal/safe_logger"
|
|
20
21
|
require "chronos/internal/bounded_queue"
|
|
21
22
|
require "chronos/internal/memory_backlog"
|
|
22
23
|
require "chronos/internal/worker_pool"
|
|
23
24
|
require "chronos/adapters/net_http_transport"
|
|
25
|
+
require "chronos/adapters/thread_local_context_store"
|
|
26
|
+
require "chronos/core/breadcrumb"
|
|
24
27
|
require "chronos/application/retry_policy"
|
|
25
28
|
require "chronos/application/circuit_breaker"
|
|
26
29
|
require "chronos/application/remote_configuration"
|
|
27
30
|
require "chronos/application/delivery_pipeline"
|
|
28
31
|
require "chronos/application/capture_exception"
|
|
29
32
|
require "chronos/agent"
|
|
33
|
+
require "chronos/integrations"
|
|
34
|
+
require "chronos/integrations/rack"
|
|
35
|
+
require "chronos/integrations/rack/middleware"
|
|
30
36
|
|
|
31
37
|
# Framework-independent public facade for the Chronos Ruby agent.
|
|
32
38
|
#
|
|
33
39
|
# @responsibility Configure the agent and expose its small lifecycle API.
|
|
34
40
|
# @motivation Give applications a stable entry point while internals evolve.
|
|
35
|
-
# @limits Version 0.
|
|
41
|
+
# @limits Version 0.4 captures Rack failures but does not install middleware automatically.
|
|
36
42
|
# @collaborators Configuration and Agent.
|
|
37
43
|
# @thread_safety Agent replacement and lookup are protected by a mutex.
|
|
38
44
|
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
@@ -75,6 +81,20 @@ module Chronos
|
|
|
75
81
|
false
|
|
76
82
|
end
|
|
77
83
|
|
|
84
|
+
def with_context(context = {})
|
|
85
|
+
agent = current_agent
|
|
86
|
+
return yield unless agent
|
|
87
|
+
|
|
88
|
+
agent.with_context(context) { yield }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_breadcrumb(attributes = {})
|
|
92
|
+
agent = current_agent
|
|
93
|
+
agent ? agent.add_breadcrumb(attributes) : false
|
|
94
|
+
rescue StandardError
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
|
|
78
98
|
def configured?
|
|
79
99
|
!current_agent.nil?
|
|
80
100
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chronos-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0.pre.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antonio Jefferson
|
|
@@ -87,6 +87,7 @@ files:
|
|
|
87
87
|
- README.md
|
|
88
88
|
- SECURITY.md
|
|
89
89
|
- contracts/event-v1.schema.json
|
|
90
|
+
- contracts/rack-context-v1.schema.json
|
|
90
91
|
- docs/adr/ADR-001-core-and-integrations.md
|
|
91
92
|
- docs/adr/ADR-002-version-lines.md
|
|
92
93
|
- docs/adr/ADR-003-net-http-legacy-transport.md
|
|
@@ -94,6 +95,7 @@ files:
|
|
|
94
95
|
- docs/adr/ADR-005-sanitize-before-backlog.md
|
|
95
96
|
- docs/adr/ADR-006-versioned-event-contract.md
|
|
96
97
|
- docs/adr/ADR-011-bounded-resilience-and-remote-control.md
|
|
98
|
+
- docs/adr/ADR-012-rack-context-isolation.md
|
|
97
99
|
- docs/architecture.md
|
|
98
100
|
- docs/compatibility.md
|
|
99
101
|
- docs/configuration.md
|
|
@@ -102,6 +104,7 @@ files:
|
|
|
102
104
|
- docs/modules/async-queue.md
|
|
103
105
|
- docs/modules/configuration.md
|
|
104
106
|
- docs/modules/notice-pipeline.md
|
|
107
|
+
- docs/modules/rack-context.md
|
|
105
108
|
- docs/modules/remote-configuration.md
|
|
106
109
|
- docs/modules/retry-backlog.md
|
|
107
110
|
- docs/modules/sanitization.md
|
|
@@ -113,6 +116,7 @@ files:
|
|
|
113
116
|
- lib/chronos.rb
|
|
114
117
|
- lib/chronos/adapters.rb
|
|
115
118
|
- lib/chronos/adapters/net_http_transport.rb
|
|
119
|
+
- lib/chronos/adapters/thread_local_context_store.rb
|
|
116
120
|
- lib/chronos/agent.rb
|
|
117
121
|
- lib/chronos/application.rb
|
|
118
122
|
- lib/chronos/application/capture_exception.rb
|
|
@@ -123,6 +127,7 @@ files:
|
|
|
123
127
|
- lib/chronos/configuration.rb
|
|
124
128
|
- lib/chronos/core.rb
|
|
125
129
|
- lib/chronos/core/backtrace_parser.rb
|
|
130
|
+
- lib/chronos/core/breadcrumb.rb
|
|
126
131
|
- lib/chronos/core/exception_cause_collector.rb
|
|
127
132
|
- lib/chronos/core/notice.rb
|
|
128
133
|
- lib/chronos/core/notice_builder.rb
|
|
@@ -132,12 +137,16 @@ files:
|
|
|
132
137
|
- lib/chronos/core/sanitizer.rb
|
|
133
138
|
- lib/chronos/core/sensitive_value_filter.rb
|
|
134
139
|
- lib/chronos/errors.rb
|
|
140
|
+
- lib/chronos/integrations.rb
|
|
141
|
+
- lib/chronos/integrations/rack.rb
|
|
142
|
+
- lib/chronos/integrations/rack/middleware.rb
|
|
135
143
|
- lib/chronos/internal.rb
|
|
136
144
|
- lib/chronos/internal/bounded_queue.rb
|
|
137
145
|
- lib/chronos/internal/memory_backlog.rb
|
|
138
146
|
- lib/chronos/internal/safe_logger.rb
|
|
139
147
|
- lib/chronos/internal/worker_pool.rb
|
|
140
148
|
- lib/chronos/ports.rb
|
|
149
|
+
- lib/chronos/ports/context_store.rb
|
|
141
150
|
- lib/chronos/ports/transport.rb
|
|
142
151
|
- lib/chronos/ruby.rb
|
|
143
152
|
- lib/chronos/ruby/version.rb
|