chronos-ruby 0.5.0.pre.1 → 0.6.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 +22 -0
- data/README.md +17 -7
- data/contracts/sidekiq-job-v1.schema.json +21 -0
- data/docs/adr/ADR-014-sidekiq-envelope-context.md +27 -0
- data/docs/architecture.md +6 -1
- data/docs/compatibility.md +6 -0
- data/docs/data-collected.md +7 -2
- data/docs/modules/rails-legacy.md +1 -1
- data/docs/modules/sidekiq-legacy.md +23 -0
- data/docs/performance.md +12 -1
- data/docs/privacy-lgpd.md +8 -3
- data/docs/troubleshooting.md +8 -0
- data/lib/chronos/agent.rb +15 -0
- data/lib/chronos/integrations/job_payload.rb +72 -0
- data/lib/chronos/integrations/sidekiq.rb +258 -0
- data/lib/chronos/internal/worker_pool.rb +19 -2
- data/lib/chronos/sidekiq.rb +12 -0
- data/lib/chronos/version.rb +1 -1
- data/lib/chronos.rb +8 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ad7bc09dd58b0476b65687d0b7c695bdc5e82c898f882fc82ed2e87eb352b79
|
|
4
|
+
data.tar.gz: 527383d76a4432bc16f48d2a476d7c2398d66f5b891bbe666d1e0ceba9dc9959
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 24bf5ed37de64c34a82fd0b98f3e1323cac14c2392fa0d6a01dbcd24d48a69a52100a6127a79b2567314bcf2c464b354fee226ab19bba08ea384bfb3ec8a12f9
|
|
7
|
+
data.tar.gz: 1696953512893c9d3eecc23d361b35c5434df7dda25e33a1fe6321b5c25ae4d9e03a2a843bd76e9c327130be2cba2a6c7a9766d70a6f068007b24959aa3cfdc0
|
data/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,33 @@ All notable changes are documented here. The project follows Semantic Versioning
|
|
|
6
6
|
|
|
7
7
|
### Fixed
|
|
8
8
|
|
|
9
|
+
- `WorkerPool#flush` no longer returns before a worker marks a just-popped event as active;
|
|
9
10
|
- release publishing now updates RubyGems to a Ruby 2.6-compatible version that supports `GEM_HOST_API_KEY`.
|
|
10
11
|
- Updated the legacy development toolchain to non-vulnerable Rake and RuboCop versions.
|
|
11
12
|
- legacy CI now resolves Bundler 1.17.3 through `Gem.bin_path` on RubyGems versions that do not support the `_version_` executable selector.
|
|
12
13
|
- documentation verification now reads source and Markdown files explicitly as UTF-8 on legacy container locales.
|
|
13
14
|
|
|
15
|
+
## [0.6.0.pre.1] - 2026-07-20
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- optional Sidekiq 4/5 client and server middleware loaded through `chronos/sidekiq`;
|
|
20
|
+
- versioned trace/request context propagation beside, never inside, public job arguments;
|
|
21
|
+
- Sidekiq class, queue, JID, retry count, duration, calculable latency, bounded arguments, tags, status, and error telemetry;
|
|
22
|
+
- shared per-job exception deduplication for nested Sidekiq and Active Job capture paths;
|
|
23
|
+
- Sidekiq payload contract, unit/integration tests, executable example, benchmark, module documentation, and ADR-014.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- version advanced to `0.6.0.pre.1`;
|
|
28
|
+
- the public facade exposes bounded propagation context for optional process-boundary integrations.
|
|
29
|
+
|
|
30
|
+
### Known limitations
|
|
31
|
+
|
|
32
|
+
- Sidekiq 4/5 remains `Best effort` until dedicated real-gem matrix jobs pass;
|
|
33
|
+
- Active Job context propagation, Resque, and Delayed Job remain subsequent version 0.6 increments;
|
|
34
|
+
- Sidekiq argument capture is automatic, though strictly bounded and sanitized.
|
|
35
|
+
|
|
14
36
|
## [0.4.0.pre.1] - 2026-07-20
|
|
15
37
|
|
|
16
38
|
### Added
|
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 and bounded telemetry to Chronos. Version 0.
|
|
3
|
+
Chronos Ruby is the framework-independent client for sending Ruby application errors and bounded telemetry to Chronos. Version 0.6 starts legacy worker support with Sidekiq 4/5 middleware, cross-process trace context, bounded job telemetry, and exception deduplication on top of the Rails, 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.6 can collect:
|
|
8
8
|
|
|
9
9
|
- exception class, message, structured backtrace, and chained causes;
|
|
10
10
|
- timestamp, severity, tags, and an optional fingerprint;
|
|
@@ -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.6.0.pre.1"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Install with a Bundler version compatible with the application. For the oldest supported runtime:
|
|
@@ -52,7 +52,7 @@ gem install chronos-ruby --pre
|
|
|
52
52
|
Version 0.5 exposes Rails support explicitly, keeping Rails and ActiveSupport out of plain Ruby applications:
|
|
53
53
|
|
|
54
54
|
```ruby
|
|
55
|
-
gem "chronos-ruby", "0.
|
|
55
|
+
gem "chronos-ruby", "0.6.0.pre.1", :require => "chronos/rails"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Generate the initializer with:
|
|
@@ -172,7 +172,16 @@ Version 0.5 emits bounded `request`, `query`, `job`, and `cache` events from pub
|
|
|
172
172
|
|
|
173
173
|
## Sidekiq and Active Job
|
|
174
174
|
|
|
175
|
-
Version 0.
|
|
175
|
+
Version `0.6.0.pre.1` adds optional Sidekiq 4/5 middleware:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
gem "sidekiq", "~> 5.0"
|
|
179
|
+
gem "chronos-ruby", "0.6.0.pre.1", :require => "chronos/sidekiq"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The client middleware propagates only trace/request identifiers in a versioned Sidekiq-envelope field and never changes worker arguments. The server records class, queue, JID, retry count, duration, calculable queue latency, bounded arguments/tags, status, and error class. Values pass through the shared sanitizer before delivery. Failed jobs are notified once and the original exception is re-raised. See [Sidekiq 4/5 legacy integration](docs/modules/sidekiq-legacy.md).
|
|
183
|
+
|
|
184
|
+
The Rails subscriber continues to record basic Active Job class, queue, and duration. Full Active Job context propagation plus optional Resque and Delayed Job adapters are subsequent version 0.6 increments.
|
|
176
185
|
|
|
177
186
|
## Deploy tracking
|
|
178
187
|
|
|
@@ -257,7 +266,7 @@ Configuration errors are raised during `Chronos.configure`. Capture and delivery
|
|
|
257
266
|
|
|
258
267
|
## Benchmark
|
|
259
268
|
|
|
260
|
-
Run the version 0.
|
|
269
|
+
Run the version 0.6 benchmarks with:
|
|
261
270
|
|
|
262
271
|
```bash
|
|
263
272
|
bundle _1.17.3_ exec ruby benchmarks/capture_exception.rb
|
|
@@ -267,13 +276,14 @@ bundle _1.17.3_ exec ruby benchmarks/queue.rb
|
|
|
267
276
|
bundle _1.17.3_ exec ruby benchmarks/retry_backlog.rb
|
|
268
277
|
bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
269
278
|
bundle _1.17.3_ exec ruby benchmarks/rails_notifications.rb
|
|
279
|
+
bundle _1.17.3_ exec ruby benchmarks/sidekiq_middleware.rb
|
|
270
280
|
```
|
|
271
281
|
|
|
272
282
|
Results depend on runtime, hardware, and payload. No performance comparison is claimed until repeatable measurements are published.
|
|
273
283
|
|
|
274
284
|
## Migration from Airbrake
|
|
275
285
|
|
|
276
|
-
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.
|
|
286
|
+
An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.6 does not claim API compatibility or automatic replacement.
|
|
277
287
|
|
|
278
288
|
## Local development
|
|
279
289
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://chronos.example/schemas/sidekiq-job-v1.schema.json",
|
|
4
|
+
"title": "Chronos Sidekiq job payload v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["kind", "class", "queue", "duration_ms", "status"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"kind": {"const": "sidekiq"},
|
|
9
|
+
"class": {"type": "string"},
|
|
10
|
+
"queue": {"type": "string"},
|
|
11
|
+
"jid": {"type": "string"},
|
|
12
|
+
"duration_ms": {"type": "number", "minimum": 0},
|
|
13
|
+
"queue_latency_ms": {"type": ["number", "null"], "minimum": 0},
|
|
14
|
+
"retry_count": {"type": "integer", "minimum": 0},
|
|
15
|
+
"arguments": {"type": "array", "maxItems": 20},
|
|
16
|
+
"arguments_truncated": {"type": "boolean"},
|
|
17
|
+
"tags": {"type": "array", "maxItems": 20},
|
|
18
|
+
"status": {"enum": ["completed", "failed"]},
|
|
19
|
+
"error_class": {"type": "string"}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ADR-014 — Sidekiq context in the job envelope
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted for version 0.6.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Sidekiq clients and servers usually run in different processes. Chronos needs correlation without changing the worker's public arguments, opening per-job resources, or allowing nested Active Job and Sidekiq hooks to duplicate the same exception.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Use Sidekiq 4/5 public client and server middleware. Store a versioned `chronos` object beside `args`, containing only enqueue time, trace ID, and request ID. Never append metadata to `args`. On the server, establish a job execution context with a shared exception-deduplication map, emit bounded job telemetry, notify failures through `notify_once`, and re-raise the original exception. Do not install a global Sidekiq error handler.
|
|
14
|
+
|
|
15
|
+
Limit job arguments before the common sanitizer and serializer: 20 top-level arguments, 20 items per collection, depth four, and 512 bytes per string. Sanitization remains mandatory before queueing, retry, backlog, or transport.
|
|
16
|
+
|
|
17
|
+
## Alternatives
|
|
18
|
+
|
|
19
|
+
Appending a context argument was rejected because it changes worker arity and application-visible data. A process-global context was rejected because concurrent jobs would leak correlation. A global error handler plus server middleware was rejected because it creates duplicate capture paths. Per-job delivery threads or Redis connections were rejected because lifecycle and resource ownership belong to Sidekiq and the existing bounded agent.
|
|
20
|
+
|
|
21
|
+
## Positive consequences
|
|
22
|
+
|
|
23
|
+
Worker signatures remain compatible, cross-process correlation is bounded, nested integrations share deduplication, and Sidekiq retains retry semantics.
|
|
24
|
+
|
|
25
|
+
## Negative consequences
|
|
26
|
+
|
|
27
|
+
The Sidekiq envelope grows slightly, only two context identifiers propagate, and automatic argument collection still requires application privacy review.
|
data/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Chronos Ruby 0.
|
|
3
|
+
Chronos Ruby 0.6 uses hexagonal boundaries so the legacy core remains independent of frameworks and delivery infrastructure.
|
|
4
4
|
|
|
5
5
|
```mermaid
|
|
6
6
|
flowchart TB
|
|
@@ -16,6 +16,8 @@ flowchart TB
|
|
|
16
16
|
Context --> ThreadLocal[Adapters / ThreadLocalContextStore]
|
|
17
17
|
Rails[Chronos Rails / Railtie and subscribers] --> Facade
|
|
18
18
|
Rails --> Notifications[ActiveSupport Notifications]
|
|
19
|
+
Sidekiq[Integrations / Sidekiq client and server middleware] --> Facade
|
|
20
|
+
Sidekiq --> Envelope[Versioned job-envelope context]
|
|
19
21
|
Application --> Telemetry[Core / TelemetryEvent]
|
|
20
22
|
```
|
|
21
23
|
|
|
@@ -28,6 +30,7 @@ flowchart TB
|
|
|
28
30
|
- Internal contains private concurrency and diagnostic mechanisms.
|
|
29
31
|
- Integrations contain optional framework entry points and never enter the domain boundary.
|
|
30
32
|
- `Chronos::Rails` contains the optional Railtie, installer, generator, and public-notification adapters.
|
|
33
|
+
- `Chronos::Integrations::Sidekiq` contains optional Sidekiq 4/5 middleware and bounded job normalization.
|
|
31
34
|
|
|
32
35
|
The `Chronos` module is a thin facade. Rails, Rack, ActiveSupport, Sidekiq, and job libraries must not be required by the core.
|
|
33
36
|
|
|
@@ -41,6 +44,8 @@ An exception becomes an immutable notice. `Sanitizer` removes sensitive values b
|
|
|
41
44
|
|
|
42
45
|
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
46
|
|
|
47
|
+
Sidekiq client middleware writes a versioned, allowlisted trace/request context beside the job's public arguments. Server middleware restores a job scope, limits arguments and tags, emits one job telemetry event, and routes a failure through the existing notice pipeline before re-raising it. Sidekiq retains queue, retry, thread, and connection lifecycle ownership.
|
|
48
|
+
|
|
44
49
|
## Failure policy
|
|
45
50
|
|
|
46
51
|
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
|
@@ -11,8 +11,14 @@ Chronos Ruby 0.x is the legacy line. Technical compatibility does not make an en
|
|
|
11
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
|
+
| Ruby | Sidekiq integration | Status | Evidence |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| 2.2.10–2.6 | Sidekiq 4 / 5 | Best effort | Middleware unit/integration contracts; dedicated real-gem matrix pending |
|
|
17
|
+
|
|
14
18
|
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
19
|
|
|
20
|
+
Version `0.6.0.pre.1` uses the public Sidekiq 4/5 middleware signatures and remains `Best effort` until dedicated jobs exercise both real gem versions across their valid Ruby combinations.
|
|
21
|
+
|
|
16
22
|
Status meanings:
|
|
17
23
|
|
|
18
24
|
- Supported: the complete required compatibility gate passes.
|
data/docs/data-collected.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Data collected
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.6 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
|
|---|---|---|
|
|
@@ -27,9 +27,14 @@ Version 0.5 emits exception, request, query, job, and cache events. All fields p
|
|
|
27
27
|
| SQL operation name, cached flag, duration | Collected; SQL text omitted | `sql.active_record` |
|
|
28
28
|
| Mailer/action and duration | Collected; message content omitted | `deliver.action_mailer` |
|
|
29
29
|
| Active Job class, queue, duration | Collected when available; arguments omitted | `perform.active_job` |
|
|
30
|
+
| Sidekiq class, queue, JID, retry count, duration, status, error class | Collected with optional middleware | Sidekiq job envelope and local clock |
|
|
31
|
+
| Sidekiq queue latency | Collected when enqueue time is available | Sidekiq or Chronos envelope timestamp |
|
|
32
|
+
| Sidekiq arguments | Collected, sanitized, and bounded | First 20 job arguments; collections/depth/strings limited |
|
|
33
|
+
| Sidekiq tags | Collected and bounded | Job payload or public worker options |
|
|
34
|
+
| Sidekiq trace/request IDs | Propagated when present; trace generated otherwise | Chronos job-envelope metadata |
|
|
30
35
|
| Cache operation, store, hit flag, duration | Collected; key/value omitted | ActiveSupport cache notifications |
|
|
31
36
|
|
|
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,
|
|
37
|
+
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, or installed gems. Sidekiq JIDs and bounded arguments are the explicit version 0.6 exception to the earlier job-ID/argument exclusion.
|
|
33
38
|
|
|
34
39
|
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.
|
|
35
40
|
|
|
@@ -57,4 +57,4 @@ The repository contains independent applications under `examples/rails-4.2` and
|
|
|
57
57
|
|
|
58
58
|
## Limits
|
|
59
59
|
|
|
60
|
-
Version 0.5 emits individual bounded timings. It does not aggregate APM metrics, capture SQL text, calculate query fingerprints
|
|
60
|
+
Version 0.5 emits individual bounded timings. It does not aggregate APM metrics, capture SQL text, or calculate query fingerprints. Sidekiq support is a separate optional integration beginning in version 0.6; full Active Job propagation remains a later 0.6 increment.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Sidekiq 4/5 legacy integration
|
|
2
|
+
|
|
3
|
+
Version `0.6.0.pre.1` starts the legacy jobs line with optional Sidekiq 4 and 5 client and server middleware. Load it after declaring Sidekiq:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
gem "sidekiq", "~> 5.0"
|
|
7
|
+
gem "chronos-ruby", "0.6.0.pre.1", :require => "chronos/sidekiq"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`chronos/sidekiq` installs middleware through the public `configure_client` and `configure_server` APIs. It does nothing when Sidekiq is unavailable, and the core gem never requires Sidekiq. Installation adds no Chronos thread or Redis/database connection per job; delivery continues through the agent's existing fixed worker pool.
|
|
11
|
+
|
|
12
|
+
The client middleware adds a top-level `chronos` metadata object to the Sidekiq envelope. It never changes the public `args` array. Only `trace_id` and `request_id` propagate, with schema version and enqueue time. The server scopes that context for the worker, measures duration and calculable queue latency, and emits one `job` event.
|
|
13
|
+
|
|
14
|
+
Collected Sidekiq fields are worker class, queue, JID, retry count, duration, queue latency, status, error class, bounded tags, and bounded arguments. At most 20 top-level arguments, 20 values per nested collection, four levels, and 512 bytes per string are traversed. Arguments then pass through the standard sensitive-key/content sanitizer before queueing. Limits reduce exposure and resource use but do not make unnecessary personal data safe to collect.
|
|
15
|
+
|
|
16
|
+
On failure, the server middleware records failed-job telemetry, invokes `notify_once`, and re-raises the identical exception so Sidekiq retains ownership of retries and failure handlers. A shared execution marker prevents a nested Active Job hook and the Sidekiq middleware from sending the same exception twice. Chronos does not install an additional global Sidekiq error handler.
|
|
17
|
+
|
|
18
|
+
Known limitations for this first 0.6 prerelease:
|
|
19
|
+
|
|
20
|
+
- the compatibility contract uses Sidekiq 4/5 public middleware signatures; dedicated real Sidekiq matrix jobs must pass before status becomes `Supported`;
|
|
21
|
+
- Active Job propagation, Resque, and Delayed Job are subsequent 0.6 increments;
|
|
22
|
+
- argument collection is automatic in this prerelease; applications should avoid placing secrets or unnecessary personal data in job arguments;
|
|
23
|
+
- context propagation is limited to trace and request identifiers.
|
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.6 makes no unverified speed claim.
|
|
4
4
|
|
|
5
5
|
Current controls:
|
|
6
6
|
|
|
@@ -19,6 +19,7 @@ Current controls:
|
|
|
19
19
|
- request context and breadcrumbs have fixed structural and byte limits;
|
|
20
20
|
- Rack middleware never consumes request or response bodies.
|
|
21
21
|
- Rails subscribers copy only small allowlisted field sets and never copy raw SQL or job arguments.
|
|
22
|
+
- Sidekiq middleware creates no per-job thread or connection and bounds arguments, collections, nesting, strings, and tags before telemetry capture.
|
|
22
23
|
|
|
23
24
|
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
|
|
|
@@ -76,3 +77,13 @@ ITERATIONS=100000 bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
|
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
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.
|
|
80
|
+
|
|
81
|
+
## Version 0.6 Sidekiq middleware benchmark
|
|
82
|
+
|
|
83
|
+
Run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
ITERATIONS=100000 bundle _1.17.3_ exec ruby benchmarks/sidekiq_middleware.rb
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The benchmark executes client context injection and successful server normalization with a no-op notifier. It includes bounded argument handling and clock reads, but excludes Sidekiq/Redis, serialization, queueing, and network delivery. Record a controlled result before making 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.6 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,14 +13,19 @@ Version 0.5 sanitizes exception and framework telemetry before JSON serializatio
|
|
|
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, raw SQL/binds, cache values, mail bodies,
|
|
16
|
+
| Request/response bodies, raw query strings, cookies, authorization headers, raw SQL/binds, cache values, mail bodies, environment variables | Never collected automatically |
|
|
17
|
+
| Sidekiq arguments | Collected automatically, limited before sanitization, then redacted by the common policy |
|
|
17
18
|
|
|
18
|
-
The retry backlog exists only in process memory, accepts only `SerializedEvent`, has a fixed capacity, and disappears on process exit. Version 0.
|
|
19
|
+
The retry backlog exists only in process memory, accepts only `SerializedEvent`, has a fixed capacity, and disappears on process exit. Version 0.6 does not persist telemetry to disk.
|
|
19
20
|
|
|
20
21
|
## Rails telemetry
|
|
21
22
|
|
|
22
23
|
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.
|
|
23
24
|
|
|
25
|
+
## Sidekiq jobs
|
|
26
|
+
|
|
27
|
+
The optional Sidekiq middleware is the only version 0.6 integration that automatically reads job arguments. It traverses at most 20 top-level arguments, 20 items per nested collection, four levels, and 512 bytes per string. These structural limits run before the common key and content sanitizer. Trace propagation contains only trace and request identifiers. Do not place credentials or unnecessary personal, health, or financial data in job arguments; configure application-specific blocklist keys and audit representative synthetic payloads before enabling production delivery.
|
|
28
|
+
|
|
24
29
|
## Rack context and breadcrumbs
|
|
25
30
|
|
|
26
31
|
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.
|
data/docs/troubleshooting.md
CHANGED
|
@@ -24,6 +24,14 @@ Require `chronos/rails` from the generated initializer and confirm it runs befor
|
|
|
24
24
|
|
|
25
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.
|
|
26
26
|
|
|
27
|
+
## Sidekiq telemetry is missing
|
|
28
|
+
|
|
29
|
+
Require `chronos/sidekiq` after Sidekiq is available and configure Chronos before jobs run. The entry point uses Sidekiq's public client/server middleware configuration and remains optional. Requiring only `chronos` or `chronos/rails` does not load Sidekiq. A job already enqueued before client middleware installation may lack propagated trace context, but server timing and failure capture can still run.
|
|
30
|
+
|
|
31
|
+
## A failed Sidekiq job appears twice
|
|
32
|
+
|
|
33
|
+
The bundled middleware does not install a global error handler and uses `notify_once` inside a shared job scope. Check for application-installed Chronos calls or third-party global handlers outside that scope. Keep a single bundled server middleware entry in the Sidekiq chain.
|
|
34
|
+
|
|
27
35
|
## Context appears missing
|
|
28
36
|
|
|
29
37
|
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.
|
data/lib/chronos/agent.rb
CHANGED
|
@@ -67,6 +67,21 @@ module Chronos
|
|
|
67
67
|
@telemetry.call(event_type, payload, telemetry_context(context))
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# Returns the correlation subset safe for an integration-owned process boundary.
|
|
71
|
+
def propagation_context
|
|
72
|
+
current = context_hash(@context_store.get)
|
|
73
|
+
nested = context_hash(current[:context] || current["context"])
|
|
74
|
+
request = context_hash(nested["request"] || nested[:request])
|
|
75
|
+
values = {
|
|
76
|
+
"trace_id" => nested["trace_id"] || nested[:trace_id],
|
|
77
|
+
"request_id" => nested["request_id"] || nested[:request_id] ||
|
|
78
|
+
request["request_id"] || request[:request_id]
|
|
79
|
+
}
|
|
80
|
+
values.delete_if { |_key, value| value.to_s.empty? }
|
|
81
|
+
rescue StandardError
|
|
82
|
+
{}
|
|
83
|
+
end
|
|
84
|
+
|
|
70
85
|
def notify_once(exception, context = {})
|
|
71
86
|
execution = @context_store.get
|
|
72
87
|
captured = execution[:__chronos_captured_exceptions] || {}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Integrations
|
|
3
|
+
# Builds bounded job metadata before the shared privacy serializer runs.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Limit job arguments, tags, strings, containers, and nesting.
|
|
6
|
+
# @motivation Worker payloads are application-controlled and may be very large.
|
|
7
|
+
# @limits It limits values but deliberately leaves redaction to Core::Sanitizer.
|
|
8
|
+
# @collaborators Sidekiq middleware and the Chronos telemetry pipeline.
|
|
9
|
+
# @thread_safety Instances keep no mutable state and may be shared.
|
|
10
|
+
# @compatibility Ruby 2.2.10 through Ruby 2.6.
|
|
11
|
+
# @example
|
|
12
|
+
# JobPayload.new.arguments(["account-1"])
|
|
13
|
+
# @errors Unreadable values become bounded class-name placeholders.
|
|
14
|
+
# @performance Traversal is capped by argument, collection, depth, and string limits.
|
|
15
|
+
class JobPayload
|
|
16
|
+
MAX_ARGUMENTS = 20
|
|
17
|
+
MAX_COLLECTION_ITEMS = 20
|
|
18
|
+
MAX_DEPTH = 4
|
|
19
|
+
MAX_STRING_BYTES = 512
|
|
20
|
+
|
|
21
|
+
def arguments(values)
|
|
22
|
+
source = values.is_a?(Array) ? values : []
|
|
23
|
+
[source.first(MAX_ARGUMENTS).map { |value| limit(value, 0) }, source.length > MAX_ARGUMENTS]
|
|
24
|
+
rescue StandardError
|
|
25
|
+
[[], true]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tags(values)
|
|
29
|
+
Array(values).first(MAX_COLLECTION_ITEMS).map { |value| limit_string(value.to_s) }
|
|
30
|
+
rescue StandardError
|
|
31
|
+
[]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def limit(value, depth)
|
|
37
|
+
return "[TRUNCATED]" if depth >= MAX_DEPTH
|
|
38
|
+
|
|
39
|
+
case value
|
|
40
|
+
when Hash
|
|
41
|
+
limit_hash(value, depth)
|
|
42
|
+
when Array
|
|
43
|
+
value.first(MAX_COLLECTION_ITEMS).map { |child| limit(child, depth + 1) }
|
|
44
|
+
when String
|
|
45
|
+
limit_string(value)
|
|
46
|
+
when NilClass, TrueClass, FalseClass, Numeric
|
|
47
|
+
value
|
|
48
|
+
else
|
|
49
|
+
limit_string(value.to_s)
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError
|
|
52
|
+
"[UNREADABLE]"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def limit_hash(value, depth)
|
|
56
|
+
result = {}
|
|
57
|
+
value.to_a.first(MAX_COLLECTION_ITEMS).each do |key, child|
|
|
58
|
+
result[limit_string(key.to_s)] = limit(child, depth + 1)
|
|
59
|
+
end
|
|
60
|
+
result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def limit_string(value)
|
|
64
|
+
return value if value.bytesize <= MAX_STRING_BYTES
|
|
65
|
+
|
|
66
|
+
value.byteslice(0, MAX_STRING_BYTES) + "[TRUNCATED]"
|
|
67
|
+
rescue StandardError
|
|
68
|
+
"[UNREADABLE]"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
require "chronos/integrations/job_payload"
|
|
3
|
+
|
|
4
|
+
module Chronos
|
|
5
|
+
module Integrations
|
|
6
|
+
# Optional Sidekiq 4/5 middleware integration for the legacy Chronos line.
|
|
7
|
+
#
|
|
8
|
+
# @responsibility Install client/server middleware and namespace worker telemetry.
|
|
9
|
+
# @motivation Sidekiq jobs need process-boundary context and failure capture.
|
|
10
|
+
# @limits It does not own Sidekiq lifecycle, Redis connections, retries, or threads.
|
|
11
|
+
# @collaborators Sidekiq public middleware configuration and Chronos facade.
|
|
12
|
+
# @thread_safety Installation is mutex-protected; middleware instances are stateless.
|
|
13
|
+
# @compatibility Sidekiq 4.x and 5.x; Ruby 2.2.10 through Ruby 2.6.
|
|
14
|
+
# @example
|
|
15
|
+
# Chronos::Integrations::Sidekiq.install
|
|
16
|
+
# @errors Missing Sidekiq or configuration failures return false.
|
|
17
|
+
# @performance Adds bounded payload normalization and two clock reads per performed job.
|
|
18
|
+
module Sidekiq
|
|
19
|
+
CONTEXT_KEY = "chronos".freeze
|
|
20
|
+
CONTEXT_SCHEMA_VERSION = "1.0".freeze
|
|
21
|
+
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
@installed = false
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def install(sidekiq = nil, notifier = Chronos)
|
|
27
|
+
library = sidekiq || (::Sidekiq if defined?(::Sidekiq))
|
|
28
|
+
return false unless library
|
|
29
|
+
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
return false if @installed
|
|
32
|
+
|
|
33
|
+
configure_client(library, notifier)
|
|
34
|
+
configure_server(library, notifier)
|
|
35
|
+
@installed = true
|
|
36
|
+
end
|
|
37
|
+
true
|
|
38
|
+
rescue StandardError
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def configure_client(library, notifier)
|
|
45
|
+
return unless library.respond_to?(:configure_client)
|
|
46
|
+
|
|
47
|
+
library.configure_client do |config|
|
|
48
|
+
next unless config.respond_to?(:client_middleware)
|
|
49
|
+
|
|
50
|
+
config.client_middleware { |chain| chain.add(ClientMiddleware, :notifier => notifier) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def configure_server(library, notifier)
|
|
55
|
+
return unless library.respond_to?(:configure_server)
|
|
56
|
+
|
|
57
|
+
library.configure_server do |config|
|
|
58
|
+
if config.respond_to?(:client_middleware)
|
|
59
|
+
config.client_middleware { |chain| chain.add(ClientMiddleware, :notifier => notifier) }
|
|
60
|
+
end
|
|
61
|
+
if config.respond_to?(:server_middleware)
|
|
62
|
+
config.server_middleware { |chain| chain.add(ServerMiddleware, :notifier => notifier) }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Adds a small allowlisted Chronos context to the Sidekiq job envelope.
|
|
69
|
+
#
|
|
70
|
+
# @responsibility Propagate trace/request identifiers without modifying `args`.
|
|
71
|
+
# @motivation Client and server usually execute in different processes.
|
|
72
|
+
# @limits It does not capture arguments, enqueue errors, or application context fields.
|
|
73
|
+
# @collaborators Chronos propagation context and Sidekiq client middleware chain.
|
|
74
|
+
# @thread_safety Calls allocate their own hashes and may execute concurrently.
|
|
75
|
+
# @compatibility Sidekiq 4.x/5.x client middleware signature.
|
|
76
|
+
# @example
|
|
77
|
+
# ClientMiddleware.new.call(MyWorker, job, "default") { push(job) }
|
|
78
|
+
# @errors Context failures are contained and the enqueue chain still runs.
|
|
79
|
+
# @performance Adds one bounded hash to the job payload; opens no connection or thread.
|
|
80
|
+
class ClientMiddleware
|
|
81
|
+
def initialize(options = {})
|
|
82
|
+
@notifier = options[:notifier] || Chronos
|
|
83
|
+
@clock = options[:clock] || proc { Time.now.to_f }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call(_worker_class, job, _queue, _redis_pool = nil)
|
|
87
|
+
add_context(job)
|
|
88
|
+
yield
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def add_context(job)
|
|
94
|
+
return unless job.is_a?(Hash)
|
|
95
|
+
|
|
96
|
+
context = propagation_context
|
|
97
|
+
context["trace_id"] = SecureRandom.uuid if context["trace_id"].to_s.empty?
|
|
98
|
+
job[CONTEXT_KEY] = {
|
|
99
|
+
"schema_version" => CONTEXT_SCHEMA_VERSION,
|
|
100
|
+
"enqueued_at" => @clock.call.to_f,
|
|
101
|
+
"context" => context
|
|
102
|
+
}
|
|
103
|
+
rescue StandardError
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def propagation_context
|
|
108
|
+
return {} unless @notifier.respond_to?(:propagation_context)
|
|
109
|
+
|
|
110
|
+
source = @notifier.propagation_context
|
|
111
|
+
return {} unless source.is_a?(Hash)
|
|
112
|
+
|
|
113
|
+
%w(trace_id request_id).each_with_object({}) do |key, result|
|
|
114
|
+
value = source[key] || source[key.to_sym]
|
|
115
|
+
result[key] = value.to_s unless value.to_s.empty?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Captures Sidekiq execution timing and failures around the worker call.
|
|
121
|
+
#
|
|
122
|
+
# @responsibility Scope propagated context, emit one job event, notify failures once, and re-raise.
|
|
123
|
+
# @motivation Worker failures otherwise lose queue metadata and request correlation.
|
|
124
|
+
# @limits It does not change retry behavior, acknowledge jobs, or install global error handlers.
|
|
125
|
+
# @collaborators Chronos facade, JobPayload, and Sidekiq server middleware chain.
|
|
126
|
+
# @thread_safety Shared instances keep only immutable collaborators.
|
|
127
|
+
# @compatibility Sidekiq 4.x/5.x server middleware signature.
|
|
128
|
+
# @example
|
|
129
|
+
# ServerMiddleware.new.call(worker, job, "default") { worker.perform }
|
|
130
|
+
# @errors The original worker exception is always re-raised after contained notification.
|
|
131
|
+
# @performance No per-job thread or connection; normalization has strict collection limits.
|
|
132
|
+
class ServerMiddleware
|
|
133
|
+
def initialize(options = {})
|
|
134
|
+
@notifier = options[:notifier] || Chronos
|
|
135
|
+
@clock = options[:clock] || proc { monotonic_time }
|
|
136
|
+
@wall_clock = options[:wall_clock] || proc { Time.now.to_f }
|
|
137
|
+
@limiter = options[:limiter] || JobPayload.new
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def call(worker, job, queue)
|
|
141
|
+
started_at = @clock.call
|
|
142
|
+
payload = base_payload(worker, job, queue)
|
|
143
|
+
context = execution_context(job, payload)
|
|
144
|
+
@notifier.with_context(context) do
|
|
145
|
+
begin
|
|
146
|
+
result = yield
|
|
147
|
+
finish(payload, started_at, "completed")
|
|
148
|
+
result
|
|
149
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
150
|
+
finish(payload, started_at, "failed", error)
|
|
151
|
+
notify_failure(error, payload)
|
|
152
|
+
raise
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def base_payload(worker, job, queue)
|
|
160
|
+
source = job.is_a?(Hash) ? job : {}
|
|
161
|
+
arguments, truncated = @limiter.arguments(source["args"] || source[:args])
|
|
162
|
+
payload = {
|
|
163
|
+
"kind" => "sidekiq", "class" => worker_class(worker, source),
|
|
164
|
+
"queue" => (source["queue"] || source[:queue] || queue).to_s,
|
|
165
|
+
"jid" => (source["jid"] || source[:jid]).to_s,
|
|
166
|
+
"retry_count" => retry_count(source), "arguments" => arguments,
|
|
167
|
+
"arguments_truncated" => truncated, "tags" => tags(worker, source),
|
|
168
|
+
"queue_latency_ms" => queue_latency(source)
|
|
169
|
+
}
|
|
170
|
+
payload
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def execution_context(job, payload)
|
|
174
|
+
metadata = job.is_a?(Hash) ? (job[CONTEXT_KEY] || job[CONTEXT_KEY.to_sym]) : nil
|
|
175
|
+
propagated = metadata.is_a?(Hash) ? (metadata["context"] || metadata[:context]) : nil
|
|
176
|
+
propagated = {} unless propagated.is_a?(Hash)
|
|
177
|
+
{
|
|
178
|
+
:context => propagated.merge("job" => job_context(payload)),
|
|
179
|
+
:__chronos_captured_exceptions => {}
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def finish(payload, started_at, status, error = nil)
|
|
184
|
+
payload["duration_ms"] = [elapsed_ms(started_at), 0.0].max
|
|
185
|
+
payload["status"] = status
|
|
186
|
+
payload["error_class"] = error.class.name.to_s if error
|
|
187
|
+
@notifier.record_event("job", payload)
|
|
188
|
+
rescue StandardError
|
|
189
|
+
false
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def notify_failure(error, payload)
|
|
193
|
+
context = {:context => {"job" => job_context(payload)},
|
|
194
|
+
:parameters => {"arguments" => payload["arguments"]}, :tags => payload["tags"]}
|
|
195
|
+
if @notifier.respond_to?(:notify_once)
|
|
196
|
+
@notifier.notify_once(error, context)
|
|
197
|
+
else
|
|
198
|
+
@notifier.notify(error, context)
|
|
199
|
+
end
|
|
200
|
+
rescue StandardError
|
|
201
|
+
false
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def job_context(payload)
|
|
205
|
+
%w(kind class queue jid retry_count queue_latency_ms duration_ms status).each_with_object({}) do |key, result|
|
|
206
|
+
result[key] = payload[key] unless payload[key].nil?
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def worker_class(worker, job)
|
|
211
|
+
explicit = job["wrapped"] || job[:wrapped] || job["class"] || job[:class]
|
|
212
|
+
explicit = worker.class.name if explicit.to_s.empty? && worker
|
|
213
|
+
explicit.to_s
|
|
214
|
+
rescue StandardError
|
|
215
|
+
""
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def retry_count(job)
|
|
219
|
+
value = job["retry_count"] || job[:retry_count]
|
|
220
|
+
value.to_i < 0 ? 0 : value.to_i
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def tags(worker, job)
|
|
224
|
+
values = job["tags"] || job[:tags]
|
|
225
|
+
if values.nil? && worker
|
|
226
|
+
owner = worker.is_a?(Class) ? worker : worker.class
|
|
227
|
+
options = owner.get_sidekiq_options if owner.respond_to?(:get_sidekiq_options)
|
|
228
|
+
values = options["tags"] || options[:tags] if options.is_a?(Hash)
|
|
229
|
+
end
|
|
230
|
+
@limiter.tags(values)
|
|
231
|
+
rescue StandardError
|
|
232
|
+
[]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def queue_latency(job)
|
|
236
|
+
metadata = job[CONTEXT_KEY] || job[CONTEXT_KEY.to_sym]
|
|
237
|
+
enqueued_at = job["enqueued_at"] || job[:enqueued_at]
|
|
238
|
+
enqueued_at ||= metadata["enqueued_at"] || metadata[:enqueued_at] if metadata.is_a?(Hash)
|
|
239
|
+
return nil unless enqueued_at
|
|
240
|
+
|
|
241
|
+
[((@wall_clock.call.to_f - enqueued_at.to_f) * 1000.0).round(3), 0.0].max
|
|
242
|
+
rescue StandardError
|
|
243
|
+
nil
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def elapsed_ms(started_at)
|
|
247
|
+
((@clock.call - started_at) * 1000.0).round(3)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def monotonic_time
|
|
251
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
252
|
+
rescue StandardError
|
|
253
|
+
Time.now.to_f
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -24,6 +24,7 @@ module Chronos
|
|
|
24
24
|
@mutex = Mutex.new
|
|
25
25
|
@threads = []
|
|
26
26
|
@active = 0
|
|
27
|
+
@pending = 0
|
|
27
28
|
@closed = false
|
|
28
29
|
@pid = Process.pid
|
|
29
30
|
end
|
|
@@ -32,7 +33,9 @@ module Chronos
|
|
|
32
33
|
prepare_after_fork
|
|
33
34
|
return false if closed?
|
|
34
35
|
|
|
36
|
+
increment_pending
|
|
35
37
|
accepted = @queue.push(event)
|
|
38
|
+
decrement_pending unless accepted
|
|
36
39
|
ensure_started if accepted
|
|
37
40
|
accepted
|
|
38
41
|
end
|
|
@@ -42,7 +45,7 @@ module Chronos
|
|
|
42
45
|
ensure_started unless @queue.empty?
|
|
43
46
|
deadline = monotonic_time + timeout.to_f
|
|
44
47
|
loop do
|
|
45
|
-
return true if
|
|
48
|
+
return true if delivery_complete?
|
|
46
49
|
return false if monotonic_time >= deadline
|
|
47
50
|
sleep(POLL_INTERVAL)
|
|
48
51
|
end
|
|
@@ -91,6 +94,7 @@ module Chronos
|
|
|
91
94
|
@logger.warn("Chronos worker contained #{error.class}")
|
|
92
95
|
ensure
|
|
93
96
|
decrement_active
|
|
97
|
+
decrement_pending
|
|
94
98
|
end
|
|
95
99
|
end
|
|
96
100
|
rescue StandardError => error
|
|
@@ -104,13 +108,14 @@ module Chronos
|
|
|
104
108
|
@pid = Process.pid
|
|
105
109
|
@threads = []
|
|
106
110
|
@active = 0
|
|
111
|
+
@pending = @queue.size
|
|
107
112
|
end
|
|
108
113
|
end
|
|
109
114
|
|
|
110
115
|
def flush_without_reopening(timeout)
|
|
111
116
|
deadline = monotonic_time + timeout.to_f
|
|
112
117
|
loop do
|
|
113
|
-
return true if
|
|
118
|
+
return true if delivery_complete?
|
|
114
119
|
return false if monotonic_time >= deadline
|
|
115
120
|
sleep(POLL_INTERVAL)
|
|
116
121
|
end
|
|
@@ -138,6 +143,18 @@ module Chronos
|
|
|
138
143
|
@mutex.synchronize { @active }
|
|
139
144
|
end
|
|
140
145
|
|
|
146
|
+
def increment_pending
|
|
147
|
+
@mutex.synchronize { @pending += 1 }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def decrement_pending
|
|
151
|
+
@mutex.synchronize { @pending -= 1 }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delivery_complete?
|
|
155
|
+
@mutex.synchronize { @pending.zero? && @active.zero? }
|
|
156
|
+
end
|
|
157
|
+
|
|
141
158
|
def closed?
|
|
142
159
|
@mutex.synchronize { @closed }
|
|
143
160
|
end
|
data/lib/chronos/version.rb
CHANGED
data/lib/chronos.rb
CHANGED
|
@@ -104,6 +104,14 @@ module Chronos
|
|
|
104
104
|
false
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
# Returns only trace/request identifiers for optional process-boundary adapters.
|
|
108
|
+
def propagation_context
|
|
109
|
+
agent = current_agent
|
|
110
|
+
agent ? agent.propagation_context : {}
|
|
111
|
+
rescue StandardError
|
|
112
|
+
{}
|
|
113
|
+
end
|
|
114
|
+
|
|
107
115
|
def notify_once(exception, context = {})
|
|
108
116
|
agent = current_agent
|
|
109
117
|
agent ? agent.notify_once(exception, context) : false
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chronos-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0.pre.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antonio Jefferson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -88,6 +88,7 @@ files:
|
|
|
88
88
|
- SECURITY.md
|
|
89
89
|
- contracts/event-v1.schema.json
|
|
90
90
|
- contracts/rack-context-v1.schema.json
|
|
91
|
+
- contracts/sidekiq-job-v1.schema.json
|
|
91
92
|
- docs/adr/ADR-001-core-and-integrations.md
|
|
92
93
|
- docs/adr/ADR-002-version-lines.md
|
|
93
94
|
- docs/adr/ADR-003-net-http-legacy-transport.md
|
|
@@ -97,6 +98,7 @@ files:
|
|
|
97
98
|
- docs/adr/ADR-011-bounded-resilience-and-remote-control.md
|
|
98
99
|
- docs/adr/ADR-012-rack-context-isolation.md
|
|
99
100
|
- docs/adr/ADR-013-legacy-rails-notifications.md
|
|
101
|
+
- docs/adr/ADR-014-sidekiq-envelope-context.md
|
|
100
102
|
- docs/architecture.md
|
|
101
103
|
- docs/compatibility.md
|
|
102
104
|
- docs/configuration.md
|
|
@@ -111,6 +113,7 @@ files:
|
|
|
111
113
|
- docs/modules/retry-backlog.md
|
|
112
114
|
- docs/modules/sanitization.md
|
|
113
115
|
- docs/modules/serialization.md
|
|
116
|
+
- docs/modules/sidekiq-legacy.md
|
|
114
117
|
- docs/modules/telemetry-events.md
|
|
115
118
|
- docs/modules/transport.md
|
|
116
119
|
- docs/performance.md
|
|
@@ -145,8 +148,10 @@ files:
|
|
|
145
148
|
- lib/chronos/core/telemetry_event.rb
|
|
146
149
|
- lib/chronos/errors.rb
|
|
147
150
|
- lib/chronos/integrations.rb
|
|
151
|
+
- lib/chronos/integrations/job_payload.rb
|
|
148
152
|
- lib/chronos/integrations/rack.rb
|
|
149
153
|
- lib/chronos/integrations/rack/middleware.rb
|
|
154
|
+
- lib/chronos/integrations/sidekiq.rb
|
|
150
155
|
- lib/chronos/internal.rb
|
|
151
156
|
- lib/chronos/internal/bounded_queue.rb
|
|
152
157
|
- lib/chronos/internal/memory_backlog.rb
|
|
@@ -161,6 +166,7 @@ files:
|
|
|
161
166
|
- lib/chronos/rails/railtie.rb
|
|
162
167
|
- lib/chronos/ruby.rb
|
|
163
168
|
- lib/chronos/ruby/version.rb
|
|
169
|
+
- lib/chronos/sidekiq.rb
|
|
164
170
|
- lib/chronos/version.rb
|
|
165
171
|
- lib/generators/chronos/install/install_generator.rb
|
|
166
172
|
- lib/generators/chronos/install/templates/chronos.rb
|