chronos-ruby 1.1.0 → 1.2.0.pre.2
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 +26 -0
- data/README.md +9 -6
- data/docs/adr/ADR-010-opentelemetry-interoperability.md +3 -3
- data/docs/compatibility.md +10 -16
- data/docs/configuration.md +7 -3
- data/docs/modules/context.md +1 -1
- data/docs/modules/external-http.md +3 -2
- data/lib/chronos/adapters/fiber_local_context_store.rb +57 -0
- data/lib/chronos/agent.rb +7 -1
- data/lib/chronos/configuration/apm_validation.rb +2 -0
- data/lib/chronos/configuration/validation.rb +2 -2
- data/lib/chronos/configuration.rb +6 -2
- 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/rails/error_reporter_subscriber.rb +39 -0
- data/lib/chronos/rails/installer.rb +13 -0
- data/lib/chronos/rails/notifications_subscriber.rb +12 -0
- data/lib/chronos/rails.rb +1 -0
- data/lib/chronos/version.rb +2 -2
- data/lib/chronos.rb +4 -0
- data/lib/generators/chronos/install/templates/chronos.rb +172 -6
- metadata +41 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28ccfc363bc7f73eb18a10d36e0457d8697ffe52dae4f939fa81a4cbe6938609
|
|
4
|
+
data.tar.gz: ac5d64237250a9d9d1e26da86649698dd09eda1afb7a115fb87eb2edf6d3183b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58987b0079af3abcd99ee06f6c2739833719d95ae29ef7764997f37c01745f269521453d610a7bfd7b9081c26593590c469d58d52a502ab752668d1d1d2d4e19
|
|
7
|
+
data.tar.gz: ba3277ee0bbc7fd67186e605505659ae2db7a1add6cad35c08bcf30669b1e7140f5c004ad9ace8154d2a25a72af9d6938aacf2ac04ea0da4dd9924700b0c2df8
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes are documented here. The project follows Semantic Versioning
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.0.pre.2] - 2026-08-11
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- the Rails install generator now writes every public configuration option with explanatory English comments;
|
|
12
|
+
- the official `https://chronosmonitor.com.br` endpoint is now the default host, including when applications omit `config.host`;
|
|
13
|
+
- generated initializers no longer read `CHRONOS_HOST`, while explicit `config.host` assignments remain supported.
|
|
14
|
+
|
|
15
|
+
## [1.2.0.pre.1] - 2026-08-11
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- fiber-aware execution context with a thread-local fallback;
|
|
20
|
+
- Sidekiq 7 and Rails 7 error reporter integration using public extension APIs;
|
|
21
|
+
- Action Cable notification spans and a dependency-free Faraday middleware;
|
|
22
|
+
- optional W3C `traceparent` propagation and an OpenTelemetry bridge that consumes the active span without installing an SDK or exporter.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- the transitional runtime range is Ruby 2.7 through 3.4 and the default context strategy is `:fiber_local`;
|
|
27
|
+
- Active Job and Sidekiq envelopes may propagate allowlisted span ID and trace flags in addition to trace/request IDs.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- W3C identifiers are syntax-validated, all-zero IDs are rejected, and Rails error reporter context is reduced to bounded allowlisted metadata.
|
|
32
|
+
|
|
7
33
|
## [1.1.0] - 2026-08-05
|
|
8
34
|
|
|
9
35
|
### Added
|
data/README.md
CHANGED
|
@@ -24,7 +24,7 @@ A gem não varre variáveis de ambiente, sistema de arquivos ou lockfiles e não
|
|
|
24
24
|
|
|
25
25
|
## Versões Ruby e Rails suportadas
|
|
26
26
|
|
|
27
|
-
A versão 1.
|
|
27
|
+
A versão candidata 1.2.0.pre.2 continua a linha transitional para Ruby 2.7–3.4, Rails 7.x e Sidekiq 7. Ela adiciona contexto por Fiber, Rails Error Reporter, Action Cable, Faraday, Trace Context W3C opcional e coexistência com um SDK OpenTelemetry já configurado. Use-a explicitamente em staging enquanto a matriz transitional é validada; as linhas legadas permanecem disponíveis em releases anteriores.
|
|
28
28
|
|
|
29
29
|
Rubies e frameworks antigos estão fora do suporte de segurança de seus mantenedores. A Chronos oferece compatibilidade técnica, não manutenção de segurança do runtime. Veja [Compatibilidade](docs/compatibility.md).
|
|
30
30
|
|
|
@@ -67,7 +67,8 @@ A Versão 0.5 introduziu Railtie, middleware e subscribers idempotentes por APIs
|
|
|
67
67
|
|
|
68
68
|
## Configuração mínima
|
|
69
69
|
|
|
70
|
-
Obrigatório quando o agente está habilitado: `project_id
|
|
70
|
+
Obrigatório quando o agente está habilitado: `project_id` e `project_key`. O `host` usa
|
|
71
|
+
`https://chronosmonitor.com.br` por padrão. Recomendado: definir ambiente e serviço explicitamente.
|
|
71
72
|
|
|
72
73
|
```ruby
|
|
73
74
|
require "chronos"
|
|
@@ -75,7 +76,7 @@ require "chronos"
|
|
|
75
76
|
Chronos.configure do |config|
|
|
76
77
|
config.project_id = ENV["CHRONOS_PROJECT_ID"]
|
|
77
78
|
config.project_key = ENV["CHRONOS_PROJECT_KEY"]
|
|
78
|
-
config.host = "https://
|
|
79
|
+
config.host = "https://chronosmonitor.com.br"
|
|
79
80
|
config.environment = ENV["APP_ENV"] || "production"
|
|
80
81
|
config.service_name = "billing"
|
|
81
82
|
config.app_version = ENV["APP_VERSION"]
|
|
@@ -204,14 +205,16 @@ Por padrão, SQL bruto e binds não são lidos pelo pipeline de análise. A insp
|
|
|
204
205
|
|
|
205
206
|
## Sidekiq e Active Job
|
|
206
207
|
|
|
207
|
-
A
|
|
208
|
+
A integração mantém o require explícito e suporta a API pública de middleware do Sidekiq 7:
|
|
209
|
+
|
|
210
|
+
O suporte histórico começou em `0.6.0.pre.1`; aplicações em Sidekiq 4/5 devem permanecer numa release legada compatível.
|
|
208
211
|
|
|
209
212
|
```ruby
|
|
210
213
|
gem "sidekiq", "~> 5.0"
|
|
211
214
|
gem "chronos-ruby", "~> 1.1.0", :require => "chronos/sidekiq"
|
|
212
215
|
```
|
|
213
216
|
|
|
214
|
-
O envelope de contexto não altera argumentos públicos e contém somente IDs limitados de trace/request. Active Job usa um campo serializado com namespace (`chronos_context`) e hooks públicos. Erros aninhados são deduplicados e reerguidos. Veja [Sidekiq legado](docs/modules/sidekiq-legacy.md), [Active Job](docs/modules/active-job.md) e [Jobs](docs/modules/job-monitoring.md).
|
|
217
|
+
O envelope de contexto não altera argumentos públicos e contém somente IDs limitados de trace/span/request e flags. Active Job usa um campo serializado com namespace (`chronos_context`) e hooks públicos. Erros aninhados são deduplicados e reerguidos. Veja [Sidekiq legado](docs/modules/sidekiq-legacy.md), [Active Job](docs/modules/active-job.md) e [Jobs](docs/modules/job-monitoring.md).
|
|
215
218
|
|
|
216
219
|
## Deploy tracking
|
|
217
220
|
|
|
@@ -319,7 +322,7 @@ Execute suíte e lint no runtime atual:
|
|
|
319
322
|
bundle _1.17.3_ exec rake
|
|
320
323
|
```
|
|
321
324
|
|
|
322
|
-
A matriz CI cobre
|
|
325
|
+
A matriz CI cobre a linha transitional e os checks de pacote/documentação; workflows legados preservam evidência das releases anteriores.
|
|
323
326
|
|
|
324
327
|
## Contribuição
|
|
325
328
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Aceito para 1.2.
|
|
6
6
|
|
|
7
7
|
## Contexto
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ O Chronos precisa correlacionar trace/request sem duplicar SDKs ou impor depend
|
|
|
10
10
|
|
|
11
11
|
## Decisão
|
|
12
12
|
|
|
13
|
-
Manter IDs de correlação e portas independentes do fornecedor
|
|
13
|
+
Manter IDs de correlação e portas independentes do fornecedor. Na 1.2, a ponte opcional consulta somente o span atual de um SDK já carregado, traduz `trace_id`, `span_id` e flags, e nunca instala SDK, instrumentação ou exporter. IDs Chronos explícitos têm precedência; a presença de OTel deve ser usada pelas integrações para evitar spans paralelos equivalentes.
|
|
14
14
|
|
|
15
15
|
## Alternativas
|
|
16
16
|
|
|
@@ -22,4 +22,4 @@ O protocolo v1 permanece estável e aplicações legadas não recebem novas depe
|
|
|
22
22
|
|
|
23
23
|
## Consequências negativas
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
A ponte não exporta spans OTel completos nem controla exporters; consumidores que precisam desses dados continuam responsáveis pela configuração do SDK.
|
data/docs/compatibility.md
CHANGED
|
@@ -1,39 +1,33 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
Chronos Ruby 1.
|
|
3
|
+
Chronos Ruby 1.2.0.pre.2 is the current candidate of the transitional line for Rails 7 and Sidekiq 7. Combinations remain `Best effort` until their dedicated CI and real-application gates pass. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure. Versions 1.0 and 1.1 remain the frozen legacy line.
|
|
4
4
|
|
|
5
5
|
## Core and Rack
|
|
6
6
|
|
|
7
7
|
| Ruby | Status | Evidence |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| 2.2
|
|
10
|
-
|
|
|
11
|
-
| 2.
|
|
12
|
-
| 2.5.9 | Supported | Same dedicated Docker gate |
|
|
13
|
-
| 2.6.10 | Supported | Same dedicated Docker gate |
|
|
14
|
-
| 2.7 and newer | Unsupported in 1.x legacy | Belongs to the transitional or modern lines |
|
|
9
|
+
| 2.7–3.2 | Best effort | Transitional matrix introduced in this candidate; promotion requires green CI evidence |
|
|
10
|
+
| 3.3–3.4 | Best effort | Core checks run in modern CI; Rails 8-specific behavior belongs to 2.x |
|
|
11
|
+
| Earlier than 2.7 | Unsupported in 1.2 | Use an appropriate frozen legacy release |
|
|
15
12
|
|
|
16
13
|
## Rails
|
|
17
14
|
|
|
18
15
|
| Ruby | Rails | Status | Evidence |
|
|
19
16
|
|---|---|---|---|
|
|
20
|
-
| 2.2
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
| 2.6.10 | 5.2 | Supported | Same dedicated application gate |
|
|
24
|
-
| Other Ruby/Rails pairs | — | Unsupported | No complete release gate; feature detection alone is not a support claim |
|
|
17
|
+
| 2.7–3.2 | 7.0–7.1 | Best effort | Real Rails applications and the complete integration gate are pending |
|
|
18
|
+
| 3.1–3.4 | 7.2 | Best effort | Feature and package checks; use a validated application combination before production rollout |
|
|
19
|
+
| Rails 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
|
|
25
20
|
|
|
26
21
|
## Sidekiq
|
|
27
22
|
|
|
28
23
|
| Ruby | Sidekiq | Status | Evidence |
|
|
29
24
|
|---|---|---|---|
|
|
30
|
-
| 2.
|
|
31
|
-
|
|
|
32
|
-
| Other Ruby/Sidekiq pairs | — | Unsupported | No complete release gate |
|
|
25
|
+
| 2.7–3.4 | 7.x | Best effort | Real Sidekiq 7 smoke applications are pending |
|
|
26
|
+
| Sidekiq 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
|
|
33
27
|
|
|
34
28
|
Active Job uses the public `serialize`, `deserialize`, and `perform_now` extension points with a bounded namespaced field. Support follows the validated Rails pairs above. Adapters that bypass these hooks require their own evidence.
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
Historical evidence remains in [Version 1.0 readiness](release-1.0-readiness.md) and [Version 1.1 readiness](release-1.1-readiness.md).
|
|
37
31
|
|
|
38
32
|
Status meanings:
|
|
39
33
|
|
data/docs/configuration.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|---|---:|---|---|
|
|
7
7
|
| `project_id` | Yes when enabled | `nil` | Public project identifier included in the envelope |
|
|
8
8
|
| `project_key` | Yes when enabled | `nil` | Secret authentication key sent only as an HTTP header |
|
|
9
|
-
| `host` |
|
|
9
|
+
| `host` | Optional | `https://chronosmonitor.com.br` | Absolute Chronos HTTP endpoint; HTTPS is required by default |
|
|
10
10
|
| `environment` | Recommended | `production` | Application environment |
|
|
11
11
|
| `app_version` | Optional | `nil` | Application release/version identifier |
|
|
12
12
|
| `service_name` | Recommended | `nil` | Logical service name |
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
| `sampling_rate` | Optional | `1.0` | Local upper bound for event sampling |
|
|
48
48
|
| `enabled_event_types` | Optional | exception, request, query, job, cache, external_http, dependencies, deploy, metric_batch | Local allowlist for supported event envelopes |
|
|
49
49
|
| `max_remote_send_interval` | Optional | `60.0` | Local upper bound for remotely requested send spacing |
|
|
50
|
-
| `context_store` | Optional | `:
|
|
50
|
+
| `context_store` | Optional | `:fiber_local` | `:fiber_local`, `:thread_local`, or an object implementing `get`, `set`, `clear`, and `with_context` |
|
|
51
51
|
| `breadcrumb_capacity` | Optional | `20` | Positive count of newest breadcrumbs retained per execution |
|
|
52
52
|
| `breadcrumb_max_bytes` | Optional | `2048` | Maximum bytes per normalized breadcrumb; minimum `128` |
|
|
53
53
|
| `rails_enabled` | Optional | `true` | Enables automatic Rails middleware and subscribers |
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
| `apm_transaction_max_connections` | Optional | `100` | Maximum concurrently tracked transaction connections; range 1–500 |
|
|
76
76
|
| `external_http_enabled` | Optional | `false` | Allows explicit per-instance outbound `Net::HTTP` instrumentation |
|
|
77
77
|
| `external_http_trace_headers` | Optional | `true` | Propagates Chronos trace/request headers on instrumented requests |
|
|
78
|
+
| `w3c_trace_context` | Optional | `false` | Injects a validated W3C `traceparent` header when trace and span IDs exist |
|
|
79
|
+
| `opentelemetry_bridge` | Optional | `true` | Consumes the current OpenTelemetry span when an SDK is already active; installs nothing |
|
|
78
80
|
| `cache_key_mode` | Optional | `:none` | `:none` omits keys; `:sha256` emits a project-scoped key hash |
|
|
79
81
|
| `dependency_reporting` | Optional | `true` | Emits one bounded dependency event per configured agent |
|
|
80
82
|
| `dependency_max_items` | Optional | `100` | Loaded gem entries retained in the inventory; range 1–200 |
|
|
@@ -100,7 +102,7 @@ Chronos.configure do |config|
|
|
|
100
102
|
config.max_retries = 3
|
|
101
103
|
config.backlog_size = 100
|
|
102
104
|
config.circuit_failure_threshold = 5
|
|
103
|
-
config.context_store = :
|
|
105
|
+
config.context_store = :fiber_local
|
|
104
106
|
config.breadcrumb_capacity = 20
|
|
105
107
|
config.rails_capture_in_test = false
|
|
106
108
|
config.rails_capture_in_console = false
|
|
@@ -125,6 +127,8 @@ Chronos.configure do |config|
|
|
|
125
127
|
config.apm_transaction_max_connections = 100
|
|
126
128
|
config.external_http_enabled = false
|
|
127
129
|
config.external_http_trace_headers = true
|
|
130
|
+
config.w3c_trace_context = false
|
|
131
|
+
config.opentelemetry_bridge = true
|
|
128
132
|
config.cache_key_mode = :none
|
|
129
133
|
config.dependency_reporting = true
|
|
130
134
|
config.dependency_max_items = 100
|
data/docs/modules/context.md
CHANGED
|
@@ -6,7 +6,7 @@ O contexto relaciona uma exceção ou métrica ao request/job atual sem criar de
|
|
|
6
6
|
|
|
7
7
|
## Fluxo e classes
|
|
8
8
|
|
|
9
|
-
`Chronos::Ports::ContextStore` define `get`, `set`, `clear` e `with_context`. Na linha
|
|
9
|
+
`Chronos::Ports::ContextStore` define `get`, `set`, `clear` e `with_context`. Na linha 1.2, `Chronos::Adapters::FiberLocalContextStore` usa storage do Fiber quando disponível e volta com segurança ao adapter thread-local. A fachada combina o contexto explícito com o escopo atual; o serializer sanitiza e limita tudo antes da fila. Rack limpa o escopo em `ensure`, enquanto Sidekiq e Active Job propagam somente `trace_id`, `span_id`, `trace_flags` e `request_id`.
|
|
10
10
|
|
|
11
11
|
## Extensão, riscos e exemplo
|
|
12
12
|
|
|
@@ -10,6 +10,7 @@ Chronos.configure do |config|
|
|
|
10
10
|
# connection settings omitted
|
|
11
11
|
config.external_http_enabled = true
|
|
12
12
|
config.external_http_trace_headers = true
|
|
13
|
+
config.w3c_trace_context = true
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
http = Net::HTTP.new("payments.example.com", 443)
|
|
@@ -18,10 +19,10 @@ Chronos.instrument_net_http(http)
|
|
|
18
19
|
response = http.request(Net::HTTP::Get.new("/health"))
|
|
19
20
|
```
|
|
20
21
|
|
|
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
|
+
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. With `w3c_trace_context = true`, Net::HTTP and `Chronos::Integrations::FaradayMiddleware` also preserve an existing header or inject a validated `traceparent`. Disable all propagation with `external_http_trace_headers = false`.
|
|
22
23
|
|
|
23
24
|
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
|
|
|
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
|
|
26
|
+
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 is supported through explicit middleware installation; HTTP.rb, Excon, and RestClient remain outside this release.
|
|
26
27
|
|
|
27
28
|
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.
|
|
@@ -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
|
@@ -148,7 +148,8 @@ module Chronos
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
def external_http_integration_options
|
|
151
|
-
{: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}
|
|
152
153
|
end
|
|
153
154
|
|
|
154
155
|
def cache_integration_options
|
|
@@ -165,6 +166,10 @@ module Chronos
|
|
|
165
166
|
"request_id" => nested["request_id"] || nested[:request_id] ||
|
|
166
167
|
request["request_id"] || request[:request_id]
|
|
167
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
|
|
168
173
|
values.delete_if { |_key, value| value.to_s.empty? }
|
|
169
174
|
rescue StandardError
|
|
170
175
|
{}
|
|
@@ -237,6 +242,7 @@ module Chronos
|
|
|
237
242
|
|
|
238
243
|
def build_context_store(strategy)
|
|
239
244
|
return Adapters::ThreadLocalContextStore.new if strategy == :thread_local
|
|
245
|
+
return Adapters::FiberLocalContextStore.new if strategy == :fiber_local
|
|
240
246
|
|
|
241
247
|
strategy
|
|
242
248
|
end
|
|
@@ -110,6 +110,8 @@ module Chronos
|
|
|
110
110
|
errors = []
|
|
111
111
|
errors << "external_http_enabled must be true or false" unless boolean?(external_http_enabled)
|
|
112
112
|
errors << "external_http_trace_headers must be true or false" unless boolean?(external_http_trace_headers)
|
|
113
|
+
errors << "w3c_trace_context must be true or false" unless boolean?(w3c_trace_context)
|
|
114
|
+
errors << "opentelemetry_bridge must be true or false" unless boolean?(opentelemetry_bridge)
|
|
113
115
|
errors << "cache_key_mode must be :none or :sha256" unless [:none, :sha256].include?(cache_key_mode)
|
|
114
116
|
errors << "dependency_reporting must be true or false" unless boolean?(dependency_reporting)
|
|
115
117
|
unless dependency_max_items.is_a?(Integer) && dependency_max_items >= 1 && dependency_max_items <= 200
|
|
@@ -67,8 +67,8 @@ module Chronos
|
|
|
67
67
|
|
|
68
68
|
def context_errors
|
|
69
69
|
errors = []
|
|
70
|
-
unless
|
|
71
|
-
errors << "context_store must be :thread_local or implement get, set, clear, and with_context"
|
|
70
|
+
unless [:thread_local, :fiber_local].include?(context_store) || compatible_context_store?
|
|
71
|
+
errors << "context_store must be :thread_local, :fiber_local, or implement get, set, clear, and with_context"
|
|
72
72
|
end
|
|
73
73
|
errors << "breadcrumb_capacity must be a positive integer" unless positive_integer?(breadcrumb_capacity)
|
|
74
74
|
unless breadcrumb_max_bytes.is_a?(Integer) && breadcrumb_max_bytes >= 128
|
|
@@ -23,6 +23,7 @@ module Chronos
|
|
|
23
23
|
class Configuration
|
|
24
24
|
include Internal::ConfigurationValidation
|
|
25
25
|
include Internal::ApmConfigurationValidation
|
|
26
|
+
DEFAULT_HOST = "https://chronosmonitor.com.br".freeze
|
|
26
27
|
DEFAULT_BLOCKLIST_KEYS = %w(
|
|
27
28
|
password password_confirmation passwd secret api_key apikey authorization
|
|
28
29
|
token access_token refresh_token private_key client_secret cookie set-cookie
|
|
@@ -54,6 +55,7 @@ module Chronos
|
|
|
54
55
|
:apm_query_inspection_min_duration_ms, :apm_query_inspection_max_queries,
|
|
55
56
|
:apm_transaction_tracking_enabled, :apm_transaction_max_connections,
|
|
56
57
|
:external_http_enabled, :external_http_trace_headers,
|
|
58
|
+
:w3c_trace_context, :opentelemetry_bridge,
|
|
57
59
|
:cache_key_mode, :dependency_reporting, :dependency_max_items
|
|
58
60
|
].freeze
|
|
59
61
|
|
|
@@ -105,7 +107,7 @@ module Chronos
|
|
|
105
107
|
def initialize_core_defaults
|
|
106
108
|
@project_id = nil
|
|
107
109
|
@project_key = nil
|
|
108
|
-
@host =
|
|
110
|
+
@host = DEFAULT_HOST
|
|
109
111
|
@environment = "production"
|
|
110
112
|
@app_version = nil
|
|
111
113
|
@service_name = nil
|
|
@@ -123,7 +125,7 @@ module Chronos
|
|
|
123
125
|
@user_agent = "chronos-ruby/#{Chronos::VERSION}"
|
|
124
126
|
@max_payload_size = 1_048_576
|
|
125
127
|
@gzip = false
|
|
126
|
-
@context_store = :
|
|
128
|
+
@context_store = :fiber_local
|
|
127
129
|
@breadcrumb_capacity = 20
|
|
128
130
|
@breadcrumb_max_bytes = 2048
|
|
129
131
|
end
|
|
@@ -167,6 +169,8 @@ module Chronos
|
|
|
167
169
|
def initialize_observability_defaults
|
|
168
170
|
@external_http_enabled = false
|
|
169
171
|
@external_http_trace_headers = true
|
|
172
|
+
@w3c_trace_context = false
|
|
173
|
+
@opentelemetry_bridge = true
|
|
170
174
|
@cache_key_mode = :none
|
|
171
175
|
@dependency_reporting = true
|
|
172
176
|
@dependency_max_items = 100
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Core
|
|
3
|
+
# Parses and formats the W3C Trace Context headers without an OpenTelemetry dependency.
|
|
4
|
+
module TraceContext
|
|
5
|
+
TRACEPARENT = /\A([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})\z/.freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def parse(value)
|
|
10
|
+
match = TRACEPARENT.match(value.to_s.downcase)
|
|
11
|
+
return {} unless match && match[1] == "00"
|
|
12
|
+
return {} if match[2] == ("0" * 32) || match[3] == ("0" * 16)
|
|
13
|
+
|
|
14
|
+
{"trace_id" => match[2], "span_id" => match[3], "trace_flags" => match[4]}
|
|
15
|
+
rescue StandardError
|
|
16
|
+
{}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def format(context)
|
|
20
|
+
trace_id = value(context, "trace_id")
|
|
21
|
+
span_id = value(context, "span_id")
|
|
22
|
+
flags = value(context, "trace_flags")
|
|
23
|
+
return nil unless trace_id =~ /\A[\da-f]{32}\z/ && span_id =~ /\A[\da-f]{16}\z/
|
|
24
|
+
return nil if trace_id == ("0" * 32) || span_id == ("0" * 16)
|
|
25
|
+
|
|
26
|
+
"00-#{trace_id}-#{span_id}-#{flags =~ /\A[\da-f]{2}\z/ ? flags : '01'}"
|
|
27
|
+
rescue StandardError
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def value(context, key)
|
|
32
|
+
return "" unless context.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
(context[key] || context[key.to_sym]).to_s.downcase
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -40,7 +40,7 @@ module Chronos
|
|
|
40
40
|
def envelope(notifier)
|
|
41
41
|
source = notifier.respond_to?(:propagation_context) ? notifier.propagation_context : {}
|
|
42
42
|
source = {} unless source.is_a?(Hash)
|
|
43
|
-
context = %w(trace_id request_id).each_with_object({}) do |key, result|
|
|
43
|
+
context = %w(trace_id span_id trace_flags request_id).each_with_object({}) do |key, result|
|
|
44
44
|
value = source[key] || source[key.to_sym]
|
|
45
45
|
result[key] = bounded(value) unless value.to_s.empty?
|
|
46
46
|
end
|
|
@@ -57,7 +57,7 @@ module Chronos
|
|
|
57
57
|
source = value["context"] || value[:context]
|
|
58
58
|
return {} unless source.is_a?(Hash)
|
|
59
59
|
|
|
60
|
-
%w(trace_id request_id).each_with_object({}) do |key, result|
|
|
60
|
+
%w(trace_id span_id trace_flags request_id).each_with_object({}) do |key, result|
|
|
61
61
|
candidate = source[key] || source[key.to_sym]
|
|
62
62
|
result[key] = bounded(candidate) unless candidate.to_s.empty?
|
|
63
63
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Integrations
|
|
3
|
+
# Optional Faraday middleware for outbound timing and W3C propagation.
|
|
4
|
+
# @responsibility Instrument one explicit Faraday connection without global patches.
|
|
5
|
+
# @motivation Modern Rails applications commonly use Faraday for outbound HTTP.
|
|
6
|
+
# @limits It never reads paths, queries, headers other than trace fields, or bodies.
|
|
7
|
+
# @collaborators Faraday middleware stack, TraceContext, and Chronos facade.
|
|
8
|
+
# @thread_safety Calls retain request state in local variables.
|
|
9
|
+
# @compatibility Faraday 1.x and 2.x middleware contracts.
|
|
10
|
+
# @example builder.use Chronos::Integrations::FaradayMiddleware
|
|
11
|
+
# @errors Original client exceptions are recorded by class and re-raised.
|
|
12
|
+
# @performance Two clock reads and one bounded asynchronous event per request.
|
|
13
|
+
class FaradayMiddleware
|
|
14
|
+
def initialize(app, options = {})
|
|
15
|
+
@app = app
|
|
16
|
+
@notifier = options[:notifier] || Chronos
|
|
17
|
+
@clock = options[:clock] || proc { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(environment)
|
|
21
|
+
started_at = @clock.call
|
|
22
|
+
inject_headers(environment)
|
|
23
|
+
response = @app.call(environment)
|
|
24
|
+
if response.respond_to?(:on_complete)
|
|
25
|
+
response.on_complete { |completed| record(completed, started_at, nil) }
|
|
26
|
+
else
|
|
27
|
+
record(environment, started_at, nil)
|
|
28
|
+
end
|
|
29
|
+
response
|
|
30
|
+
rescue StandardError => error
|
|
31
|
+
record(environment, started_at, error) if started_at
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def inject_headers(environment)
|
|
38
|
+
options = integration_options
|
|
39
|
+
return unless options[:trace_headers] && environment.respond_to?(:request_headers)
|
|
40
|
+
|
|
41
|
+
context = @notifier.respond_to?(:propagation_context) ? @notifier.propagation_context : {}
|
|
42
|
+
headers = environment.request_headers
|
|
43
|
+
traceparent = Core::TraceContext.format(context) if options[:w3c_trace_context]
|
|
44
|
+
headers["traceparent"] ||= traceparent if traceparent
|
|
45
|
+
headers["X-Chronos-Trace-ID"] ||= context["trace_id"] if context["trace_id"]
|
|
46
|
+
headers["X-Chronos-Request-ID"] ||= context["request_id"] if context["request_id"]
|
|
47
|
+
rescue StandardError
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def record(environment, started_at, error)
|
|
52
|
+
url = environment.url if environment.respond_to?(:url)
|
|
53
|
+
payload = {
|
|
54
|
+
"host" => (url.host.to_s if url && url.respond_to?(:host)),
|
|
55
|
+
"method" => (environment.method.to_s.upcase if environment.respond_to?(:method)),
|
|
56
|
+
"status" => (environment.status.to_i if environment.respond_to?(:status) && environment.status),
|
|
57
|
+
"duration_ms" => ((@clock.call - started_at) * 1000.0).round(3),
|
|
58
|
+
"error_class" => (error.class.name.to_s if error)
|
|
59
|
+
}
|
|
60
|
+
payload.delete_if { |_key, value| value.nil? || value == "" }
|
|
61
|
+
@notifier.record_event("external_http", payload)
|
|
62
|
+
rescue StandardError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def integration_options
|
|
67
|
+
return {:enabled => true, :trace_headers => true, :w3c_trace_context => false} unless
|
|
68
|
+
@notifier.respond_to?(:external_http_integration_options)
|
|
69
|
+
|
|
70
|
+
@notifier.external_http_integration_options
|
|
71
|
+
rescue StandardError
|
|
72
|
+
{:enabled => false, :trace_headers => false, :w3c_trace_context => false}
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -53,7 +53,8 @@ module Chronos
|
|
|
53
53
|
{
|
|
54
54
|
:notifier => notifier,
|
|
55
55
|
:clock => options[:clock] || proc { monotonic_time },
|
|
56
|
-
:trace_headers => configured.fetch(:trace_headers, true)
|
|
56
|
+
:trace_headers => configured.fetch(:trace_headers, true),
|
|
57
|
+
:w3c_trace_context => configured.fetch(:w3c_trace_context, false)
|
|
57
58
|
}
|
|
58
59
|
end
|
|
59
60
|
|
|
@@ -106,6 +107,9 @@ module Chronos
|
|
|
106
107
|
end
|
|
107
108
|
set_header(request, "X-Chronos-Trace-ID", context["trace_id"] || context[:trace_id])
|
|
108
109
|
set_header(request, "X-Chronos-Request-ID", context["request_id"] || context[:request_id])
|
|
110
|
+
if options[:w3c_trace_context]
|
|
111
|
+
set_header(request, "traceparent", Core::TraceContext.format(context))
|
|
112
|
+
end
|
|
109
113
|
rescue StandardError
|
|
110
114
|
nil
|
|
111
115
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Integrations
|
|
3
|
+
# Optional, dependency-free bridge to an already configured OpenTelemetry SDK.
|
|
4
|
+
module OpenTelemetry
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def current_context
|
|
8
|
+
return {} unless defined?(::OpenTelemetry::Trace) && ::OpenTelemetry::Trace.respond_to?(:current_span)
|
|
9
|
+
|
|
10
|
+
span = ::OpenTelemetry::Trace.current_span
|
|
11
|
+
context = span.context if span && span.respond_to?(:context)
|
|
12
|
+
return {} unless context && (!context.respond_to?(:valid?) || context.valid?)
|
|
13
|
+
|
|
14
|
+
trace_id = hex_identifier(context, :hex_trace_id, :trace_id, 32)
|
|
15
|
+
span_id = hex_identifier(context, :hex_span_id, :span_id, 16)
|
|
16
|
+
return {} if trace_id.empty? || span_id.empty?
|
|
17
|
+
|
|
18
|
+
{"trace_id" => trace_id, "span_id" => span_id,
|
|
19
|
+
"trace_flags" => sampled?(context) ? "01" : "00", "source" => "opentelemetry"}
|
|
20
|
+
rescue StandardError
|
|
21
|
+
{}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def active?
|
|
25
|
+
!current_context.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def hex_identifier(context, hex_method, numeric_method, width)
|
|
29
|
+
value = context.public_send(hex_method) if context.respond_to?(hex_method)
|
|
30
|
+
value ||= context.public_send(numeric_method).to_i.to_s(16) if context.respond_to?(numeric_method)
|
|
31
|
+
value.to_s.downcase.rjust(width, "0")[-width, width]
|
|
32
|
+
rescue StandardError
|
|
33
|
+
""
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sampled?(context)
|
|
37
|
+
flags = context.trace_flags if context.respond_to?(:trace_flags)
|
|
38
|
+
flags.respond_to?(:sampled?) ? flags.sampled? : flags.to_i.odd?
|
|
39
|
+
rescue StandardError
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -62,7 +62,7 @@ module Chronos
|
|
|
62
62
|
def request_capture_context(env)
|
|
63
63
|
request = request_values(env)
|
|
64
64
|
{
|
|
65
|
-
:context =>
|
|
65
|
+
:context => trace_context(env).merge("request" => request),
|
|
66
66
|
:parameters => parameters(env),
|
|
67
67
|
:user => hash_value(env["chronos.user"])
|
|
68
68
|
}
|
|
@@ -124,7 +124,13 @@ module Chronos
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def trace_id(env)
|
|
127
|
-
env["
|
|
127
|
+
trace_context(env)["trace_id"]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def trace_context(env)
|
|
131
|
+
parsed = Core::TraceContext.parse(env["HTTP_TRACEPARENT"])
|
|
132
|
+
parsed["trace_id"] = env["chronos.trace_id"] || SecureRandom.hex(16) if parsed.empty?
|
|
133
|
+
parsed
|
|
128
134
|
end
|
|
129
135
|
|
|
130
136
|
def response_size(headers)
|
|
@@ -110,7 +110,7 @@ module Chronos
|
|
|
110
110
|
source = @notifier.propagation_context
|
|
111
111
|
return {} unless source.is_a?(Hash)
|
|
112
112
|
|
|
113
|
-
%w(trace_id request_id).each_with_object({}) do |key, result|
|
|
113
|
+
%w(trace_id span_id trace_flags request_id).each_with_object({}) do |key, result|
|
|
114
114
|
value = source[key] || source[key.to_sym]
|
|
115
115
|
result[key] = value.to_s unless value.to_s.empty?
|
|
116
116
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Chronos
|
|
2
|
+
module Rails
|
|
3
|
+
# Sends exceptions observed by the Rails 7 error reporter to Chronos once.
|
|
4
|
+
# @responsibility Translate the public Rails reporter callback into one notice.
|
|
5
|
+
# @motivation Rails can report handled errors that never reach Rack middleware.
|
|
6
|
+
# @limits Only handled, severity, source, and one bounded component are retained.
|
|
7
|
+
# @collaborators ActiveSupport::ErrorReporter and Chronos facade.
|
|
8
|
+
# @thread_safety Instances contain only an immutable notifier reference.
|
|
9
|
+
# @compatibility Rails 7 public error reporter subscriber API.
|
|
10
|
+
# @example Rails.error.subscribe(ErrorReporterSubscriber.new)
|
|
11
|
+
# @errors Agent and context failures are contained and return false.
|
|
12
|
+
# @performance Allocates one small allowlisted metadata hash per report.
|
|
13
|
+
class ErrorReporterSubscriber
|
|
14
|
+
def initialize(notifier = Chronos)
|
|
15
|
+
@notifier = notifier
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def report(error, handled:, severity:, context:, source: nil)
|
|
19
|
+
details = {
|
|
20
|
+
:context => {"rails_error_reporter" => {
|
|
21
|
+
"handled" => handled == true, "severity" => severity.to_s, "source" => source.to_s
|
|
22
|
+
}}
|
|
23
|
+
}
|
|
24
|
+
details[:context]["rails_error_reporter"]["component"] = component(context)
|
|
25
|
+
@notifier.notify_once(error, details)
|
|
26
|
+
rescue StandardError
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def component(context)
|
|
33
|
+
return "" unless context.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
(context[:controller] || context["controller"] || context[:job] || context["job"]).to_s[0, 128]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -34,6 +34,7 @@ module Chronos
|
|
|
34
34
|
|
|
35
35
|
install_middleware(application, options)
|
|
36
36
|
install_active_job
|
|
37
|
+
install_error_reporter
|
|
37
38
|
@subscriber.install
|
|
38
39
|
self.class.applications[application.object_id] = true
|
|
39
40
|
end
|
|
@@ -65,6 +66,18 @@ module Chronos
|
|
|
65
66
|
Chronos::Integrations::ActiveJob.install(::ActiveJob::Base, @notifier)
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
def install_error_reporter
|
|
70
|
+
return false unless defined?(::Rails) && ::Rails.respond_to?(:error)
|
|
71
|
+
|
|
72
|
+
reporter = ::Rails.error
|
|
73
|
+
return false unless reporter && reporter.respond_to?(:subscribe)
|
|
74
|
+
|
|
75
|
+
reporter.subscribe(ErrorReporterSubscriber.new(@notifier))
|
|
76
|
+
true
|
|
77
|
+
rescue StandardError
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
68
81
|
def environment
|
|
69
82
|
defined?(::Rails) && ::Rails.respond_to?(:env) ? ::Rails.env.to_s : nil
|
|
70
83
|
end
|
|
@@ -17,6 +17,7 @@ module Chronos
|
|
|
17
17
|
process_action.action_controller render_template.action_view sql.active_record
|
|
18
18
|
deliver.action_mailer perform.active_job cache_read.active_support
|
|
19
19
|
cache_write.active_support cache_fetch_hit.active_support
|
|
20
|
+
perform_action.action_cable transmit.action_cable broadcast.action_cable
|
|
20
21
|
).freeze
|
|
21
22
|
|
|
22
23
|
@mutex = Mutex.new
|
|
@@ -105,6 +106,8 @@ module Chronos
|
|
|
105
106
|
when "sql.active_record" then sql(payload, duration)
|
|
106
107
|
when "deliver.action_mailer" then mailer(payload, duration)
|
|
107
108
|
when "perform.active_job" then active_job(payload, duration)
|
|
109
|
+
when "perform_action.action_cable", "transmit.action_cable", "broadcast.action_cable"
|
|
110
|
+
action_cable(name, payload, duration)
|
|
108
111
|
else cache(name, payload, duration)
|
|
109
112
|
end
|
|
110
113
|
end
|
|
@@ -345,6 +348,15 @@ module Chronos
|
|
|
345
348
|
@notifier.record_event("cache", data)
|
|
346
349
|
end
|
|
347
350
|
|
|
351
|
+
def action_cable(name, payload, duration)
|
|
352
|
+
data = {
|
|
353
|
+
"kind" => "action_cable", "operation" => name.split(".").first,
|
|
354
|
+
"channel" => safe_class_name(value(payload, :channel)),
|
|
355
|
+
"action" => value(payload, :action).to_s[0, 128], "duration_ms" => duration
|
|
356
|
+
}
|
|
357
|
+
@notifier.record_event("request", data)
|
|
358
|
+
end
|
|
359
|
+
|
|
348
360
|
def capture_controller_exception(payload)
|
|
349
361
|
exception = value(payload, :exception_object)
|
|
350
362
|
details = value(payload, :exception)
|
data/lib/chronos/rails.rb
CHANGED
data/lib/chronos/version.rb
CHANGED
data/lib/chronos.rb
CHANGED
|
@@ -16,6 +16,7 @@ require "chronos/core/sensitive_value_filter"
|
|
|
16
16
|
require "chronos/core/sanitizer"
|
|
17
17
|
require "chronos/core/safe_serializer"
|
|
18
18
|
require "chronos/core/correlation_context"
|
|
19
|
+
require "chronos/core/trace_context"
|
|
19
20
|
require "chronos/core/deploy_normalizer"
|
|
20
21
|
require "chronos/core/payload_serializer"
|
|
21
22
|
require "chronos/core/telemetry_event"
|
|
@@ -32,6 +33,7 @@ require "chronos/internal/memory_backlog"
|
|
|
32
33
|
require "chronos/internal/worker_pool"
|
|
33
34
|
require "chronos/adapters/net_http_transport"
|
|
34
35
|
require "chronos/adapters/thread_local_context_store"
|
|
36
|
+
require "chronos/adapters/fiber_local_context_store"
|
|
35
37
|
require "chronos/core/breadcrumb"
|
|
36
38
|
require "chronos/application/retry_policy"
|
|
37
39
|
require "chronos/application/circuit_breaker"
|
|
@@ -49,6 +51,8 @@ require "chronos/observability_facade"
|
|
|
49
51
|
require "chronos/integrations"
|
|
50
52
|
require "chronos/integrations/rack"
|
|
51
53
|
require "chronos/integrations/rack/middleware"
|
|
54
|
+
require "chronos/integrations/opentelemetry"
|
|
55
|
+
require "chronos/integrations/faraday"
|
|
52
56
|
|
|
53
57
|
# Framework-independent public facade for the Chronos Ruby agent.
|
|
54
58
|
#
|
|
@@ -1,20 +1,186 @@
|
|
|
1
1
|
require "chronos/rails"
|
|
2
2
|
|
|
3
|
+
# This initializer lists every public Chronos option so the application's
|
|
4
|
+
# monitoring behavior is explicit and can be reviewed in one place.
|
|
3
5
|
Chronos.configure do |config|
|
|
4
|
-
#
|
|
6
|
+
# Identification and authentication
|
|
7
|
+
|
|
8
|
+
# Identifies the project that will receive monitoring events.
|
|
5
9
|
config.project_id = ENV["CHRONOS_PROJECT_ID"]
|
|
10
|
+
# Authenticates event delivery. Keep this value in a secret manager.
|
|
6
11
|
config.project_key = ENV["CHRONOS_PROJECT_KEY"]
|
|
7
|
-
|
|
12
|
+
# Defines the Chronos Monitor endpoint used to deliver events.
|
|
13
|
+
config.host = "https://chronosmonitor.com.br"
|
|
14
|
+
# Labels events with the current Rails environment.
|
|
8
15
|
config.environment = Rails.env.to_s
|
|
9
|
-
|
|
16
|
+
# Identifies the deployed application version in events and releases.
|
|
10
17
|
config.app_version = ENV["CHRONOS_APP_VERSION"]
|
|
18
|
+
# Distinguishes this application from other monitored services.
|
|
19
|
+
config.service_name = ENV["CHRONOS_SERVICE_NAME"]
|
|
20
|
+
# Records the source revision, such as a Git commit SHA.
|
|
21
|
+
config.revision = ENV["CHRONOS_REVISION"]
|
|
22
|
+
# Associates events with a specific deployment.
|
|
23
|
+
config.deploy_id = ENV["CHRONOS_DEPLOY_ID"]
|
|
24
|
+
# Identifies the infrastructure region where the application is running.
|
|
25
|
+
config.region = ENV["CHRONOS_REGION"]
|
|
26
|
+
# Identifies the process, container, or host that produced an event.
|
|
27
|
+
config.instance_id = ENV["CHRONOS_INSTANCE_ID"]
|
|
28
|
+
# Removes the application root from file paths reported to Chronos.
|
|
29
|
+
config.root_directory = Rails.root.to_s
|
|
30
|
+
# Sends internal Chronos diagnostics through the Rails logger.
|
|
11
31
|
config.logger = Rails.logger if Rails.respond_to?(:logger)
|
|
12
32
|
|
|
13
|
-
#
|
|
14
|
-
|
|
33
|
+
# Transport and delivery
|
|
34
|
+
|
|
35
|
+
# Limits how long an established request may wait for the Chronos server.
|
|
36
|
+
config.timeout = 5
|
|
37
|
+
# Limits how long Chronos may wait while opening a network connection.
|
|
38
|
+
config.open_timeout = 2
|
|
39
|
+
# Sets the maximum number of events waiting in the in-memory queue.
|
|
40
|
+
config.queue_size = 100
|
|
41
|
+
# Sets the number of background workers that deliver queued events.
|
|
42
|
+
config.workers = 1
|
|
43
|
+
# Enables or disables all Chronos event collection and delivery.
|
|
44
|
+
config.enabled = true
|
|
45
|
+
# Controls whether captured exceptions are sent to Chronos.
|
|
46
|
+
config.error_notifications = true
|
|
47
|
+
# Prevents delivery from the listed application environments.
|
|
48
|
+
config.ignored_environments = []
|
|
49
|
+
# Routes outbound requests through a proxy URL when one is required.
|
|
50
|
+
config.proxy = nil
|
|
51
|
+
# Verifies the TLS certificate presented by the Chronos endpoint.
|
|
52
|
+
config.ssl_verify = true
|
|
53
|
+
# Identifies this SDK in outbound HTTP requests.
|
|
54
|
+
config.user_agent = "chronos-ruby/#{Chronos::VERSION}"
|
|
55
|
+
# Rejects serialized events larger than this number of bytes.
|
|
56
|
+
config.max_payload_size = 1_048_576
|
|
57
|
+
# Compresses request payloads with gzip when enabled.
|
|
58
|
+
config.gzip = false
|
|
59
|
+
|
|
60
|
+
# Privacy and filtering
|
|
61
|
+
|
|
62
|
+
# Removes values whose keys commonly contain credentials or private data.
|
|
63
|
+
config.blocklist_keys = Chronos::Configuration::DEFAULT_BLOCKLIST_KEYS.dup
|
|
64
|
+
# Keeps only the listed keys when this list is not empty.
|
|
65
|
+
config.allowlist_keys = []
|
|
66
|
+
# Applies custom callables that can transform or discard events.
|
|
67
|
+
config.filters = []
|
|
68
|
+
# Replaces values for these keys with stable hashes instead of raw data.
|
|
69
|
+
config.hash_keys = []
|
|
70
|
+
# Removes the final segment of captured IP addresses.
|
|
71
|
+
config.anonymize_ip = true
|
|
72
|
+
# Discards events that match any configured ignore rule.
|
|
73
|
+
config.ignore_rules = []
|
|
74
|
+
# Limits the number of ignore rules evaluated for each event.
|
|
75
|
+
config.max_ignore_rules = 20
|
|
76
|
+
|
|
77
|
+
# Reliability and remote configuration
|
|
78
|
+
|
|
79
|
+
# Sets the number of delivery retries after a recoverable failure.
|
|
80
|
+
config.max_retries = 3
|
|
81
|
+
# Sets the initial delay, in seconds, for exponential retry backoff.
|
|
82
|
+
config.retry_base_interval = 0.5
|
|
83
|
+
# Caps the retry delay, in seconds.
|
|
84
|
+
config.retry_max_interval = 30
|
|
85
|
+
# Randomizes retry delays to prevent synchronized retry traffic.
|
|
86
|
+
config.retry_jitter = 0.25
|
|
87
|
+
# Keeps this many failed events in memory for later delivery attempts.
|
|
88
|
+
config.backlog_size = 100
|
|
89
|
+
# Opens the circuit breaker after this many consecutive failures.
|
|
90
|
+
config.circuit_failure_threshold = 5
|
|
91
|
+
# Waits this many seconds before testing an open circuit again.
|
|
92
|
+
config.circuit_reset_timeout = 30
|
|
93
|
+
# Allows the server to supply bounded runtime configuration updates.
|
|
94
|
+
config.remote_configuration = true
|
|
95
|
+
# Limits the size of an accepted remote configuration response, in bytes.
|
|
96
|
+
config.remote_config_max_bytes = 4_096
|
|
97
|
+
# Captures this fraction of eligible events, from 0.0 to 1.0.
|
|
98
|
+
config.sampling_rate = 1.0
|
|
99
|
+
# Restricts remote collection to these event categories.
|
|
100
|
+
config.enabled_event_types = %w(
|
|
101
|
+
exception request query job cache external_http dependencies deploy metric_batch
|
|
102
|
+
)
|
|
103
|
+
# Caps the server-requested interval between deliveries, in seconds.
|
|
104
|
+
config.max_remote_send_interval = 60
|
|
105
|
+
|
|
106
|
+
# Execution context and breadcrumbs
|
|
107
|
+
|
|
108
|
+
# Isolates trace and request context between concurrent fibers.
|
|
109
|
+
config.context_store = :fiber_local
|
|
110
|
+
# Keeps this many recent breadcrumbs per execution context.
|
|
111
|
+
config.breadcrumb_capacity = 20
|
|
112
|
+
# Limits the serialized size of each breadcrumb, in bytes.
|
|
113
|
+
config.breadcrumb_max_bytes = 2_048
|
|
114
|
+
|
|
115
|
+
# Rails integration
|
|
116
|
+
|
|
117
|
+
# Installs Rails request, job, cache, SQL, and error integrations.
|
|
118
|
+
config.rails_enabled = true
|
|
119
|
+
# Captures events from Rails console sessions when enabled.
|
|
15
120
|
config.rails_capture_in_console = false
|
|
121
|
+
# Captures events while the Rails test environment is running when enabled.
|
|
122
|
+
config.rails_capture_in_test = false
|
|
123
|
+
# Includes the request User-Agent in request metadata when enabled.
|
|
16
124
|
config.rails_capture_user_agent = false
|
|
125
|
+
|
|
126
|
+
# Application performance monitoring
|
|
127
|
+
|
|
128
|
+
# Enables transaction, span, query, and aggregate performance monitoring.
|
|
129
|
+
config.apm_enabled = true
|
|
130
|
+
# Limits the number of metric groups retained between flushes.
|
|
131
|
+
config.apm_max_groups = 200
|
|
132
|
+
# Flushes APM aggregates after this many observations.
|
|
133
|
+
config.apm_flush_count = 100
|
|
134
|
+
# Sends at most this many APM items in one request.
|
|
135
|
+
config.apm_batch_size = 50
|
|
136
|
+
# Limits the SQL queries recorded for a single request or transaction.
|
|
137
|
+
config.apm_max_queries_per_request = 100
|
|
138
|
+
# Marks SQL queries slower than this duration, in milliseconds.
|
|
139
|
+
config.apm_slow_query_threshold_ms = 500
|
|
140
|
+
# Marks transactions longer than this duration, in milliseconds.
|
|
141
|
+
config.apm_long_transaction_threshold_ms = 1_000
|
|
142
|
+
# Reports repeated equivalent queries after this count is reached.
|
|
143
|
+
config.apm_n_plus_one_threshold = 5
|
|
144
|
+
# Defines the duration buckets, in milliseconds, used by APM histograms.
|
|
145
|
+
config.apm_histogram_buckets = [5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000]
|
|
146
|
+
# Retains active trace state for this many seconds.
|
|
147
|
+
config.apm_trace_ttl_seconds = 60
|
|
148
|
+
# Normalizes SQL and records bounded query analysis metadata.
|
|
149
|
+
config.apm_query_analysis_enabled = true
|
|
150
|
+
# Limits the number of analyzed queries retained per transaction.
|
|
151
|
+
config.apm_query_analysis_max_queries = 100
|
|
152
|
+
# Enables database-specific query inspection for slow queries.
|
|
153
|
+
config.apm_query_inspection_enabled = false
|
|
154
|
+
# Collects database query statistics when inspection supports them.
|
|
155
|
+
config.apm_query_statistics_enabled = false
|
|
156
|
+
# Collects query execution plans when inspection supports them.
|
|
157
|
+
config.apm_query_plan_enabled = false
|
|
158
|
+
# Inspects only queries at least this slow, in milliseconds.
|
|
159
|
+
config.apm_query_inspection_min_duration_ms = 500
|
|
160
|
+
# Limits the inspected queries retained per transaction.
|
|
161
|
+
config.apm_query_inspection_max_queries = 20
|
|
162
|
+
# Tracks database transaction lifecycles and durations.
|
|
163
|
+
config.apm_transaction_tracking_enabled = true
|
|
164
|
+
# Limits the database connections tracked for transaction state.
|
|
165
|
+
config.apm_transaction_max_connections = 100
|
|
166
|
+
|
|
167
|
+
# Distributed tracing and integrations
|
|
168
|
+
|
|
169
|
+
# Instruments supported outbound HTTP clients when enabled.
|
|
170
|
+
config.external_http_enabled = false
|
|
171
|
+
# Propagates Chronos trace headers on instrumented outbound requests.
|
|
172
|
+
config.external_http_trace_headers = true
|
|
173
|
+
# Extracts and injects the standard W3C traceparent header when enabled.
|
|
174
|
+
config.w3c_trace_context = false
|
|
175
|
+
# Reuses an active OpenTelemetry span without configuring its SDK.
|
|
176
|
+
config.opentelemetry_bridge = true
|
|
177
|
+
# Controls cache key reporting; :none avoids collecting cache key material.
|
|
178
|
+
config.cache_key_mode = :none
|
|
179
|
+
# Reports a bounded inventory of application dependencies.
|
|
180
|
+
config.dependency_reporting = true
|
|
181
|
+
# Limits the number of dependencies included in an inventory event.
|
|
182
|
+
config.dependency_max_items = 100
|
|
17
183
|
end
|
|
18
184
|
|
|
19
|
-
#
|
|
185
|
+
# Installation is idempotent, regardless of when the Railtie was evaluated.
|
|
20
186
|
Chronos::Rails::Installer.new.install(Rails.application)
|
metadata
CHANGED
|
@@ -1,93 +1,91 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chronos-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0.pre.2
|
|
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-08-
|
|
11
|
+
date: 2026-08-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1
|
|
19
|
+
version: '2.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3'
|
|
20
23
|
type: :development
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.1'
|
|
30
|
+
- - "<"
|
|
25
31
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
32
|
+
version: '3'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: rake
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
|
-
- - "
|
|
37
|
+
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
39
|
version: '12.3'
|
|
34
|
-
- - "
|
|
40
|
+
- - "<"
|
|
35
41
|
- !ruby/object:Gem::Version
|
|
36
|
-
version:
|
|
42
|
+
version: '14'
|
|
37
43
|
type: :development
|
|
38
44
|
prerelease: false
|
|
39
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
46
|
requirements:
|
|
41
|
-
- - "
|
|
47
|
+
- - ">="
|
|
42
48
|
- !ruby/object:Gem::Version
|
|
43
49
|
version: '12.3'
|
|
44
|
-
- - "
|
|
50
|
+
- - "<"
|
|
45
51
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
52
|
+
version: '14'
|
|
47
53
|
- !ruby/object:Gem::Dependency
|
|
48
54
|
name: rspec
|
|
49
55
|
requirement: !ruby/object:Gem::Requirement
|
|
50
56
|
requirements:
|
|
51
|
-
- - "
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.10'
|
|
60
|
+
- - "<"
|
|
52
61
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
62
|
+
version: '4'
|
|
54
63
|
type: :development
|
|
55
64
|
prerelease: false
|
|
56
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
66
|
requirements:
|
|
58
|
-
- - "
|
|
67
|
+
- - ">="
|
|
59
68
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '3.
|
|
69
|
+
version: '3.10'
|
|
70
|
+
- - "<"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '4'
|
|
61
73
|
- !ruby/object:Gem::Dependency
|
|
62
74
|
name: rubocop
|
|
63
75
|
requirement: !ruby/object:Gem::Requirement
|
|
64
76
|
requirements:
|
|
65
77
|
- - "~>"
|
|
66
78
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
79
|
+
version: 1.57.0
|
|
68
80
|
type: :development
|
|
69
81
|
prerelease: false
|
|
70
82
|
version_requirements: !ruby/object:Gem::Requirement
|
|
71
83
|
requirements:
|
|
72
84
|
- - "~>"
|
|
73
85
|
- !ruby/object:Gem::Version
|
|
74
|
-
version:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - '='
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: 1.19.2
|
|
82
|
-
type: :development
|
|
83
|
-
prerelease: false
|
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
-
requirements:
|
|
86
|
-
- - '='
|
|
87
|
-
- !ruby/object:Gem::Version
|
|
88
|
-
version: 1.19.2
|
|
89
|
-
description: Base do cliente Chronos para excecoes, telemetria e observabilidade em
|
|
90
|
-
aplicacoes Ruby legadas.
|
|
86
|
+
version: 1.57.0
|
|
87
|
+
description: Cliente Chronos para excecoes, telemetria e observabilidade em aplicacoes
|
|
88
|
+
Ruby e Rails.
|
|
91
89
|
email:
|
|
92
90
|
- antoniojeferson96@gmail.com
|
|
93
91
|
executables: []
|
|
@@ -171,6 +169,7 @@ files:
|
|
|
171
169
|
- docs/troubleshooting.md
|
|
172
170
|
- lib/chronos.rb
|
|
173
171
|
- lib/chronos/adapters.rb
|
|
172
|
+
- lib/chronos/adapters/fiber_local_context_store.rb
|
|
174
173
|
- lib/chronos/adapters/net_http_transport.rb
|
|
175
174
|
- lib/chronos/adapters/thread_local_context_store.rb
|
|
176
175
|
- lib/chronos/agent.rb
|
|
@@ -210,12 +209,15 @@ files:
|
|
|
210
209
|
- lib/chronos/core/sql_normalizer.rb
|
|
211
210
|
- lib/chronos/core/sql_query_analyzer.rb
|
|
212
211
|
- lib/chronos/core/telemetry_event.rb
|
|
212
|
+
- lib/chronos/core/trace_context.rb
|
|
213
213
|
- lib/chronos/errors.rb
|
|
214
214
|
- lib/chronos/integrations.rb
|
|
215
215
|
- lib/chronos/integrations/active_job.rb
|
|
216
216
|
- lib/chronos/integrations/capistrano.rb
|
|
217
|
+
- lib/chronos/integrations/faraday.rb
|
|
217
218
|
- lib/chronos/integrations/job_payload.rb
|
|
218
219
|
- lib/chronos/integrations/net_http.rb
|
|
220
|
+
- lib/chronos/integrations/opentelemetry.rb
|
|
219
221
|
- lib/chronos/integrations/rack.rb
|
|
220
222
|
- lib/chronos/integrations/rack/middleware.rb
|
|
221
223
|
- lib/chronos/integrations/sidekiq.rb
|
|
@@ -232,6 +234,7 @@ files:
|
|
|
232
234
|
- lib/chronos/ports/transport.rb
|
|
233
235
|
- lib/chronos/rails.rb
|
|
234
236
|
- lib/chronos/rails/active_record_query_inspector.rb
|
|
237
|
+
- lib/chronos/rails/error_reporter_subscriber.rb
|
|
235
238
|
- lib/chronos/rails/installer.rb
|
|
236
239
|
- lib/chronos/rails/notifications_subscriber.rb
|
|
237
240
|
- lib/chronos/rails/railtie.rb
|
|
@@ -258,15 +261,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
258
261
|
requirements:
|
|
259
262
|
- - ">="
|
|
260
263
|
- !ruby/object:Gem::Version
|
|
261
|
-
version: 2.
|
|
264
|
+
version: '2.7'
|
|
262
265
|
- - "<"
|
|
263
266
|
- !ruby/object:Gem::Version
|
|
264
|
-
version: '
|
|
267
|
+
version: '3.5'
|
|
265
268
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
269
|
requirements:
|
|
267
|
-
- - "
|
|
270
|
+
- - ">"
|
|
268
271
|
- !ruby/object:Gem::Version
|
|
269
|
-
version:
|
|
272
|
+
version: 1.3.1
|
|
270
273
|
requirements: []
|
|
271
274
|
rubygems_version: 3.4.22
|
|
272
275
|
signing_key:
|