chronos-ruby 1.0.0 → 1.2.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 +36 -0
- data/README.md +27 -13
- data/contracts/apm-batch-v1.schema.json +51 -1
- data/docs/adr/ADR-010-opentelemetry-interoperability.md +3 -3
- data/docs/adr/ADR-015-bounded-apm-aggregation.md +3 -3
- data/docs/adr/ADR-019-bounded-query-diagnostics.md +44 -0
- data/docs/architecture.md +5 -2
- data/docs/compatibility.md +12 -18
- data/docs/configuration.md +27 -2
- data/docs/data-collected.md +9 -3
- data/docs/examples/plain-ruby.md +6 -0
- data/docs/modules/apm-aggregation.md +18 -5
- data/docs/modules/context.md +1 -1
- data/docs/modules/external-http.md +3 -2
- data/docs/modules/sidekiq-legacy.md +1 -1
- data/docs/modules/sql-monitoring.md +60 -6
- data/docs/modules/telemetry-events.md +1 -1
- data/docs/performance.md +19 -3
- data/docs/privacy-lgpd.md +6 -3
- data/docs/protocol-v1.md +1 -1
- data/docs/release-1.1-readiness.md +51 -0
- data/docs/security-review.md +7 -3
- data/docs/troubleshooting.md +6 -0
- data/lib/chronos/adapters/fiber_local_context_store.rb +57 -0
- data/lib/chronos/agent.rb +19 -3
- data/lib/chronos/application/apm_aggregator.rb +179 -29
- data/lib/chronos/configuration/apm_validation.rb +53 -1
- data/lib/chronos/configuration/validation.rb +2 -2
- data/lib/chronos/configuration.rb +21 -2
- data/lib/chronos/core/metric_aggregate.rb +69 -7
- data/lib/chronos/core/sql_query_analyzer.rb +309 -0
- data/lib/chronos/core/trace_context.rb +38 -0
- data/lib/chronos/integrations/active_job.rb +2 -2
- data/lib/chronos/integrations/faraday.rb +76 -0
- data/lib/chronos/integrations/net_http.rb +5 -1
- data/lib/chronos/integrations/opentelemetry.rb +44 -0
- data/lib/chronos/integrations/rack/middleware.rb +8 -2
- data/lib/chronos/integrations/sidekiq.rb +1 -1
- data/lib/chronos/ports/query_inspector.rb +23 -0
- data/lib/chronos/rails/active_record_query_inspector.rb +235 -0
- data/lib/chronos/rails/error_reporter_subscriber.rb +39 -0
- data/lib/chronos/rails/installer.rb +13 -0
- data/lib/chronos/rails/notifications_subscriber.rb +175 -2
- data/lib/chronos/rails.rb +2 -0
- data/lib/chronos/version.rb +2 -2
- data/lib/chronos.rb +6 -0
- metadata +46 -38
|
@@ -4,7 +4,7 @@ Version `0.6.0.pre.1` starts the legacy jobs line with optional Sidekiq 4 and 5
|
|
|
4
4
|
|
|
5
5
|
```ruby
|
|
6
6
|
gem "sidekiq", "~> 5.0"
|
|
7
|
-
gem "chronos-ruby", "~> 1.
|
|
7
|
+
gem "chronos-ruby", "~> 1.1.0", :require => "chronos/sidekiq"
|
|
8
8
|
```
|
|
9
9
|
|
|
10
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.
|
|
@@ -1,22 +1,76 @@
|
|
|
1
1
|
# Monitoramento SQL
|
|
2
2
|
|
|
3
|
-
## Problema e
|
|
3
|
+
## Problema e limites
|
|
4
4
|
|
|
5
|
-
O monitoramento SQL mede
|
|
5
|
+
O monitoramento SQL mede padrões de acesso e duração sem transmitir SQL bruto ou binds. Ele identifica query lenta/repetida, possível N+1, transação longa, famílias de erro e candidatos a índice. Recomendações são evidências heurísticas: a gem não cria índices, não executa DDL, não usa `EXPLAIN ANALYZE` e não substitui revisão do DBA.
|
|
6
6
|
|
|
7
7
|
## Fluxo e classes
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
AS[sql.active_record] --> N[SqlNormalizer]
|
|
12
|
+
N --> A[SqlQueryAnalyzer]
|
|
13
|
+
A --> M[ApmAggregator]
|
|
14
|
+
I[ActiveRecordQueryInspector opt-in] -->|índices, estatística, EXPLAIN allowlisted| A
|
|
15
|
+
M --> B[metric_batch sanitizado]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`Rails::NotificationsSubscriber` extrai campos permitidos. `Core::SqlNormalizer` remove comentários e literais, limita identificadores e calcula fingerprint SHA-256. `Core::SqlQueryAnalyzer` analisa somente o `SELECT` normalizado, extrai tabelas e colunas de igualdade, faixa, join e ordenação e produz candidatos limitados. `Rails::ActiveRecordQueryInspector` implementa a porta `Ports::QueryInspector` e, quando habilitado, compara o candidato com índices existentes, lê estimativa de linhas e obtém plano sem executar a consulta.
|
|
19
|
+
|
|
20
|
+
O inspector envia somente nomes limitados de tabela/índice/coluna, estimativa de linhas e nós de plano allowlisted: tipo, tabela, índice, custo e linhas estimadas. Predicados, filtros, valores, SQL original e mensagens de erro são descartados. Falhas viram apenas classe limitada em um diagnóstico `error`.
|
|
21
|
+
|
|
22
|
+
`Application::ApmAggregator` agrega no grupo da query:
|
|
23
|
+
|
|
24
|
+
- `severity_counts`: `error`, `warning`, `info` e `suggestion`;
|
|
25
|
+
- `diagnostics`: até 20 diagnósticos estáveis com código, categoria, evidência e contagem;
|
|
26
|
+
- `query_analysis`: uma análise representativa, preferindo a observação com inspeção mais rica;
|
|
27
|
+
- `signals`: contadores compatíveis de slow query, repetição, N+1, transação e erros;
|
|
28
|
+
- duração total/mínima/máxima/média, histograma e p50/p95/p99 aproximados.
|
|
29
|
+
|
|
30
|
+
## Severidades
|
|
31
|
+
|
|
32
|
+
| Severidade | Exemplos |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `error` | exceção SQL, conexão, pool/statement/lock timeout, deadlock, constraint e falha da inspeção |
|
|
35
|
+
| `warning` | query lenta, transação longa, possível N+1 e sequential scan observado |
|
|
36
|
+
| `info` | padrão normalizado analisado, query repetida e índice existente que cobre o padrão |
|
|
37
|
+
| `suggestion` | candidato de índice e uso de eager/batch loading para possível N+1 |
|
|
38
|
+
|
|
39
|
+
No projeto consumidor, cada item de `diagnostics` pode ser persistido como informação associada à métrica usando `code`, `severity`, `category`, `message`, `recommendation`, `evidence` e `count`. `code + severity + evidence.table + evidence.columns` forma a identidade agregada atual. O consumidor deve usar `severity` para apresentação e ordenação, não inferir severidade pelo texto. Campos desconhecidos devem ser preservados ou ignorados para manter compatibilidade aditiva.
|
|
10
40
|
|
|
11
|
-
|
|
41
|
+
Um candidato permanece `unverified` até o catálogo ser consultado. Ele se torna `covered` quando um índice possui o prefixo observado ou `missing` quando índices foram lidos e nenhum cobre esse prefixo. Seletividade, custo de escrita e distribuição de dados ainda precisam ser avaliados antes de qualquer migração.
|
|
12
42
|
|
|
13
|
-
|
|
43
|
+
## Configuração segura
|
|
14
44
|
|
|
15
45
|
```ruby
|
|
16
46
|
Chronos.configure do |config|
|
|
17
47
|
config.apm_slow_query_threshold_ms = 500.0
|
|
18
48
|
config.apm_n_plus_one_threshold = 5
|
|
49
|
+
config.apm_trace_ttl_seconds = 60.0
|
|
50
|
+
config.apm_query_analysis_enabled = true
|
|
51
|
+
config.apm_query_analysis_max_queries = 100
|
|
52
|
+
|
|
53
|
+
# Opt-in por adicionar consultas de somente leitura ao banco.
|
|
54
|
+
config.apm_query_inspection_enabled = true
|
|
55
|
+
config.apm_query_statistics_enabled = true
|
|
56
|
+
config.apm_query_plan_enabled = true
|
|
57
|
+
config.apm_query_inspection_min_duration_ms = 500.0
|
|
58
|
+
config.apm_query_inspection_max_queries = 20
|
|
19
59
|
end
|
|
20
60
|
```
|
|
21
61
|
|
|
22
|
-
|
|
62
|
+
A análise normalizada é ativada por padrão, não consulta o banco e é calculada uma vez por fingerprint até o limite do cache. Inspeção, estatísticas e plano são desativados por padrão. Cada fingerprint é inspecionado no máximo uma vez por subscriber até o limite configurado; uma inspeção posterior pode enriquecer a análise estática já armazenada. PostgreSQL usa `pg_class` e `EXPLAIN (FORMAT JSON)`; MySQL/Trilogy usa `information_schema` e `EXPLAIN`. Adapters não reconhecidos ainda recebem análise estática e índices caso exponham `connection.indexes`.
|
|
63
|
+
|
|
64
|
+
## Transações e N+1
|
|
65
|
+
|
|
66
|
+
O subscriber mede o intervalo aproximado entre os callbacks de `BEGIN`/`START TRANSACTION` e `COMMIT`/`ROLLBACK`, por identidade local da conexão. Savepoints não encerram a transação externa. Estados ociosos expiram e conexões rastreadas são limitadas. O valor não inclui o tempo gasto antes do callback de `BEGIN` nem depois do callback final.
|
|
67
|
+
|
|
68
|
+
Possível N+1 exige `SELECT` não cacheado, mesmo fingerprint, mesmo trace e repetição até o threshold. A sugestão recomenda eager loading ou carregamento em lote, mas o consumidor deve confirmar a semântica antes da alteração.
|
|
69
|
+
|
|
70
|
+
## Riscos e extensão
|
|
71
|
+
|
|
72
|
+
`EXPLAIN` ainda usa planner, conexão e locks leves de catálogo; por isso é opt-in e selecionado por duração. Não existe timeout portátil entre os adapters legacy, então habilite primeiro em staging e monitore o orçamento. Nomes de schema/tabela/coluna/índice podem revelar o domínio e precisam de avaliação LGPD.
|
|
73
|
+
|
|
74
|
+
Um inspector alternativo pode implementar `call(raw_sql, query, options)` e retornar `indexes`, `statistics`, `plan` e `errors` nos limites do contrato. Ele deve ser somente leitura e nunca devolver valores ou mensagens livres.
|
|
75
|
+
|
|
76
|
+
Os testes principais são `spec/unit/core/sql_query_analyzer_spec.rb`, `spec/unit/rails/active_record_query_inspector_spec.rb`, `spec/unit/application/apm_aggregator_spec.rb` e `spec/integration/rails_telemetry_delivery_spec.rb`. O exemplo executável está em `examples/plain-ruby/query_analysis.rb`.
|
|
@@ -6,6 +6,6 @@ Telemetry has the same `schema_version`, event ID, timestamps, project, environm
|
|
|
6
6
|
|
|
7
7
|
`Chronos.record_event` is the narrow integration entry point. Application code should prefer documented higher-level APIs; its payload is allowlisted by each bundled integration and then sanitized. Remote configuration can reduce or disable any locally enabled telemetry type but cannot enable a type excluded by local configuration.
|
|
8
8
|
|
|
9
|
-
Version 0.8 aggregates request, query, job, and enabled external HTTP observations into `metric_batch` events by default. Cache remains an individual event while contributing to an existing traced request breakdown. Dependencies use one separate event per agent. Set `apm_enabled = false` only when individual legacy telemetry is required for diagnostics.
|
|
9
|
+
Version 0.8 aggregates request, query, job, and enabled external HTTP observations into `metric_batch` events by default. Cache remains an individual event while contributing to an existing traced request breakdown. Dependencies use one separate event per agent. Set `apm_enabled = false` only when individual legacy telemetry is required for diagnostics. The agent emits histogram-derived approximate p50/p95/p99 while the SaaS may calculate more precise merged values; see [Essential APM aggregation](apm-aggregation.md).
|
|
10
10
|
|
|
11
11
|
Version 0.9 delivers deploy telemetry synchronously because deployment commands are short-lived. Every telemetry and exception envelope contains the fixed correlation fields documented in [Deploy tracking and release correlation](deploy-tracking.md).
|
data/docs/performance.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Performance
|
|
2
2
|
|
|
3
|
-
Performance is a functional requirement, but version 1.
|
|
3
|
+
Performance is a functional requirement, but version 1.1 makes no unverified speed claim.
|
|
4
4
|
|
|
5
5
|
Current controls:
|
|
6
6
|
|
|
@@ -18,9 +18,11 @@ Current controls:
|
|
|
18
18
|
- shutdown and flush have caller-controlled timeouts;
|
|
19
19
|
- request context and breadcrumbs have fixed structural and byte limits;
|
|
20
20
|
- Rack middleware never consumes request or response bodies.
|
|
21
|
-
- Rails subscribers copy only small allowlisted field sets and never
|
|
21
|
+
- Rails subscribers copy only small allowlisted field sets and never deliver raw SQL or job arguments.
|
|
22
22
|
- Sidekiq middleware creates no per-job thread or connection and bounds arguments, collections, nesting, strings, and tags before telemetry capture.
|
|
23
23
|
- APM group, trace, query-fingerprint, histogram, and batch counts are fixed; no APM timer thread is created.
|
|
24
|
+
- active trace trackers survive aggregate drains but expire after `apm_trace_ttl_seconds`; query inspections and transaction connections have independent fixed caps;
|
|
25
|
+
- normalized query analysis is bounded and database inspection is disabled by default; opt-in index/statistics/plan inspection adds database round trips only once per selected fingerprint;
|
|
24
26
|
- outbound HTTP instrumentation uses two clock reads and bounded metadata without body/header traversal;
|
|
25
27
|
- cache normalization is bounded and SHA-256 runs only when explicitly enabled;
|
|
26
28
|
- dependency inventory runs at most once per agent and is capped at 200 loaded specs.
|
|
@@ -29,7 +31,21 @@ Current controls:
|
|
|
29
31
|
|
|
30
32
|
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.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
`benchmarks/query_analysis.rb` compares normalization alone with normalization plus bounded static analysis. It deliberately excludes database inspection because catalog and planner cost must be measured against the actual adapter, schema, statistics, and database host before production enablement.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
ITERATIONS=100000 WARMUP=5000 bundle exec ruby benchmarks/query_analysis.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Version 1.1.0 release gates
|
|
41
|
+
|
|
42
|
+
Version 1.1 adds a release-gate benchmark for normalized SQL analysis. Static analysis is calculated once per fingerprint and cached under `apm_query_analysis_max_queries`; database inspection remains disabled in the benchmark because its cost depends on the actual adapter, schema, statistics, network, and database host.
|
|
43
|
+
|
|
44
|
+
The tag workflow runs query analysis with 1,000 warmup and 10,000 measured iterations, then repeats the existing Rack comparison and 500-event fake-endpoint privacy/load gate. Local preparation evidence is recorded in [Version 1.1 readiness](release-1.1-readiness.md). Results are environment-specific and are not a cross-runtime performance promise.
|
|
45
|
+
|
|
46
|
+
The local Ruby 2.2.10 preparation run measured 453.981 µs per normalization, 594.862 µs for the first static analysis of a fingerprint, 43.946 µs median incremental Rack work, and 500/500 fake-endpoint deliveries at 378.15 events/s. Static analysis is cached; the 594.862 µs value is not paid for every repeated fingerprint.
|
|
47
|
+
|
|
48
|
+
## Historical version 1.0.0 release gates
|
|
33
49
|
|
|
34
50
|
`benchmarks/comparative.rb` compares the same successful Rack fixture without and with Chronos instrumentation. It performs configurable warmup, at least three samples, and reports median plus median absolute deviation. `benchmarks/fake_endpoint_load.rb` sends asynchronous exception events to a local TCP endpoint, verifies the v1 schema marker, ensures the secret key is absent from every payload, and fails on loss, rejection, invalid payload, or timeout.
|
|
35
51
|
|
data/docs/privacy-lgpd.md
CHANGED
|
@@ -15,7 +15,8 @@ Version 0.9 sanitizes exception, framework telemetry, dependency/deploy inventor
|
|
|
15
15
|
| Unknown Ruby objects | Represented by class name without calling application serialization |
|
|
16
16
|
| Request/response bodies, raw query strings, cookies, authorization headers, raw SQL/binds, cache values, mail bodies, environment variables | Never collected automatically |
|
|
17
17
|
| Sidekiq arguments | Collected automatically, limited before sanitization, then redacted by the common policy |
|
|
18
|
-
| SQL | Comments, quoted/numeric/boolean/null literals removed; binds never read; bounded identifiers remain |
|
|
18
|
+
| SQL | Comments, quoted/numeric/boolean/null literals removed; binds never read; bounded identifiers remain; raw SQL is never transmitted |
|
|
19
|
+
| Query inspection | Disabled by default; local `EXPLAIN` has no `ANALYZE`; only allowlisted plan/catalog metadata is retained |
|
|
19
20
|
| External HTTP | Host/method/status/timing only; URL path/query, Authorization, bodies, headers, and error messages omitted |
|
|
20
21
|
| Cache key | Omitted by default; optional project-scoped SHA-256 hash; cache value never read |
|
|
21
22
|
| Dependencies | Bounded loaded gem names/versions and detected runtime labels; paths and lockfiles omitted |
|
|
@@ -39,16 +40,18 @@ Raw cache keys can contain user or business data and are never delivered. Option
|
|
|
39
40
|
|
|
40
41
|
## Rails telemetry
|
|
41
42
|
|
|
42
|
-
Rails subscribers use per-notification allowlists. SQL events retain
|
|
43
|
+
Rails subscribers use per-notification allowlists. SQL events retain normalized value-free metadata, bounded analysis, cached flag, duration and optional allowlisted inspection evidence; cache events omit key and value; mailer events omit addresses and content; Active Job events omit arguments; view identifiers are reduced to basenames. Controller parameters are sanitized by the normal payload pipeline before queueing.
|
|
43
44
|
|
|
44
45
|
## Sidekiq jobs
|
|
45
46
|
|
|
46
47
|
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.
|
|
47
48
|
|
|
48
|
-
## APM dimensions
|
|
49
|
+
## APM dimensions and query inspection
|
|
49
50
|
|
|
50
51
|
Metric groups deliberately exclude user IDs, JIDs, raw URLs, request parameters, bind values, exception messages, and cache keys. SQL normalization removes common literal forms and comments before fingerprinting, but retains bounded database identifiers and cannot parse every dialect. Do not encode personal or secret values in schema, table, column, SQL keyword, or operation names. Slow-query source contains a bounded file/line frame under the configured application root, not source-code contents.
|
|
51
52
|
|
|
53
|
+
Static query analysis is enabled by default and processes only the normalized SQL. Index, statistics, and plan inspection are separate opt-ins. The original SELECT is used only to ask the same local connection for `EXPLAIN` without `ANALYZE`; it is never placed in telemetry. Plan predicates and arbitrary `Extra`/filter text are discarded. Only bounded node type, relation, index, estimated rows, cost and schema-index definitions cross the sanitizer. These identifiers can still disclose business vocabulary, so production enablement requires a representative synthetic payload audit.
|
|
54
|
+
|
|
52
55
|
## Rack context and breadcrumbs
|
|
53
56
|
|
|
54
57
|
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/protocol-v1.md
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
The schemas under `contracts/` are the source of truth for protocol v1. Version `1.0.0` freezes `schema_version: "1.0"` and treats every required field, enum value, privacy exclusion, and maximum as a compatibility contract.
|
|
4
4
|
|
|
5
|
-
Compatible changes may add optional bounded fields or new event types accepted by the server. Removing or renaming a field, changing its type/meaning, weakening a bound, or
|
|
5
|
+
Compatible changes may add optional bounded fields or new event types accepted by the server. APM v1 metrics can now carry tracking-loss metadata, approximate percentiles, severity counts, at most 20 structured diagnostics, and one bounded query-analysis object. They remain optional in the schema for compatibility even though this agent version emits them. Consumers should persist diagnostic `code` and `severity` as stable model fields and treat English messages/recommendations as display fallback text. Removing or renaming a field, changing its type/meaning, weakening a bound, or adding a required field without coordinated consumer rollout needs a new protocol major schema. Authentication remains outside the JSON payload. Contract tests and `script/verify_docs` must pass before a release.
|
|
6
6
|
|
|
7
7
|
The explicit integration check uses the normal exception envelope and identifies itself through `context.integration_verification`. Its receiver acknowledgement is governed separately by [`integration-verification-response-v1.schema.json`](../contracts/integration-verification-response-v1.schema.json). A successful HTTP status without a complete, correlated response is not proof of authentication or ingestion.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Version 1.1 release evidence
|
|
2
|
+
|
|
3
|
+
Version `1.1.0` is a backward-compatible minor release of the stable legacy line. It adds bounded SQL diagnostics, optional read-only database inspection, richer query metrics, and safer trace correlation while keeping `schema_version: "1.0"` and the previously validated Ruby/Rails/Sidekiq support matrix.
|
|
4
|
+
|
|
5
|
+
## Scope decision
|
|
6
|
+
|
|
7
|
+
The long-term roadmap associates a transitional 1.x line with Ruby 2.7 and Rails 6. This release does not claim that support: no runtime/framework pair becomes `Supported` without dedicated CI, a real example application, fake-endpoint delivery, shutdown, privacy, and integration evidence. Version 1.1.0 is therefore a SemVer-compatible capability release for the current legacy package; the transitional matrix remains future work.
|
|
8
|
+
|
|
9
|
+
## Candidate evidence
|
|
10
|
+
|
|
11
|
+
Local release preparation on 2026-08-05 used Ruby 2.2.10 x86_64 on macOS. The tag workflow must reproduce every supported Linux/container gate before RubyGems publication.
|
|
12
|
+
|
|
13
|
+
| Gate | Local candidate evidence | Tag enforcement |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Unit, integration, contract and documentation | 195 examples, zero failures | every legacy core job plus publish `rake` |
|
|
16
|
+
| Ruby style and legacy syntax | 204 Ruby files, zero RuboCop offenses on Ruby 2.2.10 | every legacy core job plus publish `rake` |
|
|
17
|
+
| Query analysis privacy | PostgreSQL/MySQL allowlist specs and final serialized-payload integration test | contract/unit/integration suite |
|
|
18
|
+
| Normalized-query benchmark | 10,000 iterations with warmup; database inspection disabled | `release-readiness` |
|
|
19
|
+
| Legacy Ruby 2.2.10–2.6.10 | locally exercised on 2.2.10; complete result belongs to tag CI | `legacy-core` matrix |
|
|
20
|
+
| Rails 4.2/5.2 applications | no new support claim; complete result belongs to tag CI | `legacy-rails` matrix |
|
|
21
|
+
| Sidekiq 4/5 applications | no new support claim; complete result belongs to tag CI | `legacy-sidekiq` matrix |
|
|
22
|
+
| Repeatable Rack comparison | executed locally with warmup, five samples, median and MAD | `release-readiness` |
|
|
23
|
+
| Fake endpoint load/privacy | 500-event local gate with complete receipt and no secret leakage | `release-readiness` |
|
|
24
|
+
| Package metadata | gem version, contents, checksum and SPDX SBOM verified locally | publish job |
|
|
25
|
+
| Trusted publication | no local push performed | protected RubyGems environment and OIDC |
|
|
26
|
+
|
|
27
|
+
## Local measurements
|
|
28
|
+
|
|
29
|
+
The 2026-08-05 Ruby 2.2.10 preparation run produced:
|
|
30
|
+
|
|
31
|
+
| Measurement | Result |
|
|
32
|
+
|---|---:|
|
|
33
|
+
| SQL normalization, 10,000 iterations after 1,000 warmup | 453.981 µs/iteration |
|
|
34
|
+
| First bounded static analysis, same fixture | 594.862 µs/iteration |
|
|
35
|
+
| Direct Rack median, 10,000 calls × 5 samples | 0.015203 s; MAD 0.000351 s |
|
|
36
|
+
| Chronos Rack median, same fixture | 0.454667 s; MAD 0.014678 s |
|
|
37
|
+
| Median incremental Rack work | 43.946 µs/request |
|
|
38
|
+
| Fake endpoint | 500/500 received, 1.322240 s, 378.15 events/s |
|
|
39
|
+
|
|
40
|
+
The database-inspection path was not benchmarked because a synthetic adapter would not represent catalog/planner cost. Production opt-in requires an adapter/schema-specific staging measurement.
|
|
41
|
+
|
|
42
|
+
## Release controls
|
|
43
|
+
|
|
44
|
+
- the tag must be exactly `v1.1.0` and match `Chronos::VERSION`;
|
|
45
|
+
- `publish` depends on the legacy core, Rails, Sidekiq, and release-readiness jobs;
|
|
46
|
+
- query inspection is disabled by default and never uses `EXPLAIN ANALYZE` or DDL;
|
|
47
|
+
- all new APM v1 properties remain optional in the schema for older consumers;
|
|
48
|
+
- artifacts contain the gem, SHA-256 checksum, and SPDX SBOM;
|
|
49
|
+
- a failed, skipped, or incomplete supported-matrix job blocks publication.
|
|
50
|
+
|
|
51
|
+
The local artifact is evidence only. Publication must happen through the tag workflow, not with a manual `gem push`.
|
data/docs/security-review.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Security review for 1.
|
|
1
|
+
# Security review for 1.1.0
|
|
2
2
|
|
|
3
|
-
Review date: 2026-
|
|
3
|
+
Review date: 2026-08-05. Scope: capture, serialization, transport, query analysis/inspection, integration verification, remote configuration, framework/job integrations, stable release workflow, examples, and fixtures.
|
|
4
4
|
|
|
5
5
|
Verified by contracts and implementation review:
|
|
6
6
|
|
|
@@ -13,5 +13,9 @@ Verified by contracts and implementation review:
|
|
|
13
13
|
- Active Job propagation uses a namespaced v1 field containing only bounded trace/request identifiers and does not alter job arguments;
|
|
14
14
|
- fixture privacy is enforced by contract tests and dependency advisories are checked by the security workflow.
|
|
15
15
|
- integration verification accepts only a strict correlated response and never exposes raw receiver bodies, credentials, stack traces, paths, SQL, or internal architecture.
|
|
16
|
+
- normalized query analysis is bounded by fingerprint; raw SQL and binds are never transmitted;
|
|
17
|
+
- database inspection is disabled by default, read-only, limited by duration/fingerprint, and never executes `EXPLAIN ANALYZE` or DDL;
|
|
18
|
+
- plan predicates, arbitrary planner text, database rows, and exception messages are excluded; only allowlisted schema/planner fields and error classes remain;
|
|
19
|
+
- new APM properties are additive/optional under protocol v1, and diagnostics/evidence pass through the common sanitizer and serializer limits.
|
|
16
20
|
|
|
17
|
-
Residual risks: supported Ruby/Rails versions are end-of-life; in-memory backlog is lost at exit; application filters/ignore rules execute application code; project identifiers and documented job IDs may be personal data in some deployments; package signing is not enabled because no trusted certificate/key lifecycle exists. Stable artifacts use a protected environment, Trusted Publishing, SHA-256 checksums and SPDX SBOMs until signing can be operated safely.
|
|
21
|
+
Residual risks: supported Ruby/Rails versions are end-of-life; opt-in catalog and planner calls add database work and lack one portable timeout across legacy adapters; static index recommendations remain heuristic; in-memory backlog is lost at exit; application filters/ignore rules execute application code; project/schema identifiers and documented job IDs may be personal data in some deployments; package signing is not enabled because no trusted certificate/key lifecycle exists. Stable artifacts use a protected environment, Trusted Publishing, SHA-256 checksums and SPDX SBOMs until signing can be operated safely.
|
data/docs/troubleshooting.md
CHANGED
|
@@ -74,6 +74,12 @@ This is intentional. The normalizer removes common literal forms and never reads
|
|
|
74
74
|
|
|
75
75
|
Local detectors emit bounded heuristics, not confirmed diagnoses. Repetition can be legitimate and exception class names can be adapter-specific. Confirm the trace in the SaaS and application logs before changing application behavior.
|
|
76
76
|
|
|
77
|
+
## Index recommendation is inaccurate or inspection reports an error
|
|
78
|
+
|
|
79
|
+
An `index_candidate` uses normalized SQL only and has low confidence. `missing_index_candidate` means the observed index prefix was absent from the bounded catalog snapshot; it does not evaluate selectivity, write cost, partial/expression indexes, storage, or production data distribution. Validate with a DBA and the actual workload before creating a migration.
|
|
80
|
+
|
|
81
|
+
Inspection is disabled by default. Enable `apm_query_inspection_enabled` first in staging, keep the fingerprint and duration limits small, then opt into statistics or plans. `query_inspection_failed` contains only the exception class. Unsupported adapters, prepared placeholders, permissions, planner timeouts, or unavailable catalog APIs can cause it. The gem never retries an inspection or falls back to `EXPLAIN ANALYZE`.
|
|
82
|
+
|
|
77
83
|
## Context appears missing
|
|
78
84
|
|
|
79
85
|
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.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Adapters
|
|
3
|
+
# Stores execution context on the current Fiber when Ruby exposes Fiber storage.
|
|
4
|
+
#
|
|
5
|
+
# @responsibility Isolate context between concurrent fibers and restore nested scopes.
|
|
6
|
+
# @motivation Rails 7 applications increasingly multiplex work on one thread.
|
|
7
|
+
# @limits Fiber storage is not propagated automatically to newly-created fibers.
|
|
8
|
+
# @collaborators ContextStore port and Agent composition root.
|
|
9
|
+
# @thread_safety Each fiber owns its value and may be used from concurrent threads.
|
|
10
|
+
# @compatibility Ruby 3.2+ Fiber storage; falls back to thread-local storage otherwise.
|
|
11
|
+
# @example
|
|
12
|
+
# store.with_context(:request_id => "r1") { store.get }
|
|
13
|
+
# @errors Previous context is restored even when the block raises.
|
|
14
|
+
# @performance Constant-time storage operations with a bounded hash merge.
|
|
15
|
+
class FiberLocalContextStore
|
|
16
|
+
def initialize(fallback = ThreadLocalContextStore.new)
|
|
17
|
+
@key = "chronos_context_#{object_id}".to_sym
|
|
18
|
+
@fallback = fallback
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get
|
|
22
|
+
supported? ? (Fiber[@key] || {}) : @fallback.get
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set(context)
|
|
26
|
+
raise ArgumentError, "context must be a Hash" unless context.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
supported? ? Fiber[@key] = context : @fallback.set(context)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def clear
|
|
32
|
+
supported? ? Fiber[@key] = nil : @fallback.clear
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_context(context)
|
|
37
|
+
previous = get
|
|
38
|
+
set(previous.merge(valid_context(context)))
|
|
39
|
+
yield
|
|
40
|
+
ensure
|
|
41
|
+
previous && !previous.empty? ? set(previous) : clear
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def supported?
|
|
47
|
+
Fiber.respond_to?(:[]) && Fiber.respond_to?(:[]=)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def valid_context(context)
|
|
51
|
+
raise ArgumentError, "context must be a Hash" unless context.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
context
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/chronos/agent.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Chronos
|
|
|
11
11
|
# agent.notify(RuntimeError.new("failed"))
|
|
12
12
|
# @errors Capture errors return false; explicit construction requires valid configuration.
|
|
13
13
|
# @performance No worker threads are created until the first asynchronous event.
|
|
14
|
-
class Agent
|
|
14
|
+
class Agent # rubocop:disable Metrics/ClassLength
|
|
15
15
|
DEFAULT_FLUSH_TIMEOUT = 5.0
|
|
16
16
|
|
|
17
17
|
attr_reader :config
|
|
@@ -133,12 +133,23 @@ module Chronos
|
|
|
133
133
|
{
|
|
134
134
|
:enabled => @config.apm_enabled,
|
|
135
135
|
:slow_query_threshold_ms => @config.apm_slow_query_threshold_ms,
|
|
136
|
-
:root_directory => @config.root_directory
|
|
136
|
+
:root_directory => @config.root_directory,
|
|
137
|
+
:query_analysis_enabled => @config.apm_query_analysis_enabled,
|
|
138
|
+
:query_analysis_max_queries => @config.apm_query_analysis_max_queries,
|
|
139
|
+
:query_inspection_enabled => @config.apm_query_inspection_enabled,
|
|
140
|
+
:query_statistics_enabled => @config.apm_query_statistics_enabled,
|
|
141
|
+
:query_plan_enabled => @config.apm_query_plan_enabled,
|
|
142
|
+
:query_inspection_min_duration_ms => @config.apm_query_inspection_min_duration_ms,
|
|
143
|
+
:query_inspection_max_queries => @config.apm_query_inspection_max_queries,
|
|
144
|
+
:transaction_tracking_enabled => @config.apm_transaction_tracking_enabled,
|
|
145
|
+
:transaction_max_connections => @config.apm_transaction_max_connections,
|
|
146
|
+
:transaction_ttl_seconds => @config.apm_trace_ttl_seconds
|
|
137
147
|
}
|
|
138
148
|
end
|
|
139
149
|
|
|
140
150
|
def external_http_integration_options
|
|
141
|
-
{:enabled => @config.external_http_enabled, :trace_headers => @config.external_http_trace_headers
|
|
151
|
+
{:enabled => @config.external_http_enabled, :trace_headers => @config.external_http_trace_headers,
|
|
152
|
+
:w3c_trace_context => @config.w3c_trace_context}
|
|
142
153
|
end
|
|
143
154
|
|
|
144
155
|
def cache_integration_options
|
|
@@ -155,6 +166,10 @@ module Chronos
|
|
|
155
166
|
"request_id" => nested["request_id"] || nested[:request_id] ||
|
|
156
167
|
request["request_id"] || request[:request_id]
|
|
157
168
|
}
|
|
169
|
+
if @config.opentelemetry_bridge
|
|
170
|
+
otel = Integrations::OpenTelemetry.current_context
|
|
171
|
+
values = otel.merge(values.delete_if { |_key, value| value.to_s.empty? })
|
|
172
|
+
end
|
|
158
173
|
values.delete_if { |_key, value| value.to_s.empty? }
|
|
159
174
|
rescue StandardError
|
|
160
175
|
{}
|
|
@@ -227,6 +242,7 @@ module Chronos
|
|
|
227
242
|
|
|
228
243
|
def build_context_store(strategy)
|
|
229
244
|
return Adapters::ThreadLocalContextStore.new if strategy == :thread_local
|
|
245
|
+
return Adapters::FiberLocalContextStore.new if strategy == :fiber_local
|
|
230
246
|
|
|
231
247
|
strategy
|
|
232
248
|
end
|