chronos-ruby 0.6.0.pre.1 → 0.8.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/README.md +53 -10
  4. data/contracts/apm-batch-v1.schema.json +35 -0
  5. data/contracts/dependencies-v1.schema.json +38 -0
  6. data/contracts/event-v1.schema.json +1 -1
  7. data/docs/adr/ADR-015-bounded-apm-aggregation.md +27 -0
  8. data/docs/adr/ADR-016-explicit-observability-integrations.md +25 -0
  9. data/docs/architecture.md +11 -2
  10. data/docs/compatibility.md +4 -0
  11. data/docs/configuration.md +28 -1
  12. data/docs/data-collected.md +17 -3
  13. data/docs/examples/plain-ruby.md +7 -1
  14. data/docs/modules/apm-aggregation.md +57 -0
  15. data/docs/modules/cache-observability.md +16 -0
  16. data/docs/modules/configuration.md +4 -0
  17. data/docs/modules/dependencies.md +18 -0
  18. data/docs/modules/external-http.md +27 -0
  19. data/docs/modules/rails-legacy.md +2 -2
  20. data/docs/modules/sidekiq-legacy.md +2 -2
  21. data/docs/modules/telemetry-events.md +2 -2
  22. data/docs/performance.md +29 -1
  23. data/docs/privacy-lgpd.md +16 -2
  24. data/docs/troubleshooting.md +25 -1
  25. data/lib/chronos/agent.rb +49 -0
  26. data/lib/chronos/application/apm_aggregator.rb +270 -0
  27. data/lib/chronos/application/apm_error_classifier.rb +27 -0
  28. data/lib/chronos/application/capture_telemetry.rb +45 -5
  29. data/lib/chronos/application/delivery_pipeline.rb +7 -0
  30. data/lib/chronos/application/dependency_reporter.rb +129 -0
  31. data/lib/chronos/application/remote_configuration.rb +8 -1
  32. data/lib/chronos/configuration/apm_validation.rb +76 -0
  33. data/lib/chronos/configuration.rb +35 -2
  34. data/lib/chronos/core/cache_normalizer.rb +99 -0
  35. data/lib/chronos/core/metric_aggregate.rb +113 -0
  36. data/lib/chronos/core/sql_normalizer.rb +114 -0
  37. data/lib/chronos/core/telemetry_event.rb +1 -1
  38. data/lib/chronos/integrations/net_http.rb +165 -0
  39. data/lib/chronos/integrations/rack/middleware.rb +19 -1
  40. data/lib/chronos/integrations/sidekiq.rb +2 -0
  41. data/lib/chronos/net_http.rb +4 -0
  42. data/lib/chronos/observability_facade.rb +43 -0
  43. data/lib/chronos/rails/notifications_subscriber.rb +42 -9
  44. data/lib/chronos/version.rb +1 -1
  45. data/lib/chronos.rb +23 -0
  46. metadata +19 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ad7bc09dd58b0476b65687d0b7c695bdc5e82c898f882fc82ed2e87eb352b79
4
- data.tar.gz: 527383d76a4432bc16f48d2a476d7c2398d66f5b891bbe666d1e0ceba9dc9959
3
+ metadata.gz: 44c7cad424497d7998727de5a916594ecc3ce9dfda7580376fe4a754edd9096e
4
+ data.tar.gz: 3f7a64cbf5b5879e107d7102c1f72cdd2ac4c941e1640a05b6210db79cd948f9
5
5
  SHA512:
6
- metadata.gz: 24bf5ed37de64c34a82fd0b98f3e1323cac14c2392fa0d6a01dbcd24d48a69a52100a6127a79b2567314bcf2c464b354fee226ab19bba08ea384bfb3ec8a12f9
7
- data.tar.gz: 1696953512893c9d3eecc23d361b35c5434df7dda25e33a1fe6321b5c25ae4d9e03a2a843bd76e9c327130be2cba2a6c7a9766d70a6f068007b24959aa3cfdc0
6
+ metadata.gz: f62fa57bfcfec70f7172afc4c939d905aa9c7d9215960b73fa0c54406568b43b93e41b02ba22eb13b381243e06cf61a087544db00ed97ea4ce5805f84e9901e7
7
+ data.tar.gz: 3a66ec4e2a85f7f2e79868523fe63a493aeb598aff810dfd21b84a25dd0505e21ee66cac35a7113927a9b4b5a5c5ea2f41d25b89b452b38088c0ef3570f876bf
data/CHANGELOG.md CHANGED
@@ -12,6 +12,54 @@ All notable changes are documented here. The project follows Semantic Versioning
12
12
  - legacy CI now resolves Bundler 1.17.3 through `Gem.bin_path` on RubyGems versions that do not support the `_version_` executable selector.
13
13
  - documentation verification now reads source and Markdown files explicitly as UTF-8 on legacy container locales.
14
14
 
15
+ ## [0.8.0.pre.1] - 2026-07-20
16
+
17
+ ### Added
18
+
19
+ - optional per-instance `Net::HTTP` instrumentation with sanitized host, method, status, duration, timeout and connection-error classification;
20
+ - outbound trace/request header propagation without collecting URLs, Authorization, request bodies, response bodies, or error messages;
21
+ - external HTTP metric aggregation and traced-request `external_http` breakdown;
22
+ - cache operation, duration, hit/miss, backend, namespace, and opt-in project-scoped SHA-256 key identity;
23
+ - bounded dependency inventory containing loaded gem versions, Ruby, optional Rails/web server/database/Sidekiq detection, and configured release;
24
+ - version 0.8 configuration, contracts, tests, executable example, benchmark, module documentation, and ADR-016.
25
+
26
+ ### Changed
27
+
28
+ - dependency inventory is queued at most once per configured agent instead of being attached to every exception;
29
+ - cache notification fields now use bounded backend/namespace/outcome names and omit raw keys by default;
30
+ - version advanced to `0.8.0.pre.1`.
31
+
32
+ ### Known limitations
33
+
34
+ - outbound HTTP instrumentation must be enabled and installed on each selected `Net::HTTP` instance;
35
+ - Faraday, HTTP.rb, Excon, and RestClient adapters are not included;
36
+ - cache key hashing is opt-in and low-entropy keys may remain guessable;
37
+ - dependency detection uses already loaded gems and does not open a database connection or inspect lockfiles.
38
+
39
+ ## [0.7.0.pre.1] - 2026-07-20
40
+
41
+ ### Added
42
+
43
+ - bounded local request, SQL, and job aggregation with count, error rate, duration statistics, histograms, status codes, and component breakdown;
44
+ - `metric_batch` v1 payloads containing at most 50 sanitized metric groups;
45
+ - bounded SQL normalization, fingerprinting, adapter/operation/table/name/cache/role/shard dimensions, and sampled slow-query source;
46
+ - heuristic slow-query, repeated-query, possible N+1, long-transaction, connection-error, and deadlock signals;
47
+ - generic Rack request metrics with Rails/Rack deduplication;
48
+ - APM configuration, diagnostics, contracts, tests, example, benchmark, module documentation, and ADR-015.
49
+
50
+ ### Changed
51
+
52
+ - request, query, and job observations aggregate by default instead of producing one delivery event each;
53
+ - `Chronos.flush` and `Chronos.close` drain APM aggregates before delivery shutdown;
54
+ - version advanced to `0.7.0.pre.1`.
55
+
56
+ ### Known limitations
57
+
58
+ - local signals are heuristic and require server-side confirmation;
59
+ - incomplete trace trackers are cleared when aggregates drain;
60
+ - normalized SQL is defensive rather than a complete dialect parser;
61
+ - external HTTP breakdown remains version 0.8 scope.
62
+
15
63
  ## [0.6.0.pre.1] - 2026-07-20
16
64
 
17
65
  ### 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.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.
3
+ Chronos Ruby is the framework-independent client for sending Ruby application errors and bounded telemetry to Chronos. Version 0.8 adds optional per-instance outbound `Net::HTTP` timing, privacy-safe cache identity, and one bounded dependency inventory per agent.
4
4
 
5
5
  ## What the gem collects
6
6
 
7
- For each exception, version 0.6 can collect:
7
+ Version 0.8 can collect:
8
8
 
9
9
  - exception class, message, structured backtrace, and chained causes;
10
10
  - timestamp, severity, tags, and an optional fingerprint;
@@ -18,7 +18,7 @@ See [Data collected](docs/data-collected.md) for the complete field table.
18
18
 
19
19
  ## What is not collected by default
20
20
 
21
- Chronos Ruby does not inspect environment variables, request bodies, cookies, HTTP headers, source code, database contents, or installed gems. Application-supplied fields are recursively sanitized, but applications should still avoid sending unnecessary personal, health, financial, or authentication data.
21
+ Chronos Ruby does not inspect environment variables, request/response bodies, cookies, Authorization headers, source code, database contents, lockfiles, or gem paths. Dependency reporting reads only already loaded gem names and versions once per agent. Application-supplied fields are recursively sanitized, but applications should still avoid sending unnecessary personal, health, financial, or authentication data.
22
22
 
23
23
  ## Supported Ruby and Rails versions
24
24
 
@@ -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.6.0.pre.1"
34
+ gem "chronos-ruby", "0.8.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.6.0.pre.1", :require => "chronos/rails"
55
+ gem "chronos-ruby", "0.8.0.pre.1", :require => "chronos/rails"
56
56
  ```
57
57
 
58
58
  Generate the initializer with:
@@ -168,7 +168,22 @@ Local exception-specific ignore callbacks are not available in version 0.4. The
168
168
 
169
169
  ## Performance monitoring
170
170
 
171
- Version 0.5 emits bounded `request`, `query`, `job`, and `cache` events from public Rails notifications. SQL text and binds, cache keys and values, job arguments, mail content and recipients, and request or response bodies are never copied. Aggregation, percentiles, query fingerprints, and external HTTP monitoring are not implemented in this version.
171
+ Version 0.7 aggregates request, query, and job observations into bounded `metric_batch` events. Groups include count, error count/rate, total/min/max/average duration, fixed histogram buckets, status counts, and component breakdown. Percentiles are calculated in the SaaS without retaining every local duration.
172
+
173
+ SQL comments and literal values are removed before a bounded normalized query and SHA-256 fingerprint are produced. Binds are never read. Slow, repeated, possible N+1, long-transaction, connection-error, and deadlock signals are heuristic and require server-side confirmation. Group count, active trace count, fingerprints per trace, histogram buckets, and batch size all have fixed limits. See [Essential APM aggregation](docs/modules/apm-aggregation.md).
174
+
175
+ ```ruby
176
+ Chronos.configure do |config|
177
+ # required connection settings omitted
178
+ config.apm_enabled = true
179
+ config.apm_max_groups = 200
180
+ config.apm_flush_count = 100
181
+ config.apm_batch_size = 50
182
+ config.apm_max_queries_per_request = 100
183
+ config.apm_slow_query_threshold_ms = 500.0
184
+ config.apm_n_plus_one_threshold = 5
185
+ end
186
+ ```
172
187
 
173
188
  ## Sidekiq and Active Job
174
189
 
@@ -176,16 +191,36 @@ Version `0.6.0.pre.1` adds optional Sidekiq 4/5 middleware:
176
191
 
177
192
  ```ruby
178
193
  gem "sidekiq", "~> 5.0"
179
- gem "chronos-ruby", "0.6.0.pre.1", :require => "chronos/sidekiq"
194
+ gem "chronos-ruby", "0.8.0.pre.1", :require => "chronos/sidekiq"
180
195
  ```
181
196
 
182
197
  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
198
 
184
199
  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.
185
200
 
201
+ ## External HTTP, cache, and dependencies
202
+
203
+ Version 0.8 instruments only explicitly selected `Net::HTTP` connection objects, avoiding a global monkey patch:
204
+
205
+ ```ruby
206
+ Chronos.configure do |config|
207
+ # required connection settings omitted
208
+ config.external_http_enabled = true
209
+ config.external_http_trace_headers = true
210
+ end
211
+
212
+ http = Net::HTTP.new("payments.example.com", 443)
213
+ http.use_ssl = true
214
+ Chronos.instrument_net_http(http)
215
+ ```
216
+
217
+ The wrapper records only sanitized host, method, status, duration, timeout, connection-error flag, and error class. It propagates `X-Chronos-Trace-ID` and `X-Chronos-Request-ID` when available and never reads URL path/query, Authorization, request body, response body, or error message.
218
+
219
+ Rails cache telemetry omits raw keys by default. Set `cache_key_mode = :sha256` to emit a project-scoped hash; `:none` is the default. Dependency reporting is enabled by default, reads at most 100 already loaded gem specs, and emits one independent `dependencies` event per agent. Set `dependency_reporting = false` to disable it. See [External HTTP](docs/modules/external-http.md), [Cache observability](docs/modules/cache-observability.md), and [Dependency inventory](docs/modules/dependencies.md).
220
+
186
221
  ## Deploy tracking
187
222
 
188
- Deploy notifications are not implemented in version 0.4. `app_version` may be included in exception events for release correlation.
223
+ Deploy notifications are not implemented in version 0.8. `app_version` is included in event service metadata and the once-per-agent dependency inventory for release correlation.
189
224
 
190
225
  ## Asynchronous queue
191
226
 
@@ -255,6 +290,12 @@ Chronos.configure do |config|
255
290
  config.context_store = :thread_local
256
291
  config.breadcrumb_capacity = 20
257
292
  config.breadcrumb_max_bytes = 2048
293
+ config.apm_enabled = true
294
+ config.apm_max_groups = 200
295
+ config.apm_flush_count = 100
296
+ config.external_http_enabled = false
297
+ config.cache_key_mode = :none
298
+ config.dependency_reporting = true
258
299
  end
259
300
  ```
260
301
 
@@ -266,7 +307,7 @@ Configuration errors are raised during `Chronos.configure`. Capture and delivery
266
307
 
267
308
  ## Benchmark
268
309
 
269
- Run the version 0.6 benchmarks with:
310
+ Run the version 0.8 benchmarks with:
270
311
 
271
312
  ```bash
272
313
  bundle _1.17.3_ exec ruby benchmarks/capture_exception.rb
@@ -277,13 +318,15 @@ bundle _1.17.3_ exec ruby benchmarks/retry_backlog.rb
277
318
  bundle _1.17.3_ exec ruby benchmarks/request_overhead.rb
278
319
  bundle _1.17.3_ exec ruby benchmarks/rails_notifications.rb
279
320
  bundle _1.17.3_ exec ruby benchmarks/sidekiq_middleware.rb
321
+ bundle _1.17.3_ exec ruby benchmarks/apm_aggregation.rb
322
+ bundle _1.17.3_ exec ruby benchmarks/external_http.rb
280
323
  ```
281
324
 
282
325
  Results depend on runtime, hardware, and payload. No performance comparison is claimed until repeatable measurements are published.
283
326
 
284
327
  ## Migration from Airbrake
285
328
 
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.
329
+ An Airbrake migration guide will be added before the legacy 1.0 release. Version 0.8 does not claim API compatibility or automatic replacement.
287
330
 
288
331
  ## Local development
289
332
 
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://chronos.example/schemas/apm-batch-v1.schema.json",
4
+ "title": "Chronos APM metric batch payload v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["metrics", "dropped_groups"],
8
+ "properties": {
9
+ "dropped_groups": {"type": "integer", "minimum": 0},
10
+ "metrics": {
11
+ "type": "array",
12
+ "minItems": 1,
13
+ "maxItems": 50,
14
+ "items": {
15
+ "type": "object",
16
+ "required": ["metric_type", "dimensions", "count", "error_count", "error_rate", "duration_ms", "histogram", "breakdown_ms", "signals", "status_codes"],
17
+ "properties": {
18
+ "metric_type": {"enum": ["request", "query", "job", "external_http"]},
19
+ "dimensions": {"type": "object"},
20
+ "count": {"type": "integer", "minimum": 1},
21
+ "error_count": {"type": "integer", "minimum": 0},
22
+ "error_rate": {"type": "number", "minimum": 0, "maximum": 1},
23
+ "duration_ms": {
24
+ "type": "object",
25
+ "required": ["total", "min", "max", "average"]
26
+ },
27
+ "histogram": {"type": "array", "maxItems": 20},
28
+ "breakdown_ms": {"type": "object"},
29
+ "signals": {"type": "object"},
30
+ "status_codes": {"type": "object"}
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://chronos.example/schemas/dependencies-v1.schema.json",
4
+ "title": "Chronos dependency inventory payload v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["dependencies", "ruby"],
8
+ "properties": {
9
+ "dependencies": {
10
+ "type": "array",
11
+ "maxItems": 200,
12
+ "items": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["name", "version"],
16
+ "properties": {
17
+ "name": {"type": "string", "maxLength": 128},
18
+ "version": {"type": "string", "maxLength": 64}
19
+ }
20
+ }
21
+ },
22
+ "ruby": {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "required": ["version", "engine", "platform"],
26
+ "properties": {
27
+ "version": {"type": "string", "maxLength": 64},
28
+ "engine": {"type": "string", "maxLength": 64},
29
+ "platform": {"type": "string", "maxLength": 128}
30
+ }
31
+ },
32
+ "rails": {"type": ["string", "null"], "maxLength": 128},
33
+ "web_server": {"type": ["string", "null"], "maxLength": 128},
34
+ "database_adapter": {"type": ["string", "null"], "maxLength": 128},
35
+ "sidekiq": {"type": ["string", "null"], "maxLength": 128},
36
+ "release": {"type": ["string", "null"], "maxLength": 128}
37
+ }
38
+ }
@@ -20,7 +20,7 @@
20
20
  "properties": {
21
21
  "schema_version": {"const": "1.0"},
22
22
  "event_id": {"type": "string", "minLength": 1},
23
- "event_type": {"enum": ["exception", "request", "query", "job", "cache"]},
23
+ "event_type": {"enum": ["exception", "request", "query", "job", "cache", "external_http", "dependencies", "metric_batch"]},
24
24
  "occurred_at": {"type": "string"},
25
25
  "sent_at": {"type": "string"},
26
26
  "project_key": {"type": "string", "minLength": 1},
@@ -0,0 +1,27 @@
1
+ # ADR-015 — Bounded local APM aggregation
2
+
3
+ ## Status
4
+
5
+ Accepted for version 0.7.
6
+
7
+ ## Context
8
+
9
+ Sending every request, SQL query, and job as an independent event increases network overhead. Retaining every observation for client-side percentiles or unbounded N+1 analysis would transfer that cost to application memory.
10
+
11
+ ## Decision
12
+
13
+ Aggregate request, query, and job observations by bounded low-cardinality dimensions. Store only counters, error counters, duration sum/min/max, fixed histogram buckets, component totals, status counters, and heuristic signal counters. Calculate averages locally and percentiles in the SaaS.
14
+
15
+ Use a fixed group limit, fixed query-fingerprint limit per trace, fixed batch size, and no new timer thread. Drain on observation threshold, explicit flush, and close. Clear incomplete trace trackers during drain. Normalize SQL comments and literal values before fingerprinting and never read binds. Use a `metric_batch` event through the existing sanitization, queue, retry, circuit breaker, and backlog pipeline.
16
+
17
+ ## Alternatives
18
+
19
+ One event per observation was rejected as the default because delivery overhead scales directly with traffic. Retaining raw duration arrays was rejected because memory would scale with traffic. Client-side percentiles, SQL parsing dependencies, and background flush threads were rejected for the legacy line. Full SQL and bind capture was rejected for privacy and cardinality.
20
+
21
+ ## Positive consequences
22
+
23
+ Delivery volume is reduced, memory remains bounded, request breakdown is available, SQL values are excluded, and the SaaS receives stable histograms suitable for percentile calculation.
24
+
25
+ ## Negative consequences
26
+
27
+ Process crashes can lose undrained aggregates, incomplete trackers are discarded on drain, local signals are heuristic, and a defensive SQL normalizer cannot understand every database dialect.
@@ -0,0 +1,25 @@
1
+ # ADR-016 — Explicit and privacy-bounded observability integrations
2
+
3
+ ## Status
4
+
5
+ Accepted for version 0.8.
6
+
7
+ ## Context
8
+
9
+ Outbound calls, cache behavior, and runtime dependencies materially improve diagnosis, but global HTTP monkey patches and raw cache/dependency data create compatibility, cardinality, and privacy risks in legacy applications.
10
+
11
+ ## Decision
12
+
13
+ Instrument only explicitly supplied `Net::HTTP` instances with a per-object prepended module. Preserve method, streaming block, response, and exception semantics. Collect only bounded host/outcome/timing fields and propagate only Chronos correlation headers.
14
+
15
+ Normalize Rails cache notifications through a core value normalizer. Omit raw keys by default and permit only an explicit project-scoped SHA-256 mode. Never inspect values.
16
+
17
+ Build one bounded dependency event per agent from already loaded gem specs and feature-detected constants. Do not inspect lockfiles or paths, activate dependencies, or open database connections. Route every event through the existing sanitizer, bounded queue, retry, circuit breaker, and backlog pipeline.
18
+
19
+ ## Alternatives
20
+
21
+ A global `Net::HTTP` patch was rejected because it changes unrelated connections and increases conflict risk. Capturing full URLs, headers, bodies, cache keys, or lockfiles was rejected for privacy and cardinality. Attaching dependencies to every exception was rejected for redundant payload cost. Automatic Faraday and other client adapters were deferred until demand and official middleware boundaries are validated.
22
+
23
+ ## Consequences
24
+
25
+ Applications choose exactly which HTTP clients are instrumented, native errors remain intact, and memory/data volume stay bounded. The tradeoffs are explicit setup per connection, partial dependency inventories when gems load late, pseudonymous rather than anonymous cache hashes, and no coverage for other HTTP clients in this release.
data/docs/architecture.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Architecture
2
2
 
3
- Chronos Ruby 0.6 uses hexagonal boundaries so the legacy core remains independent of frameworks and delivery infrastructure.
3
+ Chronos Ruby 0.8 uses hexagonal boundaries so the legacy core remains independent of frameworks and delivery infrastructure.
4
4
 
5
5
  ```mermaid
6
6
  flowchart TB
@@ -19,6 +19,11 @@ flowchart TB
19
19
  Sidekiq[Integrations / Sidekiq client and server middleware] --> Facade
20
20
  Sidekiq --> Envelope[Versioned job-envelope context]
21
21
  Application --> Telemetry[Core / TelemetryEvent]
22
+ Telemetry --> APM[Application / ApmAggregator]
23
+ APM --> Metrics[Core / MetricAggregate and SqlNormalizer]
24
+ NetHTTP[Explicit Net::HTTP instance wrapper] --> Facade
25
+ Rails --> Cache[Core / CacheNormalizer]
26
+ Application --> Dependencies[Application / DependencyReporter]
22
27
  ```
23
28
 
24
29
  ## Boundaries
@@ -44,7 +49,11 @@ An exception becomes an immutable notice. `Sanitizer` removes sensitive values b
44
49
 
45
50
  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.
46
51
 
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.
52
+ 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 a job observation, and routes a failure through the existing notice pipeline before re-raising it. Sidekiq retains queue, retry, thread, and connection lifecycle ownership.
53
+
54
+ Version 0.7 routes request, query, and job observations through `ApmAggregator`. `SqlNormalizer` removes values before grouping, while `MetricAggregate` owns fixed numerical statistics. Aggregates and per-trace query trackers are bounded and mutex-protected. Threshold or lifecycle drains create `metric_batch` telemetry that passes through the existing sanitizer and delivery pipeline. No APM-specific thread is created.
55
+
56
+ Version 0.8 prepends a wrapper only to each explicitly selected `Net::HTTP` object. The wrapper records bounded outcome metadata and feeds the existing APM aggregator; it does not modify `Net::HTTP` globally. `CacheNormalizer` turns public Rails cache notifications into bounded metadata before delivery. `DependencyReporter` reads already loaded runtime metadata once per agent under a mutex and queues it as a separate event.
48
57
 
49
58
  ## Failure policy
50
59
 
@@ -19,6 +19,10 @@ Version 0.5 includes Rails 4.2 and 5.2 applications plus a dedicated matrix, but
19
19
 
20
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
21
 
22
+ Version `0.7.0.pre.1` keeps the same Ruby/Rails matrix and implements APM aggregation without modern concurrency or SQL-parser dependencies. Its compatibility remains `Best effort` until request/SQL/job aggregate payloads pass the dedicated fake-server gates for every listed runtime.
23
+
24
+ Version `0.8.0.pre.1` uses per-object `Module#prepend`, legacy `Net::HTTP`, standard-library SHA-256, and loaded-spec feature detection. It adds no runtime dependency and keeps the same matrix. Outbound HTTP, cache, and dependency gates must pass every listed runtime before support is promoted.
25
+
22
26
  Status meanings:
23
27
 
24
28
  - Supported: the complete required compatibility gate passes.
@@ -39,7 +39,7 @@
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 event sampling |
42
- | `enabled_event_types` | Optional | exception, request, query, job, cache | Local allowlist for supported event envelopes |
42
+ | `enabled_event_types` | Optional | exception, request, query, job, cache, external_http, dependencies, metric_batch | Local allowlist for supported event envelopes |
43
43
  | `max_remote_send_interval` | Optional | `60.0` | Local upper bound for remotely requested send spacing |
44
44
  | `context_store` | Optional | `:thread_local` | `:thread_local` or an object implementing `get`, `set`, `clear`, and `with_context` |
45
45
  | `breadcrumb_capacity` | Optional | `20` | Positive count of newest breadcrumbs retained per execution |
@@ -48,6 +48,20 @@
48
48
  | `rails_capture_in_console` | Optional | `false` | Enables automatic integration while Rails console is loaded |
49
49
  | `rails_capture_in_test` | Optional | `false` | Enables automatic integration in the Rails test environment |
50
50
  | `rails_capture_user_agent` | Optional | `false` | Adds the Rack user agent to request context |
51
+ | `apm_enabled` | Optional | `true` | Aggregates request, query, job, and enabled external HTTP observations into bounded metric batches |
52
+ | `apm_max_groups` | Optional | `200` | Maximum metric groups and active trace trackers retained locally |
53
+ | `apm_flush_count` | Optional | `100` | Aggregate observations accepted before a threshold drain |
54
+ | `apm_batch_size` | Optional | `50` | Metric groups per batch; hard maximum 50 |
55
+ | `apm_max_queries_per_request` | Optional | `100` | Query fingerprints tracked per trace for repetition signals |
56
+ | `apm_slow_query_threshold_ms` | Optional | `500.0` | Query duration that produces a slow-query signal and sampled source |
57
+ | `apm_long_transaction_threshold_ms` | Optional | `1000.0` | Transaction-labelled SQL duration that produces a signal |
58
+ | `apm_n_plus_one_threshold` | Optional | `5` | Repeated fingerprint count producing one possible-N+1 signal; minimum 2 |
59
+ | `apm_histogram_buckets` | Optional | Fixed millisecond boundaries | Increasing positive duration boundaries; at most 19 plus `+Inf` |
60
+ | `external_http_enabled` | Optional | `false` | Allows explicit per-instance outbound `Net::HTTP` instrumentation |
61
+ | `external_http_trace_headers` | Optional | `true` | Propagates Chronos trace/request headers on instrumented requests |
62
+ | `cache_key_mode` | Optional | `:none` | `:none` omits keys; `:sha256` emits a project-scoped key hash |
63
+ | `dependency_reporting` | Optional | `true` | Emits one bounded dependency event per configured agent |
64
+ | `dependency_max_items` | Optional | `100` | Loaded gem entries retained in the inventory; range 1–200 |
51
65
 
52
66
  ```ruby
53
67
  Chronos.configure do |config|
@@ -67,6 +81,19 @@ Chronos.configure do |config|
67
81
  config.breadcrumb_capacity = 20
68
82
  config.rails_capture_in_test = false
69
83
  config.rails_capture_in_console = false
84
+ config.apm_enabled = true
85
+ config.apm_max_groups = 200
86
+ config.apm_flush_count = 100
87
+ config.apm_batch_size = 50
88
+ config.apm_max_queries_per_request = 100
89
+ config.apm_slow_query_threshold_ms = 500.0
90
+ config.apm_long_transaction_threshold_ms = 1000.0
91
+ config.apm_n_plus_one_threshold = 5
92
+ config.external_http_enabled = false
93
+ config.external_http_trace_headers = true
94
+ config.cache_key_mode = :none
95
+ config.dependency_reporting = true
96
+ config.dependency_max_items = 100
70
97
  end
71
98
  ```
72
99
 
@@ -1,6 +1,6 @@
1
1
  # Data collected
2
2
 
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.
3
+ Version 0.8 emits exceptions, cache telemetry, one dependency inventory, and aggregated request/query/job/external-HTTP metric batches. 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
  |---|---|---|
@@ -32,9 +32,23 @@ Version 0.6 emits exception, request, query, job, and cache events. All fields p
32
32
  | Sidekiq arguments | Collected, sanitized, and bounded | First 20 job arguments; collections/depth/strings limited |
33
33
  | Sidekiq tags | Collected and bounded | Job payload or public worker options |
34
34
  | Sidekiq trace/request IDs | Propagated when present; trace generated otherwise | Chronos job-envelope metadata |
35
- | Cache operation, store, hit flag, duration | Collected; key/value omitted | ActiveSupport cache notifications |
35
+ | APM counts, error counts/rates, duration total/min/max/average | Aggregated by default | Request, query, and job observations |
36
+ | Fixed duration histogram and status counts | Aggregated by default | Local bounded counters |
37
+ | Component breakdown | database/view/external_http/cache/queue/application when observable | Trace-local bounded totals |
38
+ | Normalized SQL and SHA-256 fingerprint | Collected without comments, literals, or binds | `sql.active_record` payload |
39
+ | SQL adapter, operation, inferred table, AR name, cache flag, role/shard | Collected when exposed | Public notification payload and connection feature detection |
40
+ | Slow SQL source frame | Collected only for threshold-selected slow queries | Bounded application call frame |
41
+ | APM diagnostic signals | Heuristic counters | Local threshold and repetition detection |
42
+ | External HTTP host, method, status, duration, timeout, connection-error flag, error class | Disabled by default; per-instance opt-in | Instrumented `Net::HTTP` object |
43
+ | Chronos trace/request headers | Propagated when available | Current execution context |
44
+ | Cache operation, backend, namespace, hit/miss, duration | Collected; key/value omitted | ActiveSupport cache notifications |
45
+ | Cache key hash | Disabled by default | Project-scoped SHA-256 when `cache_key_mode = :sha256` |
46
+ | Loaded gem names/versions and Ruby runtime | Once per agent by default; bounded | `Gem.loaded_specs` and Ruby constants |
47
+ | Rails, web server, database adapter, Sidekiq, release | Included when safely detectable/configured | Loaded constants/specs and `app_version` |
36
48
 
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.
49
+ 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, raw cache keys/values, mail bodies/recipients, gem paths, or lockfile contents. Sidekiq JIDs/arguments and bounded loaded gem names/versions are documented integration fields.
50
+
51
+ APM dimensions never include user ID, job ID, raw URL, exception message, bind value, or cache key. Normalized routes replace common numeric/UUID segments. Normalized SQL can retain schema, table, and column identifiers; review those identifiers as part of the privacy audit.
38
52
 
39
53
  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.
40
54
 
@@ -23,4 +23,10 @@ For a network-free outage and bounded-backlog example, run:
23
23
  bundle _1.17.3_ exec ruby examples/plain-ruby/resilient_delivery.rb
24
24
  ```
25
25
 
26
- The example builds the final event locally and demonstrates key redaction, e-mail and Bearer-token detection, application-specific blocklisting, and identifier hashing.
26
+ For network-free per-instance outbound HTTP instrumentation, run:
27
+
28
+ ```bash
29
+ bundle _1.17.3_ exec ruby examples/plain-ruby/external_http.rb
30
+ ```
31
+
32
+ The examples demonstrate final event construction, key redaction, e-mail and Bearer-token detection, application-specific blocklisting, identifier hashing, and the bounded external HTTP payload without opening a socket.
@@ -0,0 +1,57 @@
1
+ # Essential APM aggregation
2
+
3
+ Version `0.8.0.pre.1` aggregates request, SQL, job, and explicitly instrumented external HTTP observations locally before delivery. The agent emits versioned `metric_batch` envelopes containing at most 50 metric groups instead of one event per observation. Aggregates drain when `apm_flush_count` observations are reached and whenever `Chronos.flush` or `Chronos.close` runs. No timer or additional APM thread is created.
4
+
5
+ ## Request metrics
6
+
7
+ Request groups use normalized route and HTTP method. Status codes are counted inside the group so error rate can be calculated without making status part of an unbounded key. Each metric contains count, error count/rate, total, minimum, maximum, average, fixed histogram buckets, and component breakdown. Percentiles remain server-side because retaining every duration locally would violate the memory boundary.
8
+
9
+ Rack records request metrics for non-Rails applications. Rails controller notifications and Rack share `record_event_once`, so the same request is counted only once. Controller notification data wins when available because its route is more precise.
10
+
11
+ ## SQL metrics and signals
12
+
13
+ `Chronos::Core::SqlNormalizer` removes block/line comments, quoted literal values, numeric values, booleans, nulls, and repeated `IN` values before producing a SHA-256 fingerprint. Binds are never read. Query dimensions can contain adapter, operation, inferred table, bounded normalized query, fingerprint, Active Record operation name, cache flag, connection role/shard, and a bounded source frame for slow sampled queries.
14
+
15
+ Local signals are intentionally heuristic:
16
+
17
+ - `slow_query` when duration reaches `apm_slow_query_threshold_ms`;
18
+ - `repeated_query` after the same fingerprint appears again in one trace;
19
+ - `possible_n_plus_one` once when the configured repetition threshold is reached;
20
+ - `long_transaction` for transaction-labelled SQL over its threshold;
21
+ - `connection_error` and `deadlock` from bounded exception class names.
22
+
23
+ The SaaS must confirm and correlate these signals. They are not proof of an N+1, deadlock, or application defect.
24
+
25
+ ## Bounded state
26
+
27
+ - at most `apm_max_groups` metric groups;
28
+ - at most `apm_max_groups` active trace trackers;
29
+ - at most `apm_max_queries_per_request` fingerprints per trace;
30
+ - at most 19 configured histogram boundaries plus `+Inf`;
31
+ - at most `apm_batch_size` groups per payload, with a hard maximum of 50;
32
+ - trackers are removed when their request completes and all remaining trackers are cleared on aggregate drain.
33
+
34
+ New groups beyond capacity are dropped and counted in `dropped_groups`. Existing groups continue accumulating. The state is process-local and is lost on restart.
35
+
36
+ ## Breakdown
37
+
38
+ The contract supports `database`, `view`, `external_http`, `cache`, `queue`, `application`, and `unknown`. Version 0.8 fills external HTTP time for explicitly instrumented calls inside a traced request, in addition to database/view/cache, queue/application for jobs, and residual application request time.
39
+
40
+ ## Configuration
41
+
42
+ ```ruby
43
+ Chronos.configure do |config|
44
+ # connection settings omitted
45
+ config.apm_enabled = true
46
+ config.apm_max_groups = 200
47
+ config.apm_flush_count = 100
48
+ config.apm_batch_size = 50
49
+ config.apm_max_queries_per_request = 100
50
+ config.apm_slow_query_threshold_ms = 500.0
51
+ config.apm_long_transaction_threshold_ms = 1000.0
52
+ config.apm_n_plus_one_threshold = 5
53
+ config.apm_histogram_buckets = [5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0]
54
+ end
55
+ ```
56
+
57
+ Setting `apm_enabled = false` restores individual request/query/job telemetry for compatibility diagnostics. Cache events remain individual in 0.7 while also contributing to an existing request breakdown.
@@ -0,0 +1,16 @@
1
+ # Cache observability
2
+
3
+ Rails ActiveSupport cache notifications produce individual bounded `cache` events containing operation, duration, hit/miss outcome, backend, and namespace. When a trace is active, duration also contributes to the request's cache breakdown.
4
+
5
+ Raw cache keys and values are never delivered. The default `cache_key_mode = :none` omits key identity entirely. Applications that need correlation can opt into a project-scoped SHA-256 value:
6
+
7
+ ```ruby
8
+ Chronos.configure do |config|
9
+ # connection settings omitted
10
+ config.cache_key_mode = :sha256
11
+ end
12
+ ```
13
+
14
+ Hashing accepts at most 2,048 bytes of key text and emits a fixed 64-character hexadecimal value. It is pseudonymization rather than anonymization: an attacker who can guess a low-entropy key may reproduce candidate hashes when project scope is known. Do not encode credentials or unnecessary personal data in cache keys.
15
+
16
+ The normalizer never calls cache read/write APIs, inspects cached values, or retains keys. Unsupported or incomplete notification payloads become an `unknown` outcome without raising into Rails.
@@ -7,3 +7,7 @@ This module is separate from capture because configuration errors should be foun
7
7
  Risks include accidentally logging credentials and changing settings while events run. The snapshot prevents mutation, while logger and transport code never interpolate `project_key`.
8
8
 
9
9
  Tests in `spec/unit/configuration_spec.rb` verify required fields, HTTPS defaults, immutable containers, disabled operation, and bounded numeric settings.
10
+
11
+ Version 0.7 adds bounded APM capacities, thresholds, and histogram boundaries. Invalid zero/negative capacities, batches above 50, N+1 thresholds below two, and non-increasing histogram boundaries are rejected before agent construction. See [Essential APM aggregation](apm-aggregation.md).
12
+
13
+ Version 0.8 adds Boolean HTTP/dependency switches, `:none`/`:sha256` cache-key policy, and a dependency limit from 1 to 200. Invalid values are rejected before instrumentation or collection starts. See [External HTTP](external-http.md), [Cache observability](cache-observability.md), and [Dependency inventory](dependencies.md).
@@ -0,0 +1,18 @@
1
+ # Dependency inventory
2
+
3
+ Version `0.8.0.pre.1` emits a separate `dependencies` event at most once per configured agent. It is queued before the first capture or during explicit flush/close and is never attached to each exception.
4
+
5
+ The inventory contains Ruby version/engine/platform, bounded names and versions from already loaded gem specs, the configured `app_version` release identifier, and Rails, Sidekiq, web server, or database adapter labels when safely detectable. The agent does not parse `Gemfile.lock`, activate missing gems, inspect gem paths/source, scan environment variables, open a database connection, or collect dependency configuration.
6
+
7
+ ```ruby
8
+ Chronos.configure do |config|
9
+ # connection settings omitted
10
+ config.dependency_reporting = true
11
+ config.dependency_max_items = 100
12
+ end
13
+
14
+ # Optional after application boot, before the first regular event:
15
+ Chronos.report_dependencies
16
+ ```
17
+
18
+ `dependency_max_items` accepts 1–200 and defaults to 100. Entries are sorted by name before truncation so repeated boots are stable. Set `dependency_reporting = false` when inventory collection is unnecessary or prohibited. Because collection is once-only, gems loaded after the first event appear only after a new agent configuration or process boot.
@@ -0,0 +1,27 @@
1
+ # External HTTP instrumentation
2
+
3
+ Version `0.8.0.pre.1` provides an optional `Net::HTTP` wrapper loaded through `chronos/net_http`. It is disabled by default and prepended only to a connection object explicitly passed to `Chronos.instrument_net_http`; the `Net::HTTP` class and unrelated instances are unchanged.
4
+
5
+ ```ruby
6
+ require "net/http"
7
+ require "chronos"
8
+
9
+ Chronos.configure do |config|
10
+ # connection settings omitted
11
+ config.external_http_enabled = true
12
+ config.external_http_trace_headers = true
13
+ end
14
+
15
+ http = Net::HTTP.new("payments.example.com", 443)
16
+ http.use_ssl = true
17
+ Chronos.instrument_net_http(http)
18
+ response = http.request(Net::HTTP::Get.new("/health"))
19
+ ```
20
+
21
+ The event contains a bounded lowercase host, uppercase method, response status, monotonic duration, timeout flag, connection-error flag, and error class. A request made inside a Chronos context receives `X-Chronos-Trace-ID` and `X-Chronos-Request-ID` unless the application already set those headers. Disable propagation with `external_http_trace_headers = false`.
22
+
23
+ The wrapper never reads or records the path, query string, Authorization, other request headers, request body, response headers/body, or exception message. The native streaming block is forwarded and the identical HTTP exception is re-raised. Telemetry failures are contained.
24
+
25
+ Successful and failed calls become bounded `external_http` APM groups keyed only by host and method. A call carrying a trace ID contributes its duration to the enclosing request's `external_http` breakdown. Faraday, HTTP.rb, Excon, and RestClient are outside this release.
26
+
27
+ Installation is idempotent per object. A `false` result means collection is disabled, the object is incompatible or already instrumented, or installation was contained after an internal error.