exis_ray 0.5.5 → 0.5.7

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: a79e0a93c6efad500f943dad46daa5d429f133d8144db2fd1ce1e32e2b07ac68
4
- data.tar.gz: 243915edbd6405a89656457b1371b34971efa5f377def03093d4a024ca73b661
3
+ metadata.gz: fa3229011d1a59fbd0d65ec137d7a5223f70bd3c3f69add47c27bc965a3e62f1
4
+ data.tar.gz: f08f78169685640fc2c6f1dbfb51c5e64826e4e776c4c1b66ee5d4196592697d
5
5
  SHA512:
6
- metadata.gz: 6877511e716b0fb3ec6fe55a4542b7129163da596344ec1b98f8042d9bdedd9b514c2fe80ec78af474a4d18d5fd9b70bacd84669d3c467ecd2c69904555a6d94
7
- data.tar.gz: cc0356850bfd66654bd241e3aa2654c5114ea2e3a6fbac91eab248e5b4cb5f10affd2ccfdf62065b5f70cbe6f6d362018572299cb307d365db20cd7d2e7c03b7
6
+ metadata.gz: cc5941334d4f7a7241ba6629d5e27f08ac93ce6e704aaa31b90868a5353ff92143795b3b21cb6426d6b47172fa457de3e834899212b9b8ef1a8b28c4a1d81895
7
+ data.tar.gz: 3d9113cc7e2ed5790723290ff9c635b15bb8d4ec6b649c55cf3aab406a8e12d3598dff59d2f2c12d310d256a3f535fda4f816ee66d196e752c5ffb9e8d9d33e3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.5.7] - 2026-03-27
2
+
3
+ ### Fixed
4
+ - **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.
5
+ - **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.
6
+
7
+ ## [0.5.6] - 2026-03-26
8
+
9
+ ### Added
10
+ - **BugBunny Integration:** Added `ExisRay::BugBunny::PublisherTracing` middleware for `BugBunny::Client` and `BugBunny::Resource`. Injects the active trace context into the `x-trace-id` AMQP header on every published message.
11
+ - **BugBunny Integration:** Added `ExisRay::BugBunny::ConsumerTracing` concern for `BugBunny::Controller`. Restores the ExisRay trace context from `x-trace-id` header via `around_action`, enabling end-to-end distributed tracing across RabbitMQ message boundaries.
12
+
1
13
  ## [0.5.5] - 2026-03-24
2
14
 
3
15
  ### Added
data/README.md CHANGED
@@ -14,6 +14,7 @@ It acts as the backbone of your architecture, ensuring that every request, backg
14
14
  * **Sidekiq Integration:** Automatic context propagation (User/ISP/Trace) and log formatting between the Enqueuer and the Worker.
15
15
  * **Task Monitor:** A specialized monitor for Rake/Cron tasks to initialize traces and format logs where no HTTP request exists.
16
16
  * **HTTP Clients:** Automatically patches `ActiveResource` and provides middleware for `Faraday`.
17
+ * **BugBunny Integration:** Propagates the trace context across RabbitMQ message boundaries via a publisher middleware and a consumer concern.
17
18
 
18
19
  ---
19
20
 
@@ -200,6 +201,54 @@ conn = Faraday.new(url: "[https://api.internal](https://api.internal)") do |f|
200
201
  end
201
202
  ```
202
203
 
204
+ ### E. BugBunny (RabbitMQ)
205
+
206
+ If your app publishes messages via [BugBunny](https://github.com/gedera/bug_bunny), ExisRay can propagate the active trace context through the `x-trace-id` AMQP header, and restore it on the consumer side so every log line during message processing shares the same `root_id` as the original HTTP request.
207
+
208
+ #### Publisher — inject the trace header
209
+
210
+ Add `ExisRay::BugBunny::PublisherTracing` to your publisher middleware stack.
211
+
212
+ **Using `BugBunny::Client` directly:**
213
+
214
+ ```ruby
215
+ client = BugBunny::Client.new(pool: connection_pool) do |stack|
216
+ stack.use ExisRay::BugBunny::PublisherTracing
217
+ stack.use BugBunny::Middleware::JsonResponse
218
+ end
219
+ ```
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
+
233
+ If no trace context is active (e.g. a standalone script), the middleware does nothing.
234
+
235
+ #### Consumer — restore the trace context
236
+
237
+ Include `ExisRay::BugBunny::ConsumerTracing` in your base controller. It registers an `around_action` that reads `x-trace-id` from the message headers and hydrates `ExisRay::Tracer` before your action runs. The context is always reset in `ensure`, preventing leaks between messages.
238
+
239
+ ```ruby
240
+ class ApplicationController < BugBunny::Controller
241
+ include ExisRay::BugBunny::ConsumerTracing
242
+ end
243
+ ```
244
+
245
+ **How it works end-to-end:**
246
+ 1. **Publish:** The publisher middleware injects `x-trace-id` into the AMQP headers of every outgoing message.
247
+ 2. **Consume:** The consumer concern reads that header, restores the trace context, and sets `source=system`.
248
+ 3. **Logs:** Every log line emitted during the controller action carries the original `root_id` and `correlation_id` from the upstream HTTP request.
249
+
250
+ If a message arrives without `x-trace-id` (published without ExisRay), the concern is a no-op.
251
+
203
252
  ---
204
253
 
205
254
  ## 📋 Advanced Logging Guide
@@ -0,0 +1,63 @@
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 configurado en `ExisRay.configuration.propagation_trace_header`
9
+ # (por defecto `'X-Amzn-Trace-Id'`) inyectado por el publicador.
10
+ #
11
+ # Permite que todos los logs emitidos durante el procesamiento de un mensaje
12
+ # incluyan el mismo root_id y correlation_id que el request HTTP original,
13
+ # logrando trazabilidad distribuida de punta a punta.
14
+ #
15
+ # El contexto se limpia siempre en `ensure`, garantizando que un error en el
16
+ # controller no contamine el procesamiento del siguiente mensaje.
17
+ #
18
+ # @example Incluir en el ApplicationController de BugBunny
19
+ # class ApplicationController < BugBunny::Controller
20
+ # include ExisRay::BugBunny::ConsumerTracing
21
+ # end
22
+ module ConsumerTracing
23
+ extend ActiveSupport::Concern
24
+
25
+ # Header AMQP desde el cual se lee el trace context propagado.
26
+ # Debe coincidir con `propagation_trace_header` usado por el publisher.
27
+ def self.trace_header
28
+ ExisRay.configuration.propagation_trace_header
29
+ end
30
+
31
+ included do
32
+ around_action :exis_ray_trace_context
33
+ end
34
+
35
+ private
36
+
37
+ # Restaura el contexto de trazabilidad y lo limpia al finalizar,
38
+ # independientemente de si el controller levantó una excepción.
39
+ #
40
+ # @yieldreturn [void]
41
+ def exis_ray_trace_context
42
+ setup_exis_ray_context
43
+ yield
44
+ ensure
45
+ ExisRay::Tracer.reset rescue nil
46
+ end
47
+
48
+ # Hidrata el Tracer con el header de traza recibido en el mensaje.
49
+ # Si el header no está presente (mensaje sin trace context), no hace nada.
50
+ #
51
+ # @return [void]
52
+ def setup_exis_ray_context
53
+ trace_header = headers[self.class.trace_header]
54
+ return unless trace_header.present?
55
+
56
+ ExisRay::Tracer.hydrate(trace_id: trace_header, source: 'system')
57
+ ExisRay.sync_correlation_id
58
+ rescue StandardError
59
+ # El tracing nunca debe interrumpir el procesamiento del mensaje.
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExisRay
4
+ module BugBunny
5
+ # Middleware de publicación para BugBunny::Client.
6
+ #
7
+ # Inyecta el header de trazabilidad de ExisRay en cada mensaje publicado,
8
+ # permitiendo la propagación del trace context a los servicios consumidores.
9
+ # Esto hace posible correlacionar un request HTTP original con todos los mensajes
10
+ # y logs que genera en el ecosistema de microservicios.
11
+ #
12
+ # @example Configuración en el cliente
13
+ # client = BugBunny::Client.new(pool: pool) do |stack|
14
+ # stack.use ExisRay::BugBunny::PublisherTracing
15
+ # end
16
+ #
17
+ # @example Configuración en un BugBunny::Resource
18
+ # class UserResource < BugBunny::Resource
19
+ # middleware do |stack|
20
+ # stack.use ExisRay::BugBunny::PublisherTracing
21
+ # end
22
+ # end
23
+ class PublisherTracing < ::BugBunny::Middleware::Base
24
+ # Inyecta el header de traza antes de publicar el mensaje.
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.
27
+ #
28
+ # @param env [BugBunny::Request] El objeto request del mensaje saliente.
29
+ # @return [void]
30
+ def on_request(env)
31
+ return unless ExisRay::Tracer.root_id.present?
32
+
33
+ env.headers[ExisRay.configuration.propagation_trace_header] = ExisRay::Tracer.generate_trace_header
34
+ rescue StandardError
35
+ # La propagación de tracing nunca debe interrumpir la publicación de mensajes.
36
+ end
37
+ end
38
+ end
39
+ end
@@ -8,20 +8,14 @@ module ExisRay
8
8
 
9
9
  def call(env)
10
10
  # 1. Hidratar Infraestructura
11
- ExisRay::Tracer.created_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
- ExisRay::Tracer.source = "http"
13
-
14
- trace_header_key = ExisRay.configuration.trace_header
15
-
16
- ExisRay::Tracer.trace_id = env[trace_header_key]
17
- ExisRay::Tracer.request_id = env['action_dispatch.request_id']
18
- ExisRay::Tracer.parse_trace_id
11
+ ExisRay::Tracer.hydrate(
12
+ trace_id: env[ExisRay.configuration.trace_header],
13
+ source: "http"
14
+ )
15
+ ExisRay::Tracer.request_id = env['action_dispatch.request_id']
19
16
 
20
17
  # 2. Hidratar Negocio
21
- # Usamos el helper centralizado para obtener la clase Current
22
- if (curr = ExisRay.current_class) && curr.respond_to?(:correlation_id=) && ExisRay::Tracer.root_id.present?
23
- curr.correlation_id = ExisRay::Tracer.correlation_id
24
- end
18
+ ExisRay.sync_correlation_id
25
19
 
26
20
  @app.call(env)
27
21
  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
@@ -46,17 +46,16 @@ module ExisRay
46
46
  # @param job [Hash] Payload de Sidekiq.
47
47
  # @return [void]
48
48
  def hydrate_tracer(worker, job)
49
- ExisRay::Tracer.created_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
49
  ExisRay::Tracer.sidekiq_job = worker.class.name
51
- ExisRay::Tracer.source = "sidekiq"
52
50
 
53
51
  if job["exis_ray_trace"]
54
52
  # Continuidad: Usamos la traza propagada desde el cliente (Web/Cron)
55
- ExisRay::Tracer.trace_id = job["exis_ray_trace"]
56
- ExisRay::Tracer.parse_trace_id
53
+ ExisRay::Tracer.hydrate(trace_id: job["exis_ray_trace"], source: "sidekiq")
57
54
  else
58
55
  # Origen: El job nació directamente aquí sin contexto previo
59
- ExisRay::Tracer.root_id = ExisRay::Tracer.send(:generate_new_root)
56
+ ExisRay::Tracer.created_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ ExisRay::Tracer.source = "sidekiq"
58
+ ExisRay::Tracer.root_id = ExisRay::Tracer.send(:generate_new_root)
60
59
  end
61
60
  end
62
61
 
@@ -61,6 +61,20 @@ module ExisRay
61
61
  (current_duration_s * 1000).round
62
62
  end
63
63
 
64
+ # Hidrata el Tracer con un trace header entrante y registra el inicio del contexto.
65
+ # Centraliza la inicialización de contexto de trazabilidad para todos los entrypoints
66
+ # (HTTP, Sidekiq, BugBunny, etc.), eliminando duplicación entre middlewares.
67
+ #
68
+ # @param trace_id [String] El header de traza entrante (formato AWS X-Ray).
69
+ # @param source [String] El entrypoint de ejecución ('http', 'sidekiq', 'task', 'system').
70
+ # @return [void]
71
+ def self.hydrate(trace_id:, source:)
72
+ self.created_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
73
+ self.source = source
74
+ self.trace_id = trace_id
75
+ parse_trace_id
76
+ end
77
+
64
78
  # Calcula el tiempo transcurrido en segundos desde el inicio de la request.
65
79
  # Cumple con el estándar Wispro-Observability-Spec (v1).
66
80
  #
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ExisRay
4
4
  # Versión actual de la gema.
5
- VERSION = "0.5.5"
5
+ VERSION = "0.5.7"
6
6
  end
data/lib/exis_ray.rb CHANGED
@@ -24,6 +24,13 @@ 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
+
27
34
  # Integración automática con Rails
28
35
  # Solo cargamos el Railtie si la constante Rails está definida.
29
36
  require "exis_ray/railtie" if defined?(Rails)
@@ -96,6 +103,17 @@ module ExisRay
96
103
  end
97
104
  end
98
105
 
106
+ # Sincroniza el correlation_id del Tracer en la clase Current configurada.
107
+ # Debe llamarse después de hidratar el Tracer (post `hydrate` o `parse_trace_id`).
108
+ #
109
+ # @return [void]
110
+ def sync_correlation_id
111
+ curr = current_class
112
+ return unless curr&.respond_to?(:correlation_id=) && Tracer.root_id.present?
113
+
114
+ curr.correlation_id = Tracer.correlation_id
115
+ end
116
+
99
117
  private
100
118
 
101
119
  def resolve_class(klass_name)
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.5
4
+ version: 0.5.7
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-24 00:00:00.000000000 Z
11
+ date: 2026-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -54,6 +54,8 @@ 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
58
+ - lib/exis_ray/bug_bunny/publisher_tracing.rb
57
59
  - lib/exis_ray/configuration.rb
58
60
  - lib/exis_ray/current.rb
59
61
  - lib/exis_ray/faraday_middleware.rb