exis_ray 0.11.0 → 0.11.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/AGENTS.md +238 -0
- data/CHANGELOG.md +8 -0
- data/CLAUDE.md +9 -184
- data/README.md +6 -3
- data/docs/config/configuracion.md +144 -0
- data/docs/interface/interface.md +120 -0
- data/docs/test/testing.md +156 -0
- data/lib/exis_ray/version.rb +1 -1
- data/skill/SKILL.md +5 -2
- data/skills.yml +6 -24
- metadata +8 -5
- data/.gemini/docs/TELEMETRY_GUIDELINES.md +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 833391532fe44ad6a4014ade7f96f43b8f24b32af0c80764d064a2222c8aca59
|
|
4
|
+
data.tar.gz: 0f49d7c49dcaec8ab49ce992798bc79404c1a457c9531207d807faadaae3a1a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0b580fddcadbe0cfc165d7c3e601c184fe31f504cd68be2cddf44b7a0fb661d398c1170534532115a5cb0b18c1c591a749e08868fd0b767d07b966b0a3c1e39
|
|
7
|
+
data.tar.gz: e29e918ecec0e4e7dd155270978bd8c63bda07d447215811bcd7b6901f36b7748fb9a3cd7a2fbe18938c572620e95a9f6305cc3b8805d06011b9f49911499b79
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# ExisRay — Project Intelligence
|
|
2
|
+
|
|
3
|
+
> Fuente de verdad del repositorio. Reglas, convenciones, estructura, entorno y
|
|
4
|
+
> arquitectura viven acá. `CLAUDE.md` queda reservado para notas específicas de
|
|
5
|
+
> Claude Code. Si una regla aplica a cualquier agente, vive en este archivo.
|
|
6
|
+
|
|
7
|
+
## ¿Qué es ExisRay?
|
|
8
|
+
|
|
9
|
+
ExisRay es la capa de observabilidad y trazabilidad distribuida del ecosistema Wispro. Se integra con Rails para emitir logs estructurados en JSON, propagar trace context entre servicios (HTTP, Sidekiq, RabbitMQ), y mantener identidad de negocio (user_id, isp_id, correlation_id) en cada línea de log.
|
|
10
|
+
|
|
11
|
+
El estándar de logging que implementa está definido en `skill/SKILL.md` (API, arquitectura, reglas generales) y `skill/references/standard.md` (Data First, mapeo OpenTelemetry, ciclo de vida). Son la fuente de verdad — cualquier duda sobre formato, campos, o semántica de niveles se resuelve ahí.
|
|
12
|
+
|
|
13
|
+
## Documentación
|
|
14
|
+
|
|
15
|
+
- **Para humanos**: `docs/` + `README.md`. Ver README para índice.
|
|
16
|
+
- **Para agentes AI**: `skill/SKILL.md` + `skill/references/`. Es la skill empaquetada que otros proyectos consumen via `wispro-agent sync`.
|
|
17
|
+
- **Nunca referenciar `skill/` desde `docs/` o `README.md`** — son audiencias distintas.
|
|
18
|
+
|
|
19
|
+
## Mapa de conocimiento (cómo leer la doc de este repo)
|
|
20
|
+
|
|
21
|
+
- **Tu conocimiento = la UNIÓN de este repo + sus asociados.** No termina en el `docs/<capa>/` local: incluye la doc de los servicios/gemas de `skills.yml`. Un flujo que cruza servicios (e2e) **no vive como doc estática** en ningún repo — se compone on-demand recorriendo el grafo (RFC-021): seguí las anclas hasta los repos asociados y unificá.
|
|
22
|
+
- **Entrá por** [`skill/SKILL.md`](skill/SKILL.md) — índice de agente; resume el contrato y linkea el detalle.
|
|
23
|
+
- **Cobertura de capas de este repo** (gema de observabilidad, sin DB, instrumenta al host):
|
|
24
|
+
|
|
25
|
+
| capa | RFC | estado | artefacto / motivo |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| comportamiento | RFC-007 | **presente** | [`docs/behavior/behavior.md`](docs/behavior/behavior.md) — parcial incremental |
|
|
28
|
+
| glosario | RFC-009 | **presente** | [`docs/glossary/glossary.md`](docs/glossary/glossary.md) — sembrado, acreta |
|
|
29
|
+
| test | RFC-013 | **presente** | [`docs/test/testing.md`](docs/test/testing.md) — piloto RFC-013 |
|
|
30
|
+
| configuración | RFC-012 | **presente** | [`docs/config/configuracion.md`](docs/config/configuracion.md) — inventario base; enriquecimiento §f pendiente |
|
|
31
|
+
| datos | RFC-002 | `n/a` | gema sin DB (sin `db/schema.rb`) |
|
|
32
|
+
| operaciones | RFC-003 | `n/a` | no expone superficie propia (HTTP/CLI/eventos) — instrumenta al host |
|
|
33
|
+
| dependencias consumidas | RFC-018 | `n/a` | inyecta hooks de tracing, no consume servicios |
|
|
34
|
+
| eventos | RFC-005 | `n/a` | no produce eventos propios; propaga trace en mensajes ajenos |
|
|
35
|
+
| interfaz | RFC-004 | **presente** | [`docs/interface/interface.md`](docs/interface/interface.md) — superficie pública consumer-facing |
|
|
36
|
+
| topología | RFC-006 | **pendiente** | deps + adapters Sidekiq/BugBunny/Faraday/ActiveResource |
|
|
37
|
+
| release | RFC-014 | **pendiente** | `version.rb` + `CHANGELOG.md` + `.github/workflows/release.yml` (tag `v*` → RubyGems) |
|
|
38
|
+
| errores | RFC-020 | **pendiente** | excepciones de validación de config en el Railtie |
|
|
39
|
+
|
|
40
|
+
- **Navegar una ancla cross-repo:** tomá la key de servicio en `skills.yml` (`services.<dep>.repo`) → ese repo es checkout hermano local o alcanzable por GitHub MCP. La doc de los asociados ES parte de tu conocimiento accesible.
|
|
41
|
+
|
|
42
|
+
## Convenciones del framework
|
|
43
|
+
|
|
44
|
+
- Este repo **consume skills del framework** declaradas en `skills.yml` (manifiesto raíz). Ese archivo enumera los MCPs y las skills que el repo trae al contexto del agente.
|
|
45
|
+
- Las skills sincronizadas en `.agents/skills/` traen **conocimiento de dependencias**. **Leer la skill de una dependencia ANTES de responder sobre ella.**
|
|
46
|
+
- El sync de skills lo hace el CLI: `wispro-agent sync`.
|
|
47
|
+
|
|
48
|
+
## Knowledge Base
|
|
49
|
+
|
|
50
|
+
- Las skills en `.agents/skills/` incluyen conocimiento de dependencias.
|
|
51
|
+
- Leer la skill de una dependencia ANTES de responder sobre ella.
|
|
52
|
+
- Rebuild / sincronización: `wispro-agent sync`.
|
|
53
|
+
|
|
54
|
+
### Entorno
|
|
55
|
+
|
|
56
|
+
- Versión de Ruby: leer `.ruby-version`
|
|
57
|
+
- Versión de Rails y gemas: leer `Gemfile.lock`
|
|
58
|
+
- Gestor de Ruby: chruby (no usar rvm ni rbenv)
|
|
59
|
+
- Package manager: Bundler
|
|
60
|
+
|
|
61
|
+
### RuboCop
|
|
62
|
+
|
|
63
|
+
- Usamos rubocop-rails-omakase como base.
|
|
64
|
+
- Correr `bundle exec rubocop -a` antes de commitear.
|
|
65
|
+
- No deshabilitar cops sin justificación en el PR.
|
|
66
|
+
|
|
67
|
+
### YARD
|
|
68
|
+
|
|
69
|
+
- Documentación incremental: si tocás un método, documentalo con YARD.
|
|
70
|
+
- Consultar la skill `yard` para tags y tipos correctos.
|
|
71
|
+
- Verificar cobertura: `bundle exec yard stats --list-undoc`
|
|
72
|
+
|
|
73
|
+
### Testing
|
|
74
|
+
|
|
75
|
+
- Framework: RSpec
|
|
76
|
+
- Correr: `bundle exec rspec`
|
|
77
|
+
- Todo código nuevo debe tener tests.
|
|
78
|
+
|
|
79
|
+
### Releases o Nuevas versiones
|
|
80
|
+
|
|
81
|
+
- Usar `/gem-release` para publicar nuevas versiones.
|
|
82
|
+
- El GitHub Action publica a RubyGems automáticamente al pushear un tag `v*`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Arquitectura & Componentes
|
|
87
|
+
|
|
88
|
+
### Core
|
|
89
|
+
|
|
90
|
+
- **`ExisRay::Tracer`** — Contexto de trazabilidad distribuida (AWS X-Ray). Extiende `ActiveSupport::CurrentAttributes` para thread-safety. Campos: `trace_id`, `root_id`, `self_id`, `source`, `created_at`, `request_id`, `sidekiq_job`, `task`.
|
|
91
|
+
- **`ExisRay::JsonFormatter`** — Formateador global. Acepta Hash, KV strings (`key=value`) y texto libre. Inyecta automáticamente el contexto del Tracer y del Current en cada línea. Castea valores numéricos y filtra claves sensibles.
|
|
92
|
+
- **`ExisRay::Current`** — Contexto de negocio (user_id, isp_id, correlation_id). Clase abstracta — la app host la subclasifica.
|
|
93
|
+
- **`ExisRay::Reporter`** — Wrapper de Sentry. Clase abstracta — la app host la subclasifica.
|
|
94
|
+
- **`ExisRay::Configuration`** — Configuración global con defaults para AWS X-Ray.
|
|
95
|
+
|
|
96
|
+
### Integraciones HTTP
|
|
97
|
+
|
|
98
|
+
- **`ExisRay::HttpMiddleware`** — Rack middleware. Hidrata el Tracer con el header entrante. Se inserta automáticamente después de `ActionDispatch::RequestId`.
|
|
99
|
+
- **`ExisRay::LogSubscriber`** — Logger nativo de requests HTTP. Reemplaza Lograge. Solo activo con `json_logs: true`.
|
|
100
|
+
- **`ExisRay::FaradayMiddleware`** — Inyecta `propagation_trace_header` en requests salientes via Faraday.
|
|
101
|
+
- **`ExisRay::ActiveResourceInstrumentation`** — Ídem para ActiveResource.
|
|
102
|
+
|
|
103
|
+
### Integraciones Sidekiq
|
|
104
|
+
|
|
105
|
+
- **`ExisRay::Sidekiq::ClientMiddleware`** — Inyecta `exis_ray_trace` en el payload del job antes de encolarlo.
|
|
106
|
+
- **`ExisRay::Sidekiq::ServerMiddleware`** — Hidrata el Tracer al inicio de cada job. Genera root_id nuevo si el job no trae trace.
|
|
107
|
+
|
|
108
|
+
### Integraciones BugBunny (RabbitMQ)
|
|
109
|
+
|
|
110
|
+
- **`ExisRay::BugBunny::PublisherTracing`** — Middleware para `BugBunny::Client`/`BugBunny::Resource`. Inyecta `propagation_trace_header` en cada mensaje publicado.
|
|
111
|
+
- **`ExisRay::BugBunny::ConsumerTracingMiddleware`** — Consumer middleware. Corre antes de todos los logs internos de BugBunny. Hidrata el Tracer desde el header AMQP entrante o genera root_id nuevo.
|
|
112
|
+
- **Railtie hooks** — `rpc_reply_headers` inyecta el trace actualizado en el reply del consumer. `on_rpc_reply` hidrata el Tracer en el thread del publisher al recibir la respuesta.
|
|
113
|
+
|
|
114
|
+
### Procesos en Background
|
|
115
|
+
|
|
116
|
+
- **`ExisRay::TaskMonitor`** — Lifecycle manager para Rake/Cron. Genera root_id, loguea `task_started`/`task_finished` con `duration_s` y `status`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Métodos Clave
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Hidratar el Tracer (HTTP, Sidekiq, BugBunny consumer)
|
|
124
|
+
ExisRay::Tracer.hydrate(trace_id: header_string, source: 'http')
|
|
125
|
+
|
|
126
|
+
# Sincronizar correlation_id al Current configurado
|
|
127
|
+
ExisRay.sync_correlation_id
|
|
128
|
+
|
|
129
|
+
# Generar header de propagación para el siguiente servicio
|
|
130
|
+
ExisRay::Tracer.generate_trace_header
|
|
131
|
+
# => "Root=1-abc123-...;Self=...;CalledFrom=wispro_agent;TotalTimeSoFar=42ms"
|
|
132
|
+
|
|
133
|
+
# Acceder a la configuración
|
|
134
|
+
ExisRay.configuration.propagation_trace_header # => 'X-Amzn-Trace-Id'
|
|
135
|
+
ExisRay.configuration.json_logs? # => true/false
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Campos Auto-Inyectados
|
|
141
|
+
|
|
142
|
+
`JsonFormatter` inyecta estos campos automáticamente. **Nunca** incluirlos manualmente en logs:
|
|
143
|
+
|
|
144
|
+
| Campo | Condición |
|
|
145
|
+
|:------|:----------|
|
|
146
|
+
| `time` | Siempre |
|
|
147
|
+
| `level` | Siempre |
|
|
148
|
+
| `severity_number` | Siempre (OTel: DEBUG=5, INFO=9, WARN=13, ERROR=17, FATAL=21) |
|
|
149
|
+
| `service` | Siempre |
|
|
150
|
+
| `service_version` | Siempre (de `config.version` o `config.x.version`) |
|
|
151
|
+
| `deployment_environment` | Siempre (de `Rails.env`) |
|
|
152
|
+
| `request_id` | Cuando `Tracer.request_id` está presente (independiente de root_id) |
|
|
153
|
+
| `root_id` | Cuando hay trace context activo |
|
|
154
|
+
| `trace_id` | Cuando hay trace context activo |
|
|
155
|
+
| `source` | Cuando hay trace context activo (HTTP siempre genera root fresco si no llega header) |
|
|
156
|
+
| `correlation_id` | Cuando `Current.correlation_id` está presente |
|
|
157
|
+
| `user_id` | Cuando `Current.user_id` está presente |
|
|
158
|
+
| `isp_id` | Cuando `Current.isp_id` está presente |
|
|
159
|
+
| `Current.log_fields` (cualquier key) | Si la subclass overrideó el hook (default `{}`) |
|
|
160
|
+
| `sidekiq_job` | Solo en procesos Sidekiq |
|
|
161
|
+
| `task` | Solo en procesos TaskMonitor |
|
|
162
|
+
| `tags` | Solo si hay Rails tagged logging activo |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Reglas de Ejecución
|
|
167
|
+
|
|
168
|
+
### Logging
|
|
169
|
+
|
|
170
|
+
- Todo log interno usa KV strings: `component=exis_ray event=algo`
|
|
171
|
+
- `component` siempre en `snake_case`
|
|
172
|
+
- DEBUG siempre en block form: `logger.debug { "k=#{v}" }`
|
|
173
|
+
- Nunca `Kernel#warn` ni `$stderr`
|
|
174
|
+
- Toda operación de logging envuelta en `rescue StandardError`
|
|
175
|
+
- Duraciones con `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
|
|
176
|
+
|
|
177
|
+
### Seguridad
|
|
178
|
+
|
|
179
|
+
- Claves sensibles (`password|pass|passwd|secret|token|api_key|auth`) → `[FILTERED]`
|
|
180
|
+
- Nunca loguear PII. Solo `user_id`, `isp_id`
|
|
181
|
+
|
|
182
|
+
### Source válidos
|
|
183
|
+
|
|
184
|
+
`http` | `sidekiq` | `task` | `system`
|
|
185
|
+
|
|
186
|
+
### Propagación de headers
|
|
187
|
+
|
|
188
|
+
- **Entrante HTTP:** `trace_header` (formato Rack: `HTTP_X_AMZN_TRACE_ID`) — solo en `HttpMiddleware`
|
|
189
|
+
- **Saliente (todos los transportes):** `propagation_trace_header` (formato HTTP: `X-Amzn-Trace-Id`)
|
|
190
|
+
|
|
191
|
+
### Prohibiciones
|
|
192
|
+
|
|
193
|
+
- No Lograge — reemplazado por `LogSubscriber`
|
|
194
|
+
- No loguear manualmente: `time`, `level`, `service`, `source`, `root_id`, `trace_id`, `correlation_id`, `sidekiq_job`, `task`
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Compatibilidad Rails
|
|
199
|
+
|
|
200
|
+
- **Reloading:** Usar `cache_classes?` helper (verifica `respond_to?(:enable_reloading)`) para Rails 7.1+
|
|
201
|
+
- **Notifications:** Rails 7.1+ usa `all_listeners_for`, Rails 6/7.0 usa `listeners_for` — siempre usar `respond_to?` guards
|
|
202
|
+
- **Soporte:** Rails 6, 7 y 8
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Integración Automática (Railtie)
|
|
207
|
+
|
|
208
|
+
El `Railtie` en `after_initialize` detecta y auto-instrumenta sin intervención del desarrollador:
|
|
209
|
+
|
|
210
|
+
| Gema detectada | Qué hace |
|
|
211
|
+
|:---------------|:---------|
|
|
212
|
+
| `BugBunny` | Registra `ConsumerTracingMiddleware` y los hooks RPC (`PublisherTracing` va en el cliente, manual) |
|
|
213
|
+
| `Sidekiq` | Registra client + server middleware |
|
|
214
|
+
| `ActiveResource` | Prepend de `ActiveResourceInstrumentation` |
|
|
215
|
+
| `Faraday` | Disponible como middleware opcional |
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Configuración Mínima
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# config/initializers/exis_ray.rb
|
|
223
|
+
ExisRay.configure do |config|
|
|
224
|
+
config.log_format = :json # :text por defecto
|
|
225
|
+
config.trace_header = 'HTTP_X_AMZN_TRACE_ID'
|
|
226
|
+
config.propagation_trace_header = 'X-Amzn-Trace-Id'
|
|
227
|
+
config.current_class = 'Current'
|
|
228
|
+
config.reporter_class = 'Reporter'
|
|
229
|
+
config.emit_legacy_exception_keys = true # default true; pasar a false cuando consumers usen exception.*
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# BugBunny publisher — debe agregarse manualmente al cliente
|
|
233
|
+
BugBunny::Client.new(pool: pool) do |stack|
|
|
234
|
+
stack.use ExisRay::BugBunny::PublisherTracing
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.11.1] - 2026-06-29
|
|
2
|
+
|
|
3
|
+
### Documentación
|
|
4
|
+
- **Capa `config` (RFC-012)**: nuevo `docs/config/configuracion.md` — inventario base (10 opciones de `ExisRay.configure` + `HOSTNAME` + inyecciones del Railtie al host §i + gemas configuradas §j) y enriquecimiento semántico §f (categoría · failure-mode · side-effect · scope-override · business-reason por var, 11/11), §g ramificadores y §h threading. — @gedera
|
|
5
|
+
- **Capa `interface` (RFC-004)**: nuevo `docs/interface/interface.md` — superficie Ruby pública consumer-facing (símbolo · tipo · firma · nota), cerrando la coexistencia transitoria del contrato embebido en `skill/SKILL.md`. — @gedera
|
|
6
|
+
- **Capa `test` (RFC-013)**: `docs/test/testing.md` — piloto del artefacto de test (suites RSpec, fixtures, coverage); status RFC-013 `accepted`. — @gedera
|
|
7
|
+
- **Mapa de conocimiento (RFC-008 r2)** en `AGENTS.md`: tabla de cobertura de las 12 capas (presente / n/a / pendiente). `skill/SKILL.md` y `README.md` recompuestos indexando las capas nuevas; SKILL re-anclado a v0.11.1. Sin cambios de código (`lib/` intacto). — @gedera
|
|
8
|
+
|
|
1
9
|
## [0.11.0] - 2026-05-26
|
|
2
10
|
|
|
3
11
|
### Nuevas funcionalidades
|
data/CLAUDE.md
CHANGED
|
@@ -1,190 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Claude Code Notes
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Fuente de verdad del repositorio
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Las reglas, convenciones, estructura y contexto del proyecto viven en `AGENTS.md`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Leer `AGENTS.md` antes de hacer cambios.
|
|
8
|
+
- Tratar `AGENTS.md` como fuente de verdad para identidad, estructura, documentación, convenciones del framework, entorno, y arquitectura.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Propósito de este archivo
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- **Para agentes AI**: `skill/SKILL.md` + `skill/references/`. Es la skill empaquetada que otros proyectos consumen via `skill-manager sync`.
|
|
13
|
-
- **Nunca referenciar `skill/` desde `docs/` o `README.md`** — son audiencias distintas.
|
|
12
|
+
`CLAUDE.md` queda reservado para instrucciones o notas específicas de Claude Code.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
- Leer la skill de una dependencia ANTES de responder sobre ella.
|
|
18
|
-
- Rebuild: `ruby .agents/skills/skill-manager/scripts/sync.rb`
|
|
19
|
-
|
|
20
|
-
### Entorno
|
|
21
|
-
- Versión de Ruby: leer `.ruby-version`
|
|
22
|
-
- Versión de Rails y gemas: leer `Gemfile.lock`
|
|
23
|
-
- Gestor de Ruby: chruby (no usar rvm ni rbenv)
|
|
24
|
-
- Package manager: Bundler
|
|
25
|
-
|
|
26
|
-
### RuboCop
|
|
27
|
-
- Usamos rubocop-rails-omakase como base.
|
|
28
|
-
- Correr `bundle exec rubocop -a` antes de commitear.
|
|
29
|
-
- No deshabilitar cops sin justificación en el PR.
|
|
30
|
-
|
|
31
|
-
### YARD
|
|
32
|
-
- Documentación incremental: si tocás un método, documentalo con YARD.
|
|
33
|
-
- Consultar la skill `yard` para tags y tipos correctos.
|
|
34
|
-
- Verificar cobertura: `bundle exec yard stats --list-undoc`
|
|
35
|
-
|
|
36
|
-
### Testing
|
|
37
|
-
- Framework: RSpec
|
|
38
|
-
- Correr: `bundle exec rspec`
|
|
39
|
-
- Todo código nuevo debe tener tests.
|
|
40
|
-
|
|
41
|
-
### Releases o Nuevas versiones
|
|
42
|
-
- Usar `/gem-release` para publicar nuevas versiones.
|
|
43
|
-
- El GitHub Action publica a RubyGems automáticamente al pushear un tag `v*`.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Arquitectura & Componentes
|
|
48
|
-
|
|
49
|
-
### Core
|
|
50
|
-
- **`ExisRay::Tracer`** — Contexto de trazabilidad distribuida (AWS X-Ray). Extiende `ActiveSupport::CurrentAttributes` para thread-safety. Campos: `trace_id`, `root_id`, `self_id`, `source`, `created_at`, `request_id`, `sidekiq_job`, `task`.
|
|
51
|
-
- **`ExisRay::JsonFormatter`** — Formateador global. Acepta Hash, KV strings (`key=value`) y texto libre. Inyecta automáticamente el contexto del Tracer y del Current en cada línea. Castea valores numéricos y filtra claves sensibles.
|
|
52
|
-
- **`ExisRay::Current`** — Contexto de negocio (user_id, isp_id, correlation_id). Clase abstracta — la app host la subclasifica.
|
|
53
|
-
- **`ExisRay::Reporter`** — Wrapper de Sentry. Clase abstracta — la app host la subclasifica.
|
|
54
|
-
- **`ExisRay::Configuration`** — Configuración global con defaults para AWS X-Ray.
|
|
55
|
-
|
|
56
|
-
### Integraciones HTTP
|
|
57
|
-
- **`ExisRay::HttpMiddleware`** — Rack middleware. Hidrata el Tracer con el header entrante. Se inserta automáticamente después de `ActionDispatch::RequestId`.
|
|
58
|
-
- **`ExisRay::LogSubscriber`** — Logger nativo de requests HTTP. Reemplaza Lograge. Solo activo con `json_logs: true`.
|
|
59
|
-
- **`ExisRay::FaradayMiddleware`** — Inyecta `propagation_trace_header` en requests salientes via Faraday.
|
|
60
|
-
- **`ExisRay::ActiveResourceInstrumentation`** — Ídem para ActiveResource.
|
|
61
|
-
|
|
62
|
-
### Integraciones Sidekiq
|
|
63
|
-
- **`ExisRay::Sidekiq::ClientMiddleware`** — Inyecta `exis_ray_trace` en el payload del job antes de encolarlo.
|
|
64
|
-
- **`ExisRay::Sidekiq::ServerMiddleware`** — Hidrata el Tracer al inicio de cada job. Genera root_id nuevo si el job no trae trace.
|
|
65
|
-
|
|
66
|
-
### Integraciones BugBunny (RabbitMQ)
|
|
67
|
-
- **`ExisRay::BugBunny::PublisherTracing`** — Middleware para `BugBunny::Client`/`BugBunny::Resource`. Inyecta `propagation_trace_header` en cada mensaje publicado.
|
|
68
|
-
- **`ExisRay::BugBunny::ConsumerTracingMiddleware`** — Consumer middleware. Corre antes de todos los logs internos de BugBunny. Hidrata el Tracer desde el header AMQP entrante o genera root_id nuevo.
|
|
69
|
-
- **Railtie hooks** — `rpc_reply_headers` inyecta el trace actualizado en el reply del consumer. `on_rpc_reply` hidrata el Tracer en el thread del publisher al recibir la respuesta.
|
|
70
|
-
|
|
71
|
-
### Procesos en Background
|
|
72
|
-
- **`ExisRay::TaskMonitor`** — Lifecycle manager para Rake/Cron. Genera root_id, loguea `task_started`/`task_finished` con `duration_s` y `status`.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Métodos Clave
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
# Hidratar el Tracer (HTTP, Sidekiq, BugBunny consumer)
|
|
80
|
-
ExisRay::Tracer.hydrate(trace_id: header_string, source: 'http')
|
|
81
|
-
|
|
82
|
-
# Sincronizar correlation_id al Current configurado
|
|
83
|
-
ExisRay.sync_correlation_id
|
|
84
|
-
|
|
85
|
-
# Generar header de propagación para el siguiente servicio
|
|
86
|
-
ExisRay::Tracer.generate_trace_header
|
|
87
|
-
# => "Root=1-abc123-...;Self=...;CalledFrom=wispro_agent;TotalTimeSoFar=42ms"
|
|
88
|
-
|
|
89
|
-
# Acceder a la configuración
|
|
90
|
-
ExisRay.configuration.propagation_trace_header # => 'X-Amzn-Trace-Id'
|
|
91
|
-
ExisRay.configuration.json_logs? # => true/false
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Campos Auto-Inyectados
|
|
97
|
-
|
|
98
|
-
`JsonFormatter` inyecta estos campos automáticamente. **Nunca** incluirlos manualmente en logs:
|
|
99
|
-
|
|
100
|
-
| Campo | Condición |
|
|
101
|
-
|:------|:----------|
|
|
102
|
-
| `time` | Siempre |
|
|
103
|
-
| `level` | Siempre |
|
|
104
|
-
| `severity_number` | Siempre (OTel: DEBUG=5, INFO=9, WARN=13, ERROR=17, FATAL=21) |
|
|
105
|
-
| `service` | Siempre |
|
|
106
|
-
| `service_version` | Siempre (de `config.version` o `config.x.version`) |
|
|
107
|
-
| `deployment_environment` | Siempre (de `Rails.env`) |
|
|
108
|
-
| `request_id` | Cuando `Tracer.request_id` está presente (independiente de root_id) |
|
|
109
|
-
| `root_id` | Cuando hay trace context activo |
|
|
110
|
-
| `trace_id` | Cuando hay trace context activo |
|
|
111
|
-
| `source` | Cuando hay trace context activo (HTTP siempre genera root fresco si no llega header) |
|
|
112
|
-
| `correlation_id` | Cuando `Current.correlation_id` está presente |
|
|
113
|
-
| `user_id` | Cuando `Current.user_id` está presente |
|
|
114
|
-
| `isp_id` | Cuando `Current.isp_id` está presente |
|
|
115
|
-
| `Current.log_fields` (cualquier key) | Si la subclass overrideó el hook (default `{}`) |
|
|
116
|
-
| `sidekiq_job` | Solo en procesos Sidekiq |
|
|
117
|
-
| `task` | Solo en procesos TaskMonitor |
|
|
118
|
-
| `tags` | Solo si hay Rails tagged logging activo |
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Reglas de Ejecución
|
|
123
|
-
|
|
124
|
-
### Logging
|
|
125
|
-
- Todo log interno usa KV strings: `component=exis_ray event=algo`
|
|
126
|
-
- `component` siempre en `snake_case`
|
|
127
|
-
- DEBUG siempre en block form: `logger.debug { "k=#{v}" }`
|
|
128
|
-
- Nunca `Kernel#warn` ni `$stderr`
|
|
129
|
-
- Toda operación de logging envuelta en `rescue StandardError`
|
|
130
|
-
- Duraciones con `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
|
|
131
|
-
|
|
132
|
-
### Seguridad
|
|
133
|
-
- Claves sensibles (`password|pass|passwd|secret|token|api_key|auth`) → `[FILTERED]`
|
|
134
|
-
- Nunca loguear PII. Solo `user_id`, `isp_id`
|
|
135
|
-
|
|
136
|
-
### Source válidos
|
|
137
|
-
`http` | `sidekiq` | `task` | `system`
|
|
138
|
-
|
|
139
|
-
### Propagación de headers
|
|
140
|
-
- **Entrante HTTP:** `trace_header` (formato Rack: `HTTP_X_AMZN_TRACE_ID`) — solo en `HttpMiddleware`
|
|
141
|
-
- **Saliente (todos los transportes):** `propagation_trace_header` (formato HTTP: `X-Amzn-Trace-Id`)
|
|
142
|
-
|
|
143
|
-
### Prohibiciones
|
|
144
|
-
- No Lograge — reemplazado por `LogSubscriber`
|
|
145
|
-
- No loguear manualmente: `time`, `level`, `service`, `source`, `root_id`, `trace_id`, `correlation_id`, `sidekiq_job`, `task`
|
|
146
|
-
- No referenciar `.gemini/` — obsoleto
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Compatibilidad Rails
|
|
151
|
-
|
|
152
|
-
- **Reloading:** Usar `cache_classes?` helper (verifica `respond_to?(:enable_reloading)`) para Rails 7.1+
|
|
153
|
-
- **Notifications:** Rails 7.1+ usa `all_listeners_for`, Rails 6/7.0 usa `listeners_for` — siempre usar `respond_to?` guards
|
|
154
|
-
- **Soporte:** Rails 6, 7 y 8
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## Integración Automática (Railtie)
|
|
159
|
-
|
|
160
|
-
El `Railtie` en `after_initialize` detecta y auto-instrumenta sin intervención del desarrollador:
|
|
161
|
-
|
|
162
|
-
| Gema detectada | Qué hace |
|
|
163
|
-
|:---------------|:---------|
|
|
164
|
-
| `BugBunny` | Registra `PublisherTracing` no — ese va en el cliente. Registra `ConsumerTracingMiddleware` y los hooks RPC |
|
|
165
|
-
| `Sidekiq` | Registra client + server middleware |
|
|
166
|
-
| `ActiveResource` | Prepend de `ActiveResourceInstrumentation` |
|
|
167
|
-
| `Faraday` | Disponible como middleware opcional |
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Configuración Mínima
|
|
172
|
-
|
|
173
|
-
```ruby
|
|
174
|
-
# config/initializers/exis_ray.rb
|
|
175
|
-
ExisRay.configure do |config|
|
|
176
|
-
config.log_format = :json # :text por defecto
|
|
177
|
-
config.trace_header = 'HTTP_X_AMZN_TRACE_ID'
|
|
178
|
-
config.propagation_trace_header = 'X-Amzn-Trace-Id'
|
|
179
|
-
config.current_class = 'Current'
|
|
180
|
-
config.reporter_class = 'Reporter'
|
|
181
|
-
config.emit_legacy_exception_keys = true # default true; pasar a false cuando consumers usen exception.*
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# BugBunny publisher — debe agregarse manualmente al cliente
|
|
185
|
-
BugBunny::Client.new(pool: pool) do |stack|
|
|
186
|
-
stack.use ExisRay::BugBunny::PublisherTracing
|
|
187
|
-
end
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
---
|
|
14
|
+
- No duplicar aquí reglas generales del repositorio.
|
|
15
|
+
- Si una regla aplica a cualquier agente, moverla a `AGENTS.md`.
|
data/README.md
CHANGED
|
@@ -64,10 +64,13 @@ Artefactos de detalle (RFC-008 — el contrato y el significado viven acá; este
|
|
|
64
64
|
|:-----|:----------|:-------|
|
|
65
65
|
| Comportamiento | [`docs/behavior/behavior.md`](docs/behavior/behavior.md) — secuencias de hidratación de trace por entrypoint y emisión en logs | parcial, incremental por PR |
|
|
66
66
|
| Glosario | [`docs/glossary/glossary.md`](docs/glossary/glossary.md) — lenguaje ubicuo (`root_id`, `trace_id`, `source`, `request_id`, `entrypoint`, ...) | sembrado inicial, acreta |
|
|
67
|
-
|
|
|
68
|
-
|
|
|
67
|
+
| Configuración | [`docs/config/configuracion.md`](docs/config/configuracion.md) — opciones de `ExisRay.configure`, `HOSTNAME` e inyecciones del Railtie al host | inventario base + enriquecimiento §f |
|
|
68
|
+
| Test | [`docs/test/testing.md`](docs/test/testing.md) — suites RSpec, fixtures y coverage | piloto RFC-013 |
|
|
69
|
+
| Interfaz | [`docs/interface/interface.md`](docs/interface/interface.md) — superficie Ruby pública (símbolo · tipo · firma) | completa (consumer-facing) |
|
|
70
|
+
| Datos · Operaciones · Eventos · Consumidas | — | n/a (gema sin DB, no expone superficie propia ni consume servicios) |
|
|
71
|
+
| Topología · Release · Errores | — | pendiente (ver mapa de cobertura en `AGENTS.md`) |
|
|
69
72
|
|
|
70
|
-
> **
|
|
73
|
+
> **Nota:** las secciones _Cómo funciona_, _Campos auto-inyectados_ y _Referencia del Tracer_ de este README son material humano de onboarding; el contrato formal de la superficie pública vive en [`docs/interface`](docs/interface/interface.md), el significado y las secuencias en `docs/glossary` y `docs/behavior`.
|
|
71
74
|
|
|
72
75
|
## Instalación
|
|
73
76
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Configuración — exis_ray
|
|
2
|
+
|
|
3
|
+
> meta: artefacto · RFC-012 (12-Factor App §III, shape v2.2) · generado arch-structure + arch-enrich · anclado a commit `7db39ca` (v0.11.0) · cobertura **inventario base completa · §f enriquecido 11/11 · §j mapeado 2/2**
|
|
4
|
+
|
|
5
|
+
## 1. Resumen
|
|
6
|
+
|
|
7
|
+
Gema de tracing/observabilidad. Configuración pública vía `ExisRay.configure do |config| ... end` (clase `ExisRay::Configuration`, 10 opciones con defaults compatibles AWS X-Ray) + 1 env var de runtime (`HOSTNAME`). Sin scheduler propio; el `Railtie` inyecta middlewares, formatter de logs e instrumentación de Sidekiq/BugBunny/ActiveResource al host durante el boot.
|
|
8
|
+
|
|
9
|
+
## 2. Cuerpo
|
|
10
|
+
|
|
11
|
+
### §a Hecho verificable
|
|
12
|
+
|
|
13
|
+
| métrica | conteo |
|
|
14
|
+
|---|---|
|
|
15
|
+
| total vars/opciones | 11 |
|
|
16
|
+
| requeridas | 0 |
|
|
17
|
+
| con default | 11 |
|
|
18
|
+
| derivadas | 2 |
|
|
19
|
+
| secretas | 0 |
|
|
20
|
+
|
|
21
|
+
### §b Inventario base
|
|
22
|
+
|
|
23
|
+
| nombre | tipo | requerida | default | origen | consumidor (file:line) | secret? |
|
|
24
|
+
|---|---|---|---|---|---|---|
|
|
25
|
+
| `trace_header` | String | no | `"HTTP_X_AMZN_TRACE_ID"` | code-default | `configuration.rb:76` | no |
|
|
26
|
+
| `propagation_trace_header` | String | no | `"X-Amzn-Trace-Id"` | code-default | `configuration.rb:77` | no |
|
|
27
|
+
| `reporter_class` | String/Class/nil | no | `nil` | code-default | `configuration.rb:78` | no |
|
|
28
|
+
| `current_class` | String/Class/nil | no | `nil` | code-default | `configuration.rb:79` | no |
|
|
29
|
+
| `log_format` | Symbol | no | `:text` | code-default | `configuration.rb:80` | no |
|
|
30
|
+
| `log_subscriber_class` | String/nil | no | `nil` | code-default | `configuration.rb:81` | no |
|
|
31
|
+
| `service_version` | String/nil | no | `derived(Rails config.version ∥ config.x.version)` | derived | `configuration.rb:82,93-111` | no |
|
|
32
|
+
| `deployment_environment` | String/nil | no | `derived(Rails.env)` | derived | `configuration.rb:83,113-117` | no |
|
|
33
|
+
| `emit_legacy_exception_keys` | Boolean | no | `true` | code-default | `configuration.rb:84` | no |
|
|
34
|
+
| `emit_legacy_path_key` | Boolean | no | `true` | code-default | `configuration.rb:85` | no |
|
|
35
|
+
| `HOSTNAME` | String | no | `"local"` (fallback) | env | `task_monitor.rb:81` | no |
|
|
36
|
+
|
|
37
|
+
> Solo shape — sin valores reales (RFC-012 §3). `requerida=no` en las 10 opciones: el `initialize` siembra default a todas; `HOSTNAME` cae a `"local"` si ausente.
|
|
38
|
+
|
|
39
|
+
### §c Meta-templates
|
|
40
|
+
|
|
41
|
+
`n/a` — sin patrón de sufijo/prefijo repetido ≥3 (no hay service discovery estática `_HOST/_PORT/_PROTOCOL`).
|
|
42
|
+
|
|
43
|
+
### §d Derivaciones simples
|
|
44
|
+
|
|
45
|
+
| var derivada | fórmula | fuente |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `service_version` | `Rails.application.config.version` → fallback `config.x.version` → `nil` | `configuration.rb:93-111` |
|
|
48
|
+
| `deployment_environment` | `Rails.env.to_s` | `configuration.rb:113-117` |
|
|
49
|
+
| `pod_id` (no-config) | `(ENV["HOSTNAME"] ∥ "local").split("-").last` | `task_monitor.rb:81` |
|
|
50
|
+
|
|
51
|
+
### §e Scheduling
|
|
52
|
+
|
|
53
|
+
`n/a` — la gema no define `sidekiq.yml`/`queue.yml`/`recurring.yml` ni queues/cron propios. **Instrumenta** el Sidekiq del host (middlewares) pero no agenda trabajo — ver §i.
|
|
54
|
+
|
|
55
|
+
### §i Inyecciones al host (Railtie)
|
|
56
|
+
|
|
57
|
+
| inyección | gatillo | efecto | file:line |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `exis_ray.configure_middleware` | initializer | inserta `ExisRay::HttpMiddleware` after `ActionDispatch::RequestId` (fallback `use`) | `railtie.rb:14-23` |
|
|
60
|
+
| `exis_ray.configure_logging` | initializer (after `:load_config_initializers`) | en modo texto: `app.config.log_tags << proc { trace_id ∥ root_id }` | `railtie.rb:28-36` |
|
|
61
|
+
| validación de clases | `config.after_initialize` | `raise` si `current_class`/`reporter_class` no heredan de base | `railtie.rb:45-53` |
|
|
62
|
+
| formatter JSON global | `config.after_initialize` (modo json) | `Rails.logger.formatter = ExisRay::JsonFormatter.new` | `railtie.rb:57` |
|
|
63
|
+
| LogSubscriber HTTP | `config.after_initialize` (modo json) | `ExisRay::LogSubscriber.install!` (reemplaza Lograge/defaults Rails) | `railtie.rb:62` |
|
|
64
|
+
| instrumentación BugBunny | `config.after_initialize` (si `defined?(::BugBunny)`) | consumer middleware + `rpc_reply_headers` + `on_rpc_reply` | `railtie.rb:68-90` |
|
|
65
|
+
| instrumentación ActiveResource | `config.after_initialize` (si `defined?(ActiveResource::Base)`) | `prepend ExisRay::ActiveResourceInstrumentation` | `railtie.rb:93-99` |
|
|
66
|
+
| instrumentación Sidekiq | `config.after_initialize` (si `defined?(::Sidekiq)`) | client/server middleware + `Sidekiq.logger.formatter` (modo json) | `railtie.rb:102-123` |
|
|
67
|
+
|
|
68
|
+
### §j Inyección a gemas configuradas
|
|
69
|
+
|
|
70
|
+
| gema configurada | superficie tocada | file:line | mapeo opción local → gema | ancla |
|
|
71
|
+
|---|---|---|---|---|
|
|
72
|
+
| `::BugBunny` | `consumer_middlewares.use ConsumerTracingMiddleware`; `configuration.rpc_reply_headers`; `configuration.on_rpc_reply` | `railtie.rb:71-88` | `propagation_trace_header` → key del header en `rpc_reply_headers` (`railtie.rb:75`) y lectura en `on_rpc_reply` (`railtie.rb:79`). El middleware/consumer no mapea opción de valor. | **integración opcional** (guard `defined?(::BugBunny)`), **no es dependencia declarada** (ausente de gemspec/Gemfile.lock) → sin ancla cross-repo, no aplica `skills.yml` |
|
|
73
|
+
| `::Sidekiq` | `configure_client` / `configure_server` (middleware chains); `Sidekiq.logger.formatter` | `railtie.rb:106-121` | `log_format=:json` (vía `json_logs?`) → `Sidekiq.logger.formatter = ExisRay::JsonFormatter.new` (`railtie.rb:121`). Las chains de middleware no mapean opción de valor. | **integración opcional** (guard `defined?(::Sidekiq)`), no es dependencia declarada → sin `docs/config/` a linkear |
|
|
74
|
+
|
|
75
|
+
## f. Enriquecimiento semántico
|
|
76
|
+
|
|
77
|
+
> cobertura: 11/11 vars enriquecidas; ausencia ≠ "no aplica".
|
|
78
|
+
|
|
79
|
+
### f.1 Propagación de trace context (`*trace_header`)
|
|
80
|
+
|
|
81
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
82
|
+
|---|---|---|---|---|---|
|
|
83
|
+
| `trace_header` | integration | silent-default (cae al header AWS X-Ray si no se setea) | per-request (leído por request) | boot-only | key del header **Rack entrante** que `HttpMiddleware` lee para hidratar el Tracer (`http_middleware.rb:13`); cambiarla solo si el edge/LB usa otro header de traza |
|
|
84
|
+
| `propagation_trace_header` | integration | silent-default | per-request | boot-only | key del header **saliente** que inyectan Faraday/ActiveResource/BugBunny-publisher + RPC reply (`faraday_middleware.rb:20`, `active_resource_instrumentation.rb:31`, `bug_bunny/publisher_tracing.rb:33`, `railtie.rb:75,79`); debe coincidir con el `trace_header` (forma HTTP) del downstream o la cadena se corta |
|
|
85
|
+
|
|
86
|
+
### f.2 Wiring de clases del host (`*_class`)
|
|
87
|
+
|
|
88
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
89
|
+
|---|---|---|---|---|---|
|
|
90
|
+
| `current_class` | orchestration | boot-crash @ after-initialize si la clase no hereda de `ExisRay::Current` (`railtie.rb:45-47`); silent-default (`nil` → contexto de negocio no-op) si no existe | restart (prod: memoizado `@current_class_cache`, `exis_ray.rb:78`) · per-request (dev: re-resuelto para Zeitwerk reload, `exis_ray.rb:80`) | boot-only en prod · mutable-singleton en dev | clase del host que provee `user_id`/`isp_id`/`correlation_id`; se pasa como String para evitar `uninitialized constant` en boot |
|
|
91
|
+
| `reporter_class` | orchestration | boot-crash @ after-initialize si no hereda de `ExisRay::Reporter` (`railtie.rb:50-52`); silent-default (`nil` → no se reporta a Sentry) | restart · per-request (idem `current_class`, `exis_ray.rb:95-97`) | boot-only en prod · mutable-singleton en dev | clase del host que envía errores a Sentry con trace context; consumida por TaskMonitor/middlewares al rescatar |
|
|
92
|
+
| `log_subscriber_class` | observability | silent-default (`nil` → `ExisRay::LogSubscriber` base sin `extra_fields`) | restart (`install!` en after_initialize, `log_subscriber.rb:295`) | boot-only | subclase para inyectar `extra_fields` en el log de cierre HTTP; **solo aplica si `json_logs?`** (ver §g) |
|
|
93
|
+
|
|
94
|
+
### f.3 Estrategia de logging (`log_format`)
|
|
95
|
+
|
|
96
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
97
|
+
|---|---|---|---|---|---|
|
|
98
|
+
| `log_format` | observability | silent-default (`:text` → tags de Rails, sin JsonFormatter) | restart (decisión tomada en boot tras `load_config_initializers`, `railtie.rb:28-29`) | boot-only | conmuta **toda** la estrategia: `:json` instala JsonFormatter global + LogSubscriber + formatter de Sidekiq (`railtie.rb:56-65,121`); `:text` solo agrega `root_id` como `log_tag`. **Ramificador** (§g). |
|
|
99
|
+
|
|
100
|
+
### f.4 Metadata de recurso OTel
|
|
101
|
+
|
|
102
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
103
|
+
|---|---|---|---|---|---|
|
|
104
|
+
| `service_version` | observability | silent-default (`nil` → campo `service_version` omitido del log) | per-request (emitido por línea, `json_formatter.rb:89`) | boot-only (computado una vez de Rails config) | equivale a `service.version` de OTel |
|
|
105
|
+
| `deployment_environment` | observability | silent-default (`nil` → campo omitido) | per-request (`json_formatter.rb:94`) | boot-only (derivado de `Rails.env`) | equivale a `deployment.environment` de OTel |
|
|
106
|
+
|
|
107
|
+
### f.5 Feature-flags de migración OTel (transitorios)
|
|
108
|
+
|
|
109
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
110
|
+
|---|---|---|---|---|---|
|
|
111
|
+
| `emit_legacy_exception_keys` | feature-flag | silent-default (`true` → emite `error_class`/`error_message` además de `exception.*`) | per-request en error (`log_subscriber.rb:156`, `task_monitor.rb:138`) | boot-only | flag transitorio ventana OTel v1.0. **Ramp:** partial (default `true`). **Cleanup:** cuando dashboards/alertas/queries migren a `exception.*`. Roadmap: default `false` en v0.12.0, removido en v1.0 |
|
|
112
|
+
| `emit_legacy_path_key` | feature-flag | silent-default (`true` → emite `path` además de `url.path`) | per-request (`log_subscriber.rb:115`) | boot-only | flag transitorio. **Ramp:** partial. **Cleanup:** cuando consumers migren a `url.path`. Roadmap: default `false` en v0.12.0, removido en v1.0 |
|
|
113
|
+
|
|
114
|
+
### f.6 Identidad de pod (`HOSTNAME`)
|
|
115
|
+
|
|
116
|
+
| var | categoría | failure-mode | side-effect | scope-override | business-reason / definición |
|
|
117
|
+
|---|---|---|---|---|---|
|
|
118
|
+
| `HOSTNAME` | infra | silent-default (`"local"` si ausente) | per-task (leído al generar root en TaskMonitor, `task_monitor.rb:81`) | boot-only (env del contenedor) | sufijo del hostname (post último `-`) como `pod_id` en el `root_id` de tasks; en K8s/Docker lo inyecta el orquestador |
|
|
119
|
+
|
|
120
|
+
**Threading (§h):** los callbacks RPC de BugBunny (`on_rpc_reply`, `railtie.rb:77-88`) corren en el **thread del publisher** y resuelven `reporter_class`/`current_class` vía los helpers memoizados. `ExisRay::Tracer`/`Current` heredan `ActiveSupport::CurrentAttributes` (thread/fiber-local). Constraint: el valor de config es un singleton global; las clases resueltas se comparten entre threads — safe porque son class objects inmutables tras boot.
|
|
121
|
+
|
|
122
|
+
**Ramificadores intra-config (§g):**
|
|
123
|
+
|
|
124
|
+
- `log_format=:json` (vía `json_logs?`) ramifica la aplicabilidad de **`log_subscriber_class`** (inerte en `:text` — `install!` solo corre en modo json) y dispara `JsonFormatter` global + `Sidekiq.logger.formatter` (`railtie.rb:56-65,121`).
|
|
125
|
+
- `emit_legacy_exception_keys` / `emit_legacy_path_key` ramifican **qué keys se emiten** en el log, no la aplicabilidad de otras vars.
|
|
126
|
+
|
|
127
|
+
## 3. Inferencias
|
|
128
|
+
|
|
129
|
+
| ítem | confidence | a verificar |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `HOSTNAME` clasificada `requerida=no` | declared | fallback `"local"` explícito en `task_monitor.rb:81` — confirmado |
|
|
132
|
+
| `service_version`/`deployment_environment` como `derived` | declared | defaults computados en `initialize` vía `default_*` — confirmado |
|
|
133
|
+
| sin secretos | declared | ninguna opción matchea `*_KEY|*_SECRET|*_PASS|*_TOKEN` ni literal sensible — confirmado |
|
|
134
|
+
| `reporter_class`/`current_class` aceptan String o Class | declared | YARD `@return [String, Class, nil]`; el `safe_constantize` del Railtie asume String — verificar uso real |
|
|
135
|
+
| `::BugBunny`/`::Sidekiq` sin ancla cross-repo (§j) | declared | son **integraciones opcionales** (guard `defined?`), no dependencias declaradas (ausentes de gemspec/Gemfile.lock) — la gema inyecta hooks si el host las tiene. Por eso NO van en `skills.yml`: no hay relación de consumo que anclar |
|
|
136
|
+
| `scope-override` boot-only vs mutable-singleton de `*_class` (§f.2) | inferred | depende de `cache_classes?` (`exis_ray.rb:118-128`): prod memoiza (boot-only), dev re-resuelve por request (mutable). Verificar que el host no mute `configuration.*_class` post-boot |
|
|
137
|
+
|
|
138
|
+
## 4. Cobertura y fronteras
|
|
139
|
+
|
|
140
|
+
- **Inventario base completo** para v0.11.0 — las 11 vars/opciones del código están listadas.
|
|
141
|
+
- **Enriquecimiento §f completo 11/11** — categoría · failure-mode · side-effect · scope-override · business-reason por opción, + threading (§h) y ramificadores (§g). Anclado al consumidor real (`file:line`); las inferencias quedan en §3 para verificación humana.
|
|
142
|
+
- **Mapeo §j → gema completo 2/2** — `propagation_trace_header`→BugBunny RPC headers; `log_format`→Sidekiq formatter. Ancla cross-repo de BugBunny pendiente (no declarada en `skills.yml`, ver §3).
|
|
143
|
+
- **Fuera de alcance:** `ENV["TENANT_ID"]` en `current.rb:22` es **ejemplo en comentario YARD** (no consumo real) → no se lista. Vars de Rails core (`SECRET_KEY_BASE`, `RAILS_ENV`, etc.) las aporta la app host, no esta gema.
|
|
144
|
+
- **`config.x.version`** (lectura de `service_version`) depende del host — la gema solo lee, no define.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Interfaz — exis_ray
|
|
2
|
+
|
|
3
|
+
> meta: artefacto · RFC-004 (RBS firmas como modelo conceptual) · generado arch-structure · anclado a commit `b7b96c7` (v0.11.0) · cobertura **superficie pública consumer-facing completa**
|
|
4
|
+
|
|
5
|
+
## 1. Resumen
|
|
6
|
+
|
|
7
|
+
API Ruby pública de la gema: módulo `ExisRay` (config + resolución de clases host), 2 bases abstractas que el host subclasea (`Current`, `Reporter`), el contexto de trazabilidad (`Tracer`), el formatter/subscriber de logs, el monitor de tasks, y 7 middlewares de propagación (HTTP/Faraday/ActiveResource/Sidekiq×2/BugBunny×2). Las clases configurables (`Configuration`) se documentan en detalle en [`docs/config/configuracion.md`](../config/configuracion.md).
|
|
8
|
+
|
|
9
|
+
## 2. Superficie pública (símbolo · tipo · firma · nota)
|
|
10
|
+
|
|
11
|
+
### `ExisRay` (módulo raíz)
|
|
12
|
+
|
|
13
|
+
| símbolo | tipo | firma | nota |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `ExisRay::VERSION` | const | `String` | `"0.11.0"` (`version.rb:5`) |
|
|
16
|
+
| `ExisRay::Error` | class | `< StandardError` | error base de la gema (`exis_ray.rb:36`) |
|
|
17
|
+
| `ExisRay.configure` | method | `() { (Configuration) -> void } -> void` | bloque de configuración (`exis_ray.rb:60`) |
|
|
18
|
+
| `ExisRay.configuration` | method | `() -> Configuration` | instancia singleton (`exis_ray.rb:46`) |
|
|
19
|
+
| `ExisRay.configuration=` | writer | `(Configuration) -> void` | reemplaza la config — uso en tests (`exis_ray.rb:40`) |
|
|
20
|
+
| `ExisRay.current_class` | method | `() -> Class?` | resuelve `config.current_class`; memoizado en prod, re-resuelto en dev (`exis_ray.rb:71`) |
|
|
21
|
+
| `ExisRay.reporter_class` | method | `() -> Class?` | resuelve `config.reporter_class` (`exis_ray.rb:88`) |
|
|
22
|
+
| `ExisRay.sync_correlation_id` | method | `() -> void` | copia `Tracer.correlation_id` → `Current.correlation_id` (`exis_ray.rb:105`) |
|
|
23
|
+
|
|
24
|
+
### `ExisRay::Configuration` (clase)
|
|
25
|
+
|
|
26
|
+
| símbolo | tipo | firma | nota |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `Configuration#<attr>` | accessor (rw) | — | 10 opciones (`trace_header`, `log_format`, `current_class`, ...) — inventario y semántica en [`docs/config/`](../config/configuracion.md) |
|
|
29
|
+
| `Configuration#json_logs?` | method | `() -> bool` | `true` si `log_format == :json` (`configuration.rb:122`) |
|
|
30
|
+
|
|
31
|
+
### `ExisRay::Tracer` (`< ActiveSupport::CurrentAttributes`)
|
|
32
|
+
|
|
33
|
+
| símbolo | tipo | firma | nota |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| `Tracer.<attr>` | accessor (rw) | — | `trace_id · request_id · root_id · self_id · called_from · total_time_so_far · created_at · sidekiq_job · source · task` (`tracer.rb:15`) |
|
|
36
|
+
| `Tracer.hydrate` | method | `(trace_id:, source:) -> void` | inicializa el contexto desde header entrante (`tracer.rb:78`) |
|
|
37
|
+
| `Tracer.generate_trace_header` | method | `() -> String` | header para propagar al siguiente servicio (`tracer.rb:115`) |
|
|
38
|
+
| `Tracer.parse_trace_id` | method | `() -> void` | extrae `root_id`/`self_id`/`called_from`/`total_time_so_far` del `trace_id` (`tracer.rb:40`) |
|
|
39
|
+
| `Tracer.service_name` | method | `() -> String` | nombre del servicio (de `Rails.application`) (`tracer.rb:22`) |
|
|
40
|
+
| `Tracer.correlation_id` | method | `() -> String` | compuesto `service_name;root_id` (`tracer.rb:32`) |
|
|
41
|
+
| `Tracer.current_duration_s` | method | `() -> Float` | segundos desde `created_at` (monotónico) (`tracer.rb:89`) |
|
|
42
|
+
| `Tracer.current_duration_ms` | method | `() -> Integer` | milisegundos desde `created_at` (`tracer.rb:67`) |
|
|
43
|
+
| `Tracer.format_duration` | method | `(Float seconds) -> String` | formato humano (`"7.0ms"`, `"2 minutes 5 seconds"`) (`tracer.rb:102`) |
|
|
44
|
+
|
|
45
|
+
### `ExisRay::Current` (`< ActiveSupport::CurrentAttributes`, base abstracta — el host subclasea)
|
|
46
|
+
|
|
47
|
+
| símbolo | tipo | firma | nota |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| `Current.<attr>` | accessor (rw) | — | `user_id · isp_id · correlation_id` (`current.rb:9`) |
|
|
50
|
+
| `Current.user_id=` | setter | `(Integer?) -> void` | usa `!nil?` (acepta `0`) + sincroniza PaperTrail/ActiveResource (`current.rb:54`) |
|
|
51
|
+
| `Current.isp_id=` | setter | `(Integer?) -> void` | ídem (`current.rb:63`) |
|
|
52
|
+
| `Current.correlation_id=` | setter | `(String?) -> void` | propaga a Session/Reporter (`current.rb:71`) |
|
|
53
|
+
| `Current.user=` / `Current.user` | accessor | `(obj) -> void` / `() -> User?` | asigna `user_id` de `obj.id` / lazy-load memoizado (`current.rb:84,88`) |
|
|
54
|
+
| `Current.isp=` / `Current.isp` | accessor | `(obj) -> void` / `() -> Isp?` | ídem (`current.rb:95,99`) |
|
|
55
|
+
| `Current.user?` / `Current.isp?` | predicate | `() -> bool` | true si el id `!nil?` (`current.rb:106,110`) |
|
|
56
|
+
| `Current.correlation_id?` | predicate | `() -> bool` | true si `present?` (`current.rb:116`) |
|
|
57
|
+
| `Current.log_fields` | hook | `() -> Hash` | **override** para inyectar campos custom en cada log; default `{}` (`current.rb:31`) |
|
|
58
|
+
|
|
59
|
+
### `ExisRay::Reporter` (`< ActiveSupport::CurrentAttributes`, base abstracta — el host subclasea)
|
|
60
|
+
|
|
61
|
+
| símbolo | tipo | firma | nota |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `Reporter.report` | method | `(String message, context: {}, tags: {}, fingerprint: [], transaction_name: nil) -> void` | reporta mensaje a Sentry (`reporter.rb:24`) |
|
|
64
|
+
| `Reporter.exception` | method | `(Exception excep, context: {}, tags: {}, fingerprint: [], transaction_name: nil) -> void` | reporta excepción (`reporter.rb:33`) |
|
|
65
|
+
| `Reporter.add_context` | method | `(Hash attrs) -> void` | acumula contexto (`reporter.rb:58`) |
|
|
66
|
+
| `Reporter.add_tags` | method | `(Hash attrs) -> void` | acumula tags (`reporter.rb:64`) |
|
|
67
|
+
| `Reporter.add_fingerprint` | method | `(value) -> void` | acumula fingerprint (`reporter.rb:52`) |
|
|
68
|
+
| `Reporter.build_custom_context` | hook | `() -> void` | **override** para contexto específico del servicio (`reporter.rb:70`) |
|
|
69
|
+
| `Reporter.sentry_user_context` | hook | `(current) -> Hash` | **override**; default `{ id: user_id }` (`reporter.rb:196`) |
|
|
70
|
+
| `Reporter.sentry_isp_context` | hook | `(current) -> Hash` | **override** (`reporter.rb:205`) |
|
|
71
|
+
|
|
72
|
+
### `ExisRay::JsonFormatter` (`< ::Logger::Formatter`)
|
|
73
|
+
|
|
74
|
+
| símbolo | tipo | firma | nota |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| `JsonFormatter#call` | method | `(severity, timestamp, progname, msg) -> String` | interfaz `Logger::Formatter`; emite JSON single-line con contexto inyectado (`json_formatter.rb:49`) |
|
|
77
|
+
|
|
78
|
+
### `ExisRay::LogSubscriber` (`< ActiveSupport::LogSubscriber`)
|
|
79
|
+
|
|
80
|
+
| símbolo | tipo | firma | nota |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `LogSubscriber.install!` | method | `() -> void` | suscribe y suprime los subscribers default de Rails (`log_subscriber.rb:68`) |
|
|
83
|
+
| `LogSubscriber.attached?` | method | `() -> bool` | si ya está instalado (`log_subscriber.rb:76`) |
|
|
84
|
+
| `LogSubscriber.extra_fields` | hook | `(event) -> Hash` | **override** para campos HTTP extra; default `{}` (`log_subscriber.rb:58`) |
|
|
85
|
+
| `LogSubscriber#process_action` | method | `(event) -> void` | interfaz subscriber; emite el log de cierre de request (`log_subscriber.rb:33`) |
|
|
86
|
+
|
|
87
|
+
### `ExisRay::TaskMonitor` (módulo)
|
|
88
|
+
|
|
89
|
+
| símbolo | tipo | firma | nota |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `TaskMonitor.run` | method | `(String task_name) { () -> void } -> void` | genera `root_id`, loguea `task_started`/`task_finished`, re-lanza excepciones, resetea contexto (`task_monitor.rb:16`) |
|
|
92
|
+
|
|
93
|
+
### Middlewares de propagación
|
|
94
|
+
|
|
95
|
+
| símbolo | tipo | firma | nota |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `ExisRay::HttpMiddleware` | class (Rack) | `#initialize(app)` · `#call(env) -> Array` | auto-insertado tras `ActionDispatch::RequestId` (`http_middleware.rb`) |
|
|
98
|
+
| `ExisRay::FaradayMiddleware` | class (`< Faraday::Middleware`) | `#call(env)` | **manual** en el stack Faraday (`faraday_middleware.rb`) |
|
|
99
|
+
| `ExisRay::ActiveResourceInstrumentation` | module (prepend) | `#headers -> Hash` | auto-prepend a `ActiveResource::Base` (`active_resource_instrumentation.rb`) |
|
|
100
|
+
| `ExisRay::Sidekiq::ClientMiddleware` | class | `#call(worker_class, job, queue, redis_pool = nil) { }` | auto-registrado (client) (`sidekiq/client_middleware.rb`) |
|
|
101
|
+
| `ExisRay::Sidekiq::ServerMiddleware` | class | `#call(worker, job, queue) { }` | auto-registrado (server) (`sidekiq/server_middleware.rb`) |
|
|
102
|
+
| `ExisRay::BugBunny::PublisherTracing` | class (`< ::BugBunny::Middleware::Base`) | `#on_request(env)` | **manual** en el cliente (`bug_bunny/publisher_tracing.rb`) |
|
|
103
|
+
| `ExisRay::BugBunny::ConsumerTracingMiddleware` | class (`< ::BugBunny::ConsumerMiddleware::Base`) | `#call(delivery_info, properties, body)` | auto-registrado (consumer) (`bug_bunny/consumer_tracing_middleware.rb`) |
|
|
104
|
+
|
|
105
|
+
## 3. Inferencias
|
|
106
|
+
|
|
107
|
+
| ítem | confidence | a verificar |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `Tracer`/`Reporter`/`TaskMonitor` no declaran `private` | declared | métodos helper (`generate_new_root`, `clean_request_id`, `build_from_*`, `*_to_old/new_sentry`, `setup_tracer`, ...) quedan **reachable** Ruby-wise pero NO son contrato — ver §4 (Hyrum's Law) |
|
|
110
|
+
| accesores de `Current`/`Tracer`/`Reporter` como métodos de clase | declared | son `ActiveSupport::CurrentAttributes` → los `attribute :x` exponen `.x`/`.x=` a nivel de clase, thread/fiber-local |
|
|
111
|
+
| middlewares "manual" vs "auto" | declared | Faraday y BugBunny-publisher requieren registro manual; el resto los inyecta el Railtie (ver `docs/config` §i) |
|
|
112
|
+
|
|
113
|
+
## 4. Cobertura y fronteras
|
|
114
|
+
|
|
115
|
+
- **Superficie consumer-facing completa** para v0.11.0 — lo que un host app usa o subclasea.
|
|
116
|
+
- **Helpers públicos-Ruby pero NO contrato:** `Tracer.generate_new_root` / `.clean_request_id`; `Reporter.build_from_tracer` / `.build_from_current` / `.*_to_old_sentry` / `.*_to_new_sentry` / `.session_*`; `TaskMonitor.setup_tracer` / `.pod_identifier` / `.log_event` / `.format_*`; helpers privados de `Current` (`assign_session_request_id`, `sync_reporter_correlation_id`, `sanitize_header_value` — sí marcados `private`, `current.rb:120`). Reachable por falta de `private` (salvo Current), pero son detalle de implementación; cambiar su firma no es breaking de contrato.
|
|
117
|
+
- **Internos del formatter/subscriber:** los `inject_*`/`extract_*`/`parse_*` de `JsonFormatter` y `LogSubscriber` son privados de implementación — el contrato es `#call` / `#process_action` + los hooks de override.
|
|
118
|
+
- **Constantes:** `JsonFormatter::SENSITIVE_KEYS`, `::SEVERITY_NUMBER`, `::KV_DETECT_RE` son detalle interno, no contrato.
|
|
119
|
+
- **Configuración:** el shape de `Configuration` (defaults, tipos, failure-modes) vive en [`docs/config/configuracion.md`](../config/configuracion.md) — acá solo se lista la clase y `json_logs?`.
|
|
120
|
+
- **Comportamiento runtime** (secuencias de hidratación/emisión) → [`docs/behavior/behavior.md`](../behavior/behavior.md); **significado de términos** → [`docs/glossary/glossary.md`](../glossary/glossary.md).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Test — exis_ray
|
|
2
|
+
|
|
3
|
+
> meta: artefacto test · RFC-013 (shape v2.1, `accepted`) · generado
|
|
4
|
+
> manualmente (**piloto RFC-013** — suplemento de gema para validar §j/§k;
|
|
5
|
+
> predata el soporte de `test` en `arch-structure`, futura regeneración vía
|
|
6
|
+
> esa skill) · anclado a `spec/` · fecha 2026-06-01 · cobertura: completa para los
|
|
7
|
+
> componentes aislados; gap real en inyección del Railtie (sin dummy app) y
|
|
8
|
+
> matrix de versiones (CI mono-versión).
|
|
9
|
+
|
|
10
|
+
## 1. Resumen
|
|
11
|
+
|
|
12
|
+
Estrategia y cobertura de test de la gema (Railtie de observabilidad). Stack:
|
|
13
|
+
**RSpec**, 8 suites / **176** examples, **sin SimpleCov**. Cada componente se
|
|
14
|
+
testea **aislado con mocks de Rails** (`stub_const`), no contra una app real.
|
|
15
|
+
Filosofía **test-last + FDD**. Este artefacto es el **suplemento gema** del
|
|
16
|
+
piloto RFC-013: ejercita §j (inyección al host + matrix) y confirma que §k
|
|
17
|
+
(concurrencia) es **n/a** acá.
|
|
18
|
+
|
|
19
|
+
## 2. Cuerpo
|
|
20
|
+
|
|
21
|
+
### a. Hecho verificable
|
|
22
|
+
|
|
23
|
+
- **Suites:** 8 / **176** examples (todos unit aislados).
|
|
24
|
+
- **Modo:** `railtie`/`library` (no http/worker — es gema).
|
|
25
|
+
- **Factories:** 0 (Structs/doubles inline).
|
|
26
|
+
- **Regresiones post-incidente:** 2 (#9, #12) — **con** comentario de origen
|
|
27
|
+
(§h, convención ya practicada).
|
|
28
|
+
- **% coverage:** `n/a (sin tooling)`.
|
|
29
|
+
- **Inyecciones al host testeadas:** 0 de ~7 (§j) — sin dummy app.
|
|
30
|
+
- **Matrix:** declarado Ruby ≥2.6 / Rails ≥6; testeado CI **solo 3.4.4** (§j).
|
|
31
|
+
- **Tests de concurrencia:** 0 (§k n/a).
|
|
32
|
+
|
|
33
|
+
### b. Estrategia / pirámide
|
|
34
|
+
|
|
35
|
+
Todo **unit aislado**: cada clase (`Tracer`, `HttpMiddleware`, `JsonFormatter`,
|
|
36
|
+
`LogSubscriber`, `Configuration`, `Current`, `Reporter`, `TaskMonitor`) se
|
|
37
|
+
testea sola, mockeando `Rails`/`Logger`/`ActionDispatch`. Sin nivel
|
|
38
|
+
integration/system (no hay app real). Mapeo: la gema no mapea a capas de
|
|
39
|
+
servicio — los componentes son su propia superficie (RFC-004 interfaz).
|
|
40
|
+
Momento: **test-last**.
|
|
41
|
+
|
|
42
|
+
### c. Inventario de suites
|
|
43
|
+
|
|
44
|
+
| Suite | Nivel | Modo | Nº specs |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `spec/exis_ray/json_formatter_spec.rb` | unit | library | 54 |
|
|
47
|
+
| `spec/exis_ray/log_subscriber_spec.rb` | unit | railtie | 29 |
|
|
48
|
+
| `spec/exis_ray/tracer_spec.rb` | unit | library | 27 |
|
|
49
|
+
| `spec/exis_ray/configuration_spec.rb` | unit | library | 27 |
|
|
50
|
+
| `spec/exis_ray/current_spec.rb` | unit | library | 16 |
|
|
51
|
+
| `spec/exis_ray/reporter_spec.rb` | unit | library | 8 |
|
|
52
|
+
| `spec/exis_ray/task_monitor_spec.rb` | unit | library | 8 |
|
|
53
|
+
| `spec/exis_ray/http_middleware_spec.rb` | unit | railtie | 7 |
|
|
54
|
+
|
|
55
|
+
Invocación: `bundle exec rake` (RSpec + RuboCop), ~1.2s.
|
|
56
|
+
|
|
57
|
+
### d. Cobertura declarada
|
|
58
|
+
|
|
59
|
+
| Flujo crítico | Cubierto? | Spec | Fuente del gap |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| Trace propagation (parse X-Ray headers, root_id) | sí | `spec/exis_ray/tracer_spec.rb`, `http_middleware_spec.rb` | — |
|
|
62
|
+
| JSON log formatting + OTel + filtrado sensible | sí | `spec/exis_ray/json_formatter_spec.rb` | — |
|
|
63
|
+
| LogSubscriber (HTTP status → log level) | sí | `spec/exis_ray/log_subscriber_spec.rb` | — |
|
|
64
|
+
| **Inyección del Railtie en app real** | **no** | — (sin `spec/dummy/`) | inferido — componentes testeados aislados; la inyección (`lib/exis_ray/railtie.rb`) no se ejercita (§j) |
|
|
65
|
+
|
|
66
|
+
`% coverage: n/a`. Gap nominal: la **inyección** (lo que distingue a una gema
|
|
67
|
+
Railtie) no tiene test — solo la lógica de cada componente por separado.
|
|
68
|
+
|
|
69
|
+
### e. Fixtures / factories / test data
|
|
70
|
+
|
|
71
|
+
Sin FactoryBot, sin fixtures, sin PII. Datos sintéticos inline: Structs
|
|
72
|
+
(`build_event`, `build_route` en `log_subscriber_spec.rb`), `stub_const` de
|
|
73
|
+
`Rails`/`Tracer`/`Logger`. n/a la columna entidad (gema sin capa de datos).
|
|
74
|
+
|
|
75
|
+
### f. Topología de integration/system tests
|
|
76
|
+
|
|
77
|
+
**n/a** — no se levanta nada real (sin DB/broker/app). Todo mockeado:
|
|
78
|
+
`Rails`, `Rails.application`, `Logger`, `ActionDispatch::*` vía `stub_const`/
|
|
79
|
+
`instance_double`. Estado requerido para correr: **ninguno** (solo
|
|
80
|
+
`activesupport`). No es el sub-caso "gema-envuelve-infra" (exis_ray no envuelve
|
|
81
|
+
infra externa; instrumenta el host).
|
|
82
|
+
|
|
83
|
+
### g. Contract tests (cross RFC-018)
|
|
84
|
+
|
|
85
|
+
**n/a** — la gema no **consume** servicios (no tiene `docs/consumed/`). Nota: sí
|
|
86
|
+
**inyecta** hooks a otras gemas (`BugBunny.consumer_middlewares`, Sidekiq
|
|
87
|
+
client/server middleware, `ActiveResource::Base.prepend` —
|
|
88
|
+
`lib/exis_ray/railtie.rb:71,96,116`), pero **ninguno tiene test** (cae en §j,
|
|
89
|
+
no en §g — es inyección, no consumo).
|
|
90
|
+
|
|
91
|
+
### h. Tests de no-regresión / nacidos de incidentes
|
|
92
|
+
|
|
93
|
+
| Test | Incidente | Qué reproduce |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `spec/exis_ray/json_formatter_spec.rb:410` `describe "...(issue #9)"` | issue #9 | inyección de contexto de tracer; root_id nil pero request_id presente (Gap C) |
|
|
96
|
+
| `spec/exis_ray/json_formatter_spec.rb:466` `describe "...(issue #12)"` | issue #12 | dedup de claves Symbol/String (no duplicar en JSON) |
|
|
97
|
+
|
|
98
|
+
- **Convención §h ya practicada** ✅ — exis_ray ancla el incidente en el
|
|
99
|
+
`describe`. Valida que la convención v2.1 es factible y de bajo costo. (Forma
|
|
100
|
+
observada: `(issue #N)` en el describe; la norma sugiere `# Regresión: #N` en
|
|
101
|
+
comentario — ambas cumplen el objetivo de link recuperable sin `git blame`.)
|
|
102
|
+
|
|
103
|
+
### i. Ejecución, gates y flaky
|
|
104
|
+
|
|
105
|
+
- **Ejecución:** `bundle exec rake` (RSpec + RuboCop), ~1.2s. Sin gates de
|
|
106
|
+
suite (no hay integration que excluir).
|
|
107
|
+
- **Flaky:** 0 (176 pasan consistentes, sin `sleep`/`Thread`).
|
|
108
|
+
- **CI:** `.github/workflows/main.yml` — corre la suite, pero ver §j (matrix).
|
|
109
|
+
|
|
110
|
+
### j. Inyección al host + matrix de versiones
|
|
111
|
+
|
|
112
|
+
**Sección de alto valor para esta gema** (motivó §j en v2.1).
|
|
113
|
+
|
|
114
|
+
**Inyección al host** — `lib/exis_ray/railtie.rb` inyecta ~7 cosas; **ninguna
|
|
115
|
+
testeada en app real** (no hay `spec/dummy/`):
|
|
116
|
+
|
|
117
|
+
| Inyección | file:line | Testeada? |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `HttpMiddleware` (`insert_after ActionDispatch::RequestId`) | `railtie.rb:17` | **sin-test** de inyección (lógica del middleware sí, aislada — `mock-rails`) |
|
|
120
|
+
| `config.log_tags` (request_id proc) | `railtie.rb:31` | sin-test |
|
|
121
|
+
| `Rails.logger.formatter = JsonFormatter.new` | `railtie.rb:57` | sin-test (formatter sí, aislado) |
|
|
122
|
+
| `LogSubscriber` (subscribe a ActionController) | `railtie.rb:61` | sin-test (subscriber sí, aislado con `mock-rails`) |
|
|
123
|
+
| `BugBunny.consumer_middlewares.use` | `railtie.rb:71` | **sin-test** |
|
|
124
|
+
| `ActiveResource::Base.prepend` instrumentation | `railtie.rb:96` | **sin-test** |
|
|
125
|
+
| Sidekiq client/server middleware | `railtie.rb:107,116` | **sin-test** |
|
|
126
|
+
|
|
127
|
+
Gap: lo que **define** a la gema (inyectar al host) es lo único sin test.
|
|
128
|
+
Cruza [`docs/config/configuracion.md`](../config/configuracion.md) §i (mismo
|
|
129
|
+
inventario de inyecciones del Railtie, lado config). Un `spec/dummy/` cerraría
|
|
130
|
+
el gap.
|
|
131
|
+
|
|
132
|
+
**Matrix de versiones:**
|
|
133
|
+
|
|
134
|
+
| | Valor |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Declarado (gemspec) | Ruby `>= 2.6.0` · activesupport/railties `>= 6.0` (`exis_ray.gemspec:15,38`) |
|
|
137
|
+
| Testeado (CI) | **solo Ruby 3.4.4** (`.github/workflows/main.yml:17`) — sin Appraisal, sin matrix de Rails |
|
|
138
|
+
| Gap | rango Ruby 2.6–3.3 y Rails 6–7 **declarado pero no ejercitado** |
|
|
139
|
+
|
|
140
|
+
### k. Tests de concurrencia / async
|
|
141
|
+
|
|
142
|
+
**n/a** — exis_ray no tiene threads ni callbacks en reader-thread. Confirma que
|
|
143
|
+
§k es **específica de gemas que gestionan concurrencia** (`bug_bunny`), no
|
|
144
|
+
universal. (Input para §5 de la RFC: §k aplicó a 1 de 4 repos validados →
|
|
145
|
+
evaluar si va como sección top-level o sub-bloque condicional.)
|
|
146
|
+
|
|
147
|
+
## 3. Inferencias
|
|
148
|
+
|
|
149
|
+
- "Inyección sin test" se infiere de la ausencia de `spec/dummy/` + presencia
|
|
150
|
+
de hooks en `railtie.rb`. No medido (sin coverage).
|
|
151
|
+
|
|
152
|
+
## 4. Cobertura y fronteras
|
|
153
|
+
|
|
154
|
+
- Cobertura completa de los **componentes aislados**; gap en la **integración
|
|
155
|
+
Railtie↔Rails real** y en la **matrix de versiones**. Ambos son los hallazgos
|
|
156
|
+
que §j hace visibles — el valor del artefacto para una gema.
|
data/lib/exis_ray/version.rb
CHANGED
data/skill/SKILL.md
CHANGED
|
@@ -11,11 +11,14 @@ Para el complemento del estándar de logging Wispro (regla Data First, mapeo Ope
|
|
|
11
11
|
|
|
12
12
|
### Artefactos de detalle (RFC-008)
|
|
13
13
|
|
|
14
|
-
Este SKILL.md **resume e indexa**; el contrato y el significado de detalle viven en `docs/<capa>/`. **Version-lock por construcción:** `gemspec.files` empaqueta `docs/**` en el mismo tag que este `SKILL.md`; los links son rutas relativas dentro del paquete del release (nunca rama/`HEAD`/URL flotante). Contrato resumido anclado a **v0.
|
|
14
|
+
Este SKILL.md **resume e indexa**; el contrato y el significado de detalle viven en `docs/<capa>/`. **Version-lock por construcción:** `gemspec.files` empaqueta `docs/**` en el mismo tag que este `SKILL.md`; los links son rutas relativas dentro del paquete del release (nunca rama/`HEAD`/URL flotante). Contrato resumido anclado a **v0.11.1** (capas de doc config/interface + Mapa de conocimiento; sin cambios de código desde v0.11.0):
|
|
15
15
|
|
|
16
16
|
- [`docs/behavior/behavior.md`](../docs/behavior/behavior.md) — secuencias de hidratación de trace por entrypoint + emisión en logs (parcial, incremental).
|
|
17
17
|
- [`docs/glossary/glossary.md`](../docs/glossary/glossary.md) — lenguaje ubicuo del bounded context (`root_id`, `trace_id`, `source`, `request_id`, `entrypoint`, ...).
|
|
18
|
-
-
|
|
18
|
+
- [`docs/config/configuracion.md`](../docs/config/configuracion.md) — inventario de las 10 opciones de `ExisRay.configure` + `HOSTNAME` + inyecciones del Railtie al host (enriquecimiento semántico §f pendiente).
|
|
19
|
+
- [`docs/test/testing.md`](../docs/test/testing.md) — mapa de suites RSpec + fixtures + coverage.
|
|
20
|
+
- [`docs/interface/interface.md`](../docs/interface/interface.md) — superficie Ruby pública (símbolo · tipo · firma · nota); el detalle de uso de cada símbolo permanece abajo.
|
|
21
|
+
- Datos · Operaciones · Eventos · Consumidas = `n/a` (gema sin DB, no expone superficie propia ni consume servicios). Topología/Release/Errores = **pendiente**. Ver mapa de cobertura completo en `AGENTS.md`.
|
|
19
22
|
|
|
20
23
|
---
|
|
21
24
|
|
data/skills.yml
CHANGED
|
@@ -2,34 +2,16 @@ mcps:
|
|
|
2
2
|
- github
|
|
3
3
|
- clickup
|
|
4
4
|
skills:
|
|
5
|
+
multi-vendor-feedback:
|
|
5
6
|
yard:
|
|
6
|
-
repo: sequre/ai_knowledge
|
|
7
7
|
quality-code:
|
|
8
|
-
repo: sequre/ai_knowledge
|
|
9
8
|
gem-release:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
dev-compose:
|
|
14
|
-
repo: sequre/ai_knowledge
|
|
15
|
-
dev-enrich:
|
|
16
|
-
repo: sequre/ai_knowledge
|
|
9
|
+
arch-structure:
|
|
10
|
+
arch-compose:
|
|
11
|
+
arch-enrich:
|
|
17
12
|
skill-feedback:
|
|
18
|
-
repo: sequre/ai_knowledge
|
|
19
13
|
agent-issue:
|
|
20
|
-
|
|
14
|
+
bug-report:
|
|
21
15
|
dev-flow:
|
|
22
|
-
repo: sequre/ai_knowledge
|
|
23
|
-
matrix-element:
|
|
24
|
-
repo: sequre/ai_knowledge
|
|
25
|
-
environment:
|
|
26
|
-
homeserver: "https://matrix.cloud.wispro.co"
|
|
27
|
-
auth_token: "${MATRIX_AUTH_TOKEN}"
|
|
28
|
-
rooms:
|
|
29
|
-
agents: "!VCHwQXgmXdyhhhPhoz:matrix.cloud.wispro.co"
|
|
30
16
|
documentation-writer:
|
|
31
|
-
|
|
32
|
-
path: skills/documentation-writer
|
|
33
|
-
opentelemetry:
|
|
34
|
-
repo: bobmatnyc/claude-mpm-skills
|
|
35
|
-
path: universal/observability/opentelemetry
|
|
17
|
+
matrix-element:
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: exis_ray
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel Edera
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -45,14 +45,17 @@ executables: []
|
|
|
45
45
|
extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
|
47
47
|
files:
|
|
48
|
-
-
|
|
48
|
+
- AGENTS.md
|
|
49
49
|
- CHANGELOG.md
|
|
50
50
|
- CLAUDE.md
|
|
51
51
|
- LICENSE.txt
|
|
52
52
|
- README.md
|
|
53
53
|
- Rakefile
|
|
54
54
|
- docs/behavior/behavior.md
|
|
55
|
+
- docs/config/configuracion.md
|
|
55
56
|
- docs/glossary/glossary.md
|
|
57
|
+
- docs/interface/interface.md
|
|
58
|
+
- docs/test/testing.md
|
|
56
59
|
- lib/exis_ray.rb
|
|
57
60
|
- lib/exis_ray/active_resource_instrumentation.rb
|
|
58
61
|
- lib/exis_ray/bug_bunny/consumer_tracing_middleware.rb
|
|
@@ -90,8 +93,8 @@ licenses:
|
|
|
90
93
|
metadata:
|
|
91
94
|
homepage_uri: https://github.com/gedera/exis_ray
|
|
92
95
|
source_code_uri: https://github.com/gedera/exis_ray
|
|
93
|
-
changelog_uri: https://github.com/gedera/exis_ray/blob/v0.11.
|
|
94
|
-
documentation_uri: https://github.com/gedera/exis_ray/blob/v0.11.
|
|
96
|
+
changelog_uri: https://github.com/gedera/exis_ray/blob/v0.11.1/CHANGELOG.md
|
|
97
|
+
documentation_uri: https://github.com/gedera/exis_ray/blob/v0.11.1/skill
|
|
95
98
|
post_install_message:
|
|
96
99
|
rdoc_options: []
|
|
97
100
|
require_paths:
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# OpenTelemetry Implementation Guidelines
|
|
2
|
-
|
|
3
|
-
This document provides expert guidance for aligning code and logs with OpenTelemetry (OTel) standards. Follow these rules for all new instrumentation.
|
|
4
|
-
|
|
5
|
-
## 1. Log Data Model
|
|
6
|
-
- **Body:** Use `body` for the primary log message.
|
|
7
|
-
- **Attributes:** Metadata must be flat Key-Value pairs.
|
|
8
|
-
- **Severity:** `level` maps to `severity_text`.
|
|
9
|
-
|
|
10
|
-
## 2. Semantic Conventions
|
|
11
|
-
When adding attributes, check the official OTel [Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/).
|
|
12
|
-
|
|
13
|
-
### HTTP Attributes
|
|
14
|
-
- `http.request.method`: GET, POST, etc.
|
|
15
|
-
- `http.response.status_code`: 200, 404, etc.
|
|
16
|
-
- `url.path`: The request path.
|
|
17
|
-
- `user_agent.original`: The raw User-Agent string.
|
|
18
|
-
|
|
19
|
-
### Database Attributes
|
|
20
|
-
- `db.system`: postgresql, redis, etc.
|
|
21
|
-
- `db.operation`: select, update, etc.
|
|
22
|
-
- `db.collection.name`: table or collection name.
|
|
23
|
-
|
|
24
|
-
## 3. Metrics & Units
|
|
25
|
-
- Always use the lowest common denominator for units (seconds, bytes).
|
|
26
|
-
- Use `_s` suffix for time duration.
|
|
27
|
-
- Avoid compound strings like `"10ms"`. Use `duration_s=0.01`.
|
|
28
|
-
|
|
29
|
-
## 4. Distributed Tracing
|
|
30
|
-
- **TraceID:** 16-byte array, represented as a 32-char lowercase hex string.
|
|
31
|
-
- **SpanID:** 8-byte array, represented as a 16-char lowercase hex string.
|
|
32
|
-
- **Context Propagation:** Follow W3C Trace Context (traceparent) when possible, or maintain AWS X-Ray compatibility as per project requirements.
|