exis_ray 0.5.6 → 0.5.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d66d71924fff9cf19c12e42d411963948739da61e6b223db1444c16633a3158e
4
- data.tar.gz: 26c599899ecf1f87388c73e1b4a183370e0902893c09e40c24c7796c973c0d96
3
+ metadata.gz: 327a3ed9c5558bba99acba81d3a3b328a271c16f4725c88a435f08ea8b1526a8
4
+ data.tar.gz: 661cf580101b617b50b565ec342f8a09d8da5a4b8c4d8c11163ea5000236a64e
5
5
  SHA512:
6
- metadata.gz: 60c34361234b638c3758b3b51d38933efcbf5db19fb8e1a9365f53f1e21bcfc42e680ceb37755c792663e7146826af2453d862b7ae526481870f65518a84b311
7
- data.tar.gz: e3cb2ffc12e45d5bdd6f3c1d1a864bc9884b7709c531fa2780923834f0ca4d4766c75148c6afd15aac4d6a6dbf716a151042620474127b544b779fa86ec9c62b
6
+ metadata.gz: d23f08bf5951c9efa611c09a2ba3654ae8763b5bff0e4706ae68f540f52f3668b0c8b6f9bb4c076ba9ad6aff05df50de40f3d0c2e1b69ebd0aaafed6dcdafce2
7
+ data.tar.gz: 9514884ededf873b309345eee8d6e217c6b16b49ba194b55d68825f8a544d907da6d6d624a620223653249da21c12a91de1efe235bb4e39ea5f97c95be15d11d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [0.5.8] - 2026-03-31
2
+
3
+ ### Added
4
+ - **`ExisRay::BugBunny::ConsumerTracingMiddleware`:** Nuevo middleware para el consumer stack de BugBunny. Corre antes de que la gema procese cada mensaje (antes de `consumer.message_received`), garantizando que todos los logs internos de BugBunny — `consumer.message_received`, `consumer.route_matched`, `consumer.rpc_reply`, `consumer.message_processed` — incluyan `root_id`, `trace_id` y `source`. Si el mensaje no trae header de traza, genera un `root_id` nuevo automáticamente.
5
+ - **Propagación RPC bidireccional:** El `Railtie` registra automáticamente dos callbacks en BugBunny: `rpc_reply_headers` inyecta el trace header actualizado (`Self`, `TotalTimeSoFar`, `CalledFrom`) en el reply del consumer; `on_rpc_reply` hidrata el tracer en el thread del publisher al recibir la respuesta, permitiendo que `producer.rpc_response_received` y `request_complete` reflejen el viaje completo por el ecosistema de microservicios.
6
+
7
+ ### Changed
8
+ - **BugBunny auto-instrumentado:** La integración BugBunny se mueve al `after_initialize` del `Railtie`, resolviendo el problema de orden de carga. Con ambas gemas en el `Gemfile`, todo se configura automáticamente sin intervención del desarrollador.
9
+ - **MANIFEST.md:** Documentación completa del estándar de observabilidad: semántica de niveles de log, block form obligatorio para DEBUG, medición de duraciones con reloj monotónico, filtrado de claves sensibles, resiliencia del logger, y tabla completa de campos auto-inyectados con condiciones de activación.
10
+
11
+ ### Removed
12
+ - **`ExisRay::BugBunny::ConsumerTracing`:** Concern eliminado. Reemplazado por `ConsumerTracingMiddleware` que cubre el ciclo completo del mensaje, no solo el action del controller.
13
+
14
+ ## [0.5.7] - 2026-03-27
15
+
16
+ ### Fixed
17
+ - **JSON HTML Escaping:** `JsonFormatter` now uses `JSON.generate` with `ascii_only: false` instead of `.to_json`, preventing `>` from being escaped as `\u003e` in log output.
18
+ - **BugBunny Header Standardization:** `PublisherTracing` and `ConsumerTracing` now use `ExisRay.configuration.propagation_trace_header` instead of the hardcoded `'x-trace-id'` constant, aligning with the same pattern used by `FaradayMiddleware` and `ActiveResourceInstrumentation`. The header is now fully configurable and consistent across all outgoing transports.
19
+
1
20
  ## [0.5.6] - 2026-03-26
2
21
 
3
22
  ### Added
data/MANIFEST.md CHANGED
@@ -5,81 +5,185 @@ Este documento define el estándar obligatorio de telemetría y logging estructu
5
5
  ## Objetivo
6
6
  Transformar el logging tradicional en un **flujo de eventos de datos**. Esto permite que herramientas de análisis puedan generar dashboards de rendimiento, alertas inteligentes y rastreo de errores sin necesidad de procesamiento manual de texto o transformaciones complejas.
7
7
 
8
+ ---
9
+
8
10
  ## Reglas Fundamentales
9
11
 
10
- ### 1. Unidad en la Key, Número en el Valor (Data First)
11
- La regla de oro para que las métricas sean operables es separar la unidad del dato numérico. **Nunca** incluir unidades dentro de los valores de los logs.
12
- * Incorrecto: `duration="0.5s"`, `memory="128MB"`.
13
- * Correcto: `duration_s=0.5`, `memory_mb=128`.
12
+ ### 1. Formato de Mensaje
13
+
14
+ Todo log debe emitirse como una línea de pares `key=value` en el siguiente orden:
15
+
16
+ ```
17
+ component=x event=y [campos adicionales]
18
+ ```
19
+
20
+ - `component`: nombre de la gema, librería o módulo responsable (ej: `exis_ray`, `storage_engine`). Siempre en `snake_case`.
21
+ - `event`: nombre puntual de la acción o hito (ej: `http_request`, `sync_complete`). Siempre en `snake_case`.
22
+ - Los campos adicionales van a continuación, en cualquier orden.
23
+
24
+ Valores con espacios deben ir entre comillas dobles: `error_message="Server reset connection"`.
25
+
26
+ ### 2. Niveles de Log
27
+
28
+ Cada nivel tiene una semántica estricta. Usar el nivel incorrecto es un error.
29
+
30
+ | Nivel | Cuándo usarlo |
31
+ | :------ | :------------ |
32
+ | `ERROR` | Se lanzó una excepción o el sistema no pudo completar una operación crítica. |
33
+ | `WARN` | Ocurrió algo inesperado pero la ejecución continuó. |
34
+ | `INFO` | Flujo normal del sistema. Eventos relevantes para el negocio u operaciones. |
35
+ | `DEBUG` | Detalle técnico interno. Solo útil durante desarrollo o diagnóstico. |
36
+
37
+ **Regla de DEBUG:** Siempre usar block form para evitar interpolación innecesaria:
38
+ ```ruby
39
+ # Correcto
40
+ logger.debug { "component=cache event=miss key=#{key}" }
41
+
42
+ # Incorrecto — evalúa la interpolación aunque DEBUG esté desactivado
43
+ logger.debug "component=cache event=miss key=#{key}"
44
+ ```
45
+
46
+ **Prohibido:** Nunca usar `Kernel#warn` ni escribir directamente a `$stderr`. Todo output debe ir por el logger configurado.
47
+
48
+ ### 3. Unidad en la Key, Número en el Valor (Data First)
49
+
50
+ La regla de oro para que las métricas sean operables es separar la unidad del dato numérico. **Nunca** incluir unidades dentro de los valores.
51
+
52
+ ```
53
+ # Incorrecto
54
+ duration="0.5s" memory="128MB"
55
+
56
+ # Correcto
57
+ duration_s=0.5 memory_mb=128
58
+ ```
14
59
 
15
- **Sufijos de unidad recomendados:**
16
- - `_s`: Segundos (Float). Estándar para duraciones y latencias visibles al negocio.
60
+ **Sufijos de unidad:**
61
+ - `_s`: Segundos (Float). Estándar para duraciones y latencias.
17
62
  - `_ms`: Milisegundos (Integer). Para precisión técnica interna de alta frecuencia.
18
63
  - `_count`: Cantidades o volúmenes (Integer). Ej: `record_count`, `retry_count`.
19
64
  - `_bytes` / `_kb` / `_mb`: Unidades de almacenamiento o memoria.
20
- - `_human`: (Opcional) Texto explicativo legible para humanos (ej: `duration_human="2 hours"`).
65
+ - `_human`: (Opcional) Texto legible para humanos. Ej: `duration_human="2 minutes 5 seconds"`.
66
+
67
+ ### 4. Medición de Duraciones
68
+
69
+ Las duraciones **siempre** deben medirse con reloj monotónico. Nunca con `Time.now` (susceptible a saltos de reloj del sistema).
70
+
71
+ ```ruby
72
+ # Correcto
73
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
74
+ # ... operación ...
75
+ duration_s = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
76
+
77
+ # Incorrecto
78
+ start = Time.now
79
+ duration_s = Time.now - start
80
+ ```
81
+
82
+ ### 5. Source: Entrypoint de Ejecución
21
83
 
22
- ### 2. Contexto de Identidad
23
- Cada línea de log debe llevar los metadatos necesarios para identificar su origen técnico de forma inmediata:
24
- * `component`: Nombre de la gema, librería o módulo responsable (ej: `exis_ray`, `storage_engine`).
25
- * `event`: Nombre de la acción puntual o hito alcanzado (ej: `http_request`, `engine.complete`).
26
- * `source`: El punto de entrada de la ejecución (`http`, `sidekiq`, `task`, `system`).
84
+ El campo `source` identifica el punto de entrada del proceso. Es obligatorio y solo acepta estos valores:
27
85
 
28
- ### 3. Convenciones de Naming & Tipos
29
- - **Naming:** Las llaves (keys) deben usar siempre `snake_case`. Para campos estándar, se prefiere seguir las **OpenTelemetry Semantic Conventions**.
30
- - **Valores Numéricos:** Deben emitirse como números reales (sin sufijos de texto) para permitir que el motor de logs realice el casting automático.
31
- - **Formato:** Pares `key=value` en una sola línea estructurada.
86
+ | Valor | Cuándo usarlo |
87
+ | :--------- | :------------ |
88
+ | `http` | Request HTTP entrante (procesado por Rack/Rails). |
89
+ | `sidekiq` | Job procesado por Sidekiq. |
90
+ | `task` | Tarea programada o proceso de larga duración (Rake, Cron, TaskMonitor). |
91
+ | `system` | Ejecución iniciada por otro sistema interno (ej: consumidor RabbitMQ/BugBunny). |
92
+
93
+ `source` es inyectado automáticamente por el middleware correspondiente. **No lo incluyas manualmente.**
94
+
95
+ ### 6. Seguridad y Privacidad
96
+
97
+ **Filtrado de claves sensibles:** Cualquier campo cuya key coincida con el patrón `password|pass|passwd|secret|token|api_key|auth` debe reemplazar su valor por `[FILTERED]`. Esto es aplicado automáticamente por `ExisRay::JsonFormatter` en todos los logs.
98
+
99
+ **Sin PII:** Nunca loguear datos crudos de usuarios. Solo se permiten identificadores: `user_id`, `isp_id`.
100
+
101
+ ```
102
+ # Incorrecto
103
+ component=auth event=login email=user@example.com password=abc123
104
+
105
+ # Correcto
106
+ component=auth event=login user_id=42 password=[FILTERED]
107
+ ```
108
+
109
+ ### 7. Resiliencia del Logger
110
+
111
+ Un fallo en el logging nunca debe interrumpir el flujo principal de la aplicación. Toda operación de logging debe estar protegida:
112
+
113
+ ```ruby
114
+ # Correcto
115
+ begin
116
+ logger.info "component=sync event=complete duration_s=#{duration_s}"
117
+ rescue StandardError
118
+ # silenciar — el log es accesorio, no crítico
119
+ end
120
+ ```
121
+
122
+ ### 8. Convenciones de Naming
123
+
124
+ - Keys siempre en `snake_case`.
125
+ - Valores numéricos emitidos como números reales (sin comillas) para que el motor de logs realice casting automático.
126
+ - Para campos estándar, seguir las **OpenTelemetry Semantic Conventions** donde sea posible.
127
+
128
+ ---
32
129
 
33
130
  ## 🔭 Alineación con OpenTelemetry
34
131
 
35
- Para garantizar la interoperabilidad, `exis_ray` sigue el **OpenTelemetry Log Data Model**. Siempre que sea posible, los campos deben mapearse a las convenciones semánticas oficiales:
132
+ `exis_ray` sigue el **OpenTelemetry Log Data Model**. Los campos se mapean a las convenciones semánticas oficiales:
36
133
 
37
- | Campo ExisRay | OTel Semantic Convention | Descripción |
38
- | :--- | :--- | :--- |
39
- | `body` | `body` | El contenido principal del log. |
40
- | `level` | `severity_text` | Nivel de importancia. |
41
- | `duration_s` | `duration` (en segundos) | Tiempo de ejecución. |
42
- | `method` | `http.request.method` | Método HTTP. |
43
- | `status` | `http.response.status_code` | Código de respuesta. |
44
- | `path` | `url.path` | Ruta del request. |
45
- | `user_id` | `user.id` | Identificador del usuario. |
134
+ | Campo ExisRay | OTel Semantic Convention | Descripción |
135
+ | :------------- | :-------------------------- | :----------------------- |
136
+ | `body` | `body` | Contenido principal del log (texto libre). |
137
+ | `level` | `severity_text` | Nivel de importancia. |
138
+ | `duration_s` | `duration` (en segundos) | Tiempo de ejecución. |
139
+ | `method` | `http.request.method` | Método HTTP. |
140
+ | `status` | `http.response.status_code` | Código de respuesta HTTP.|
141
+ | `path` | `url.path` | Ruta del request. |
142
+ | `user_id` | `user.id` | Identificador del usuario.|
46
143
 
47
- ## 🏗 Infraestructura de Datos (Automática)
144
+ ---
48
145
 
49
- Para evitar logs redundantes y pesados, **NUNCA** incluyas manualmente las siguientes llaves en tus mensajes de log. La capa de infraestructura (`exis_ray`) las inyecta automáticamente en el nivel raíz del JSON:
146
+ ## 🏗 Campos Auto-Inyectados por ExisRay
50
147
 
51
- | Llave | Descripción | Por qué no incluirla |
52
- | :--- | :--- | :--- |
53
- | `time` | Timestamp ISO8601 | Lo añade el Logger base. |
54
- | `level` | INFO, ERROR, etc. | Lo añade el Logger base. |
55
- | `service` | Nombre de la App | Se obtiene de la configuración global. |
56
- | `source` | Entrypoint (http, task) | Lo inyecta el middleware/monitor correspondiente. |
57
- | `root_id` | Trace ID (AWS X-Ray) | Se gestiona a nivel de hilo/petición. |
58
- | `correlation_id`| ID de rastreo cruzado | Se genera automáticamente al inicio de la ejecución. |
59
- | `user_id` / `isp_id`| Contexto de negocio | Se extrae del estado global de la petición. |
60
- | `sidekiq_job` | Clase del Worker | Inyectado automáticamente en procesos Sidekiq. |
61
- | `task` | Nombre de la tarea | Inyectado automáticamente por el TaskMonitor. |
148
+ **NUNCA** incluyas manualmente los siguientes campos. `ExisRay::JsonFormatter` los inyecta automáticamente en cada línea de log:
62
149
 
63
- > **Regla de Oro:** Tu log manual solo debe contener datos de **tu lógica de negocio**. La infraestructura ya sabe quién eres, de dónde vienes y cuál es tu ID de traza.
150
+ | Campo | Descripción | Condición |
151
+ | :--------------- | :--------------------------------- | :--------------------------------------------- |
152
+ | `time` | Timestamp ISO8601 UTC | Siempre |
153
+ | `level` | Nivel de severidad | Siempre |
154
+ | `service` | Nombre de la aplicación | Siempre |
155
+ | `root_id` | Trace ID raíz (AWS X-Ray) | Cuando hay trace context activo |
156
+ | `trace_id` | Trace ID completo (formato X-Ray) | Cuando hay trace context activo |
157
+ | `source` | Entrypoint de ejecución | Cuando hay trace context activo |
158
+ | `correlation_id` | ID de rastreo cruzado | Cuando `Current.correlation_id` está presente |
159
+ | `user_id` | ID del usuario autenticado | Cuando `Current.user_id` está presente |
160
+ | `isp_id` | ID del ISP | Cuando `Current.isp_id` está presente |
161
+ | `sidekiq_job` | Clase del Worker Sidekiq | Solo en procesos Sidekiq |
162
+ | `task` | Nombre de la tarea | Solo en procesos TaskMonitor |
163
+ | `tags` | Tags de Rails TaggedLogging | Solo si hay tags activos en el hilo |
164
+
165
+ > **Regla de Oro:** Tu log manual solo debe contener datos de **tu lógica de negocio**. La infraestructura ya sabe quién eres, de dónde venís y cuál es tu ID de traza.
64
166
 
65
167
  ---
66
168
 
67
169
  ## 🛰 Ciclo de Vida del Evento
68
170
 
69
- ## Ciclo de Vida del Evento
70
-
71
171
  ### Procesos, Trabajos y Tareas (Jobs/Tasks)
172
+
72
173
  Todo proceso aislado debe reportar su inicio y su finalización con una estructura consistente:
174
+
73
175
  - `event`: Identificador de estado (ej: `task_started`, `task_finished`).
74
- - `status`: Resultado final de la operación (`success`, `failed`, `aborted`). En contexto de tareas/jobs, siempre es un string semántico, no un código HTTP.
75
- - `duration_s`: Tiempo total de ejecución (calculado con reloj monotónico).
176
+ - `status`: Resultado final (`success`, `failed`, `aborted`). Siempre string semántico, nunca código HTTP.
177
+ - `duration_s`: Tiempo total de ejecución (reloj monotónico).
76
178
  - `error_class` / `error_message`: Obligatorios solo en caso de fallo.
77
179
 
78
- ### Peticiones de Interfaz (APIs/HTTP)
79
- Los logs de cierre de peticiones deben estandarizar el reporte de rendimiento para telemetría:
80
- - `status`: Código de respuesta HTTP (Integer). En contexto HTTP siempre es el código numérico (ej: `200`, `404`, `500`).
180
+ ### Peticiones HTTP
181
+
182
+ Los logs de cierre de petición deben estandarizar el reporte de rendimiento:
183
+
184
+ - `status`: Código HTTP (Integer). Ej: `200`, `404`, `500`.
81
185
  - `duration_s`: Tiempo total de respuesta del servidor.
82
- - `[subsystem]_runtime_s`: Desglose opcional de tiempos por capa (ej: `db_runtime_s`, `view_runtime_s`).
186
+ - `[subsystem]_runtime_s`: Desglose opcional por capa (ej: `db_runtime_s`, `view_runtime_s`).
83
187
 
84
188
  ---
85
189
 
@@ -87,26 +191,32 @@ Los logs de cierre de peticiones deben estandarizar el reporte de rendimiento pa
87
191
 
88
192
  **En el código fuente:**
89
193
  ```ruby
90
- # Reporte de éxito con métricas integradas
91
- logger.info "component=data_processor event=sync_complete status=success duration_s=1.25 record_count=500"
194
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
195
+ # ... operación ...
196
+ duration_s = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
92
197
 
93
- # Reporte de error con contexto técnico
94
- logger.error "component=data_processor event=sync_complete status=failed error_class=Timeout error_message=\"Server reset\""
198
+ logger.info "component=data_processor event=sync_complete status=success duration_s=#{duration_s} record_count=500"
199
+
200
+ # En caso de error:
201
+ rescue => e
202
+ logger.error "component=data_processor event=sync_complete status=failed error_class=#{e.class} error_message=\"#{e.message}\""
203
+ end
95
204
  ```
96
205
 
97
- **Resultado JSON unificado:**
206
+ **Resultado JSON unificado (emitido por ExisRay):**
98
207
  ```json
99
208
  {
100
209
  "time": "2026-03-24T14:00:00Z",
101
210
  "level": "INFO",
102
211
  "service": "my_application",
212
+ "root_id": "1-abc123",
213
+ "trace_id": "Root=1-abc123",
214
+ "source": "task",
215
+ "correlation_id": "my_application;1-abc123",
103
216
  "component": "data_processor",
104
217
  "event": "sync_complete",
105
218
  "status": "success",
106
219
  "duration_s": 1.25,
107
- "record_count": 500,
108
- "source": "task",
109
- "root_id": "Root=1-...",
110
- "correlation_id": "my_application;Root=..."
220
+ "record_count": 500
111
221
  }
112
222
  ```
data/README.md CHANGED
@@ -207,7 +207,9 @@ If your app publishes messages via [BugBunny](https://github.com/gedera/bug_bunn
207
207
 
208
208
  #### Publisher — inject the trace header
209
209
 
210
- Add `ExisRay::BugBunny::PublisherTracing` to your client middleware stack. Works with both `BugBunny::Client` and `BugBunny::Resource`.
210
+ Add `ExisRay::BugBunny::PublisherTracing` to your publisher middleware stack.
211
+
212
+ **Using `BugBunny::Client` directly:**
211
213
 
212
214
  ```ruby
213
215
  client = BugBunny::Client.new(pool: connection_pool) do |stack|
@@ -216,6 +218,18 @@ client = BugBunny::Client.new(pool: connection_pool) do |stack|
216
218
  end
217
219
  ```
218
220
 
221
+ **Using `BugBunny::Resource`** (via `client_middleware`):
222
+
223
+ ```ruby
224
+ class ApplicationResource < BugBunny::Resource
225
+ client_middleware do |stack|
226
+ stack.use ExisRay::BugBunny::PublisherTracing
227
+ end
228
+ end
229
+ ```
230
+
231
+ Defining it once in a base `ApplicationResource` is enough — all subclasses inherit the middleware stack automatically.
232
+
219
233
  If no trace context is active (e.g. a standalone script), the middleware does nothing.
220
234
 
221
235
  #### Consumer — restore the trace context
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExisRay
4
+ module BugBunny
5
+ # Consumer middleware para BugBunny que establece el trace context de ExisRay
6
+ # antes de que la gema empiece a procesar el mensaje.
7
+ #
8
+ # Al correr en el consumer middleware stack (antes de `consumer.message_received`),
9
+ # garantiza que **todos** los logs internos de BugBunny y del controller action
10
+ # incluyan `root_id`, `trace_id` y `source`.
11
+ #
12
+ # Se registra automáticamente al cargar ExisRay con BugBunny presente.
13
+ # El usuario final no necesita configuración adicional.
14
+ #
15
+ # @example Registro automático (ocurre en Railtie#after_initialize)
16
+ # BugBunny.consumer_middlewares.use ExisRay::BugBunny::ConsumerTracingMiddleware
17
+ class ConsumerTracingMiddleware < ::BugBunny::ConsumerMiddleware::Base
18
+ # Inyecta el trace context antes de delegar al siguiente middleware.
19
+ # Limpia el contexto en `ensure` para no contaminar el próximo mensaje.
20
+ #
21
+ # @param delivery_info [Bunny::DeliveryInfo]
22
+ # @param properties [Bunny::MessageProperties]
23
+ # @param body [String]
24
+ def call(delivery_info, properties, body)
25
+ setup_trace_context(properties)
26
+ @app.call(delivery_info, properties, body)
27
+ ensure
28
+ ExisRay::Tracer.reset rescue nil
29
+ end
30
+
31
+ private
32
+
33
+ # Hidrata el Tracer con el header de traza del mensaje entrante.
34
+ # Si no hay header (mensaje publicado sin trace context), genera un root_id nuevo
35
+ # para que los logs siempre tengan contexto de trazabilidad.
36
+ #
37
+ # @param properties [Bunny::MessageProperties]
38
+ # @return [void]
39
+ def setup_trace_context(properties)
40
+ trace_header = properties.headers&.[](ExisRay.configuration.propagation_trace_header)
41
+
42
+ if trace_header.present?
43
+ ExisRay::Tracer.hydrate(trace_id: trace_header, source: 'system')
44
+ else
45
+ ExisRay::Tracer.created_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
46
+ ExisRay::Tracer.source = 'system'
47
+ ExisRay::Tracer.root_id = ExisRay::Tracer.send(:generate_new_root)
48
+ end
49
+
50
+ ExisRay.sync_correlation_id
51
+ rescue StandardError
52
+ # El tracing nunca debe interrumpir el procesamiento del mensaje.
53
+ end
54
+ end
55
+ end
56
+ end
@@ -21,18 +21,16 @@ module ExisRay
21
21
  # end
22
22
  # end
23
23
  class PublisherTracing < ::BugBunny::Middleware::Base
24
- # Header AMQP personalizado para propagar el trace context entre servicios.
25
- TRACE_HEADER = 'x-trace-id'
26
-
27
24
  # Inyecta el header de traza antes de publicar el mensaje.
28
25
  # Solo actúa si hay un trace context activo (root_id presente).
26
+ # Usa `propagation_trace_header` de la configuración, igual que FaradayMiddleware.
29
27
  #
30
28
  # @param env [BugBunny::Request] El objeto request del mensaje saliente.
31
29
  # @return [void]
32
30
  def on_request(env)
33
31
  return unless ExisRay::Tracer.root_id.present?
34
32
 
35
- env.headers[TRACE_HEADER] = ExisRay::Tracer.generate_trace_header
33
+ env.headers[ExisRay.configuration.propagation_trace_header] = ExisRay::Tracer.generate_trace_header
36
34
  rescue StandardError
37
35
  # La propagación de tracing nunca debe interrumpir la publicación de mensajes.
38
36
  end
@@ -47,8 +47,9 @@ module ExisRay
47
47
  inject_current_tags(payload)
48
48
  process_message(payload, msg)
49
49
 
50
- # Compactamos para eliminar claves con valores nulos (nil) y generamos el JSON
51
- "#{payload.compact.to_json}\n"
50
+ # Compactamos para eliminar claves con valores nulos (nil) y generamos el JSON.
51
+ # Usamos JSON.generate con unsafe_chars para evitar el escape HTML de > como \u003e.
52
+ "#{JSON.generate(payload.compact, { ascii_only: false })}\n"
52
53
  end
53
54
 
54
55
  private
@@ -44,6 +44,31 @@ module ExisRay
44
44
  log_boot("component=exis_ray event=json_logging_enabled")
45
45
  end
46
46
 
47
+ # --- Integración BugBunny ---
48
+ if defined?(::BugBunny)
49
+ require "exis_ray/bug_bunny/publisher_tracing"
50
+ require "exis_ray/bug_bunny/consumer_tracing_middleware"
51
+ ::BugBunny.consumer_middlewares.use ExisRay::BugBunny::ConsumerTracingMiddleware
52
+ ::BugBunny.configuration.rpc_reply_headers = lambda {
53
+ next {} unless ExisRay::Tracer.root_id.present?
54
+
55
+ { ExisRay.configuration.propagation_trace_header => ExisRay::Tracer.generate_trace_header }
56
+ }
57
+ ::BugBunny.configuration.on_rpc_reply = lambda { |headers|
58
+ begin
59
+ trace_header = headers&.[](ExisRay.configuration.propagation_trace_header)
60
+ next unless trace_header.present?
61
+
62
+ # Solo actualizamos trace_id — no tocamos created_at ni source del publisher.
63
+ ExisRay::Tracer.trace_id = trace_header
64
+ ExisRay::Tracer.parse_trace_id
65
+ ExisRay.sync_correlation_id
66
+ rescue StandardError
67
+ end
68
+ }
69
+ log_boot("component=exis_ray event=bug_bunny_instrumented")
70
+ end
71
+
47
72
  # --- Instrumentación de ActiveResource ---
48
73
  if defined?(ActiveResource::Base)
49
74
  require "exis_ray/active_resource_instrumentation"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ExisRay
4
4
  # Versión actual de la gema.
5
- VERSION = "0.5.6"
5
+ VERSION = "0.5.8"
6
6
  end
data/lib/exis_ray.rb CHANGED
@@ -24,13 +24,6 @@ require "exis_ray/faraday_middleware" if defined?(Faraday)
24
24
  # Solo cargamos la instrumentación si ActiveResource está presente.
25
25
  require "exis_ray/active_resource_instrumentation" if defined?(ActiveResource::Base)
26
26
 
27
- # Integraciones BugBunny: publisher middleware y consumer concern.
28
- # Solo se cargan si la gema BugBunny está presente.
29
- if defined?(::BugBunny)
30
- require "exis_ray/bug_bunny/publisher_tracing"
31
- require "exis_ray/bug_bunny/consumer_tracing"
32
- end
33
-
34
27
  # Integración automática con Rails
35
28
  # Solo cargamos el Railtie si la constante Rails está definida.
36
29
  require "exis_ray/railtie" if defined?(Rails)
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.5.6
4
+ version: 0.5.8
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-03-26 00:00:00.000000000 Z
11
+ date: 2026-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -54,7 +54,7 @@ files:
54
54
  - Rakefile
55
55
  - lib/exis_ray.rb
56
56
  - lib/exis_ray/active_resource_instrumentation.rb
57
- - lib/exis_ray/bug_bunny/consumer_tracing.rb
57
+ - lib/exis_ray/bug_bunny/consumer_tracing_middleware.rb
58
58
  - lib/exis_ray/bug_bunny/publisher_tracing.rb
59
59
  - lib/exis_ray/configuration.rb
60
60
  - lib/exis_ray/current.rb
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
-
5
- module ExisRay
6
- module BugBunny
7
- # Concern para BugBunny::Controller que restaura el trace context de ExisRay
8
- # a partir del header 'x-trace-id' inyectado por el publicador.
9
- #
10
- # Permite que todos los logs emitidos durante el procesamiento de un mensaje
11
- # incluyan el mismo root_id y correlation_id que el request HTTP original,
12
- # logrando trazabilidad distribuida de punta a punta.
13
- #
14
- # El contexto se limpia siempre en `ensure`, garantizando que un error en el
15
- # controller no contamine el procesamiento del siguiente mensaje.
16
- #
17
- # @example Incluir en el ApplicationController de BugBunny
18
- # class ApplicationController < BugBunny::Controller
19
- # include ExisRay::BugBunny::ConsumerTracing
20
- # end
21
- module ConsumerTracing
22
- extend ActiveSupport::Concern
23
-
24
- # Header AMQP desde el cual se lee el trace context propagado.
25
- TRACE_HEADER = 'x-trace-id'
26
-
27
- included do
28
- around_action :exis_ray_trace_context
29
- end
30
-
31
- private
32
-
33
- # Restaura el contexto de trazabilidad y lo limpia al finalizar,
34
- # independientemente de si el controller levantó una excepción.
35
- #
36
- # @yieldreturn [void]
37
- def exis_ray_trace_context
38
- setup_exis_ray_context
39
- yield
40
- ensure
41
- ExisRay::Tracer.reset rescue nil
42
- end
43
-
44
- # Hidrata el Tracer con el header de traza recibido en el mensaje.
45
- # Si el header no está presente (mensaje sin trace context), no hace nada.
46
- #
47
- # @return [void]
48
- def setup_exis_ray_context
49
- trace_header = headers[TRACE_HEADER]
50
- return unless trace_header.present?
51
-
52
- ExisRay::Tracer.hydrate(trace_id: trace_header, source: 'system')
53
- ExisRay.sync_correlation_id
54
- rescue StandardError
55
- # El tracing nunca debe interrumpir el procesamiento del mensaje.
56
- end
57
- end
58
- end
59
- end