exis_ray 0.5.7 → 0.5.9

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: fa3229011d1a59fbd0d65ec137d7a5223f70bd3c3f69add47c27bc965a3e62f1
4
- data.tar.gz: f08f78169685640fc2c6f1dbfb51c5e64826e4e776c4c1b66ee5d4196592697d
3
+ metadata.gz: f17193f5df75197d569fbe74d8b27e55b116e861bdb819f1e92526a1da79869e
4
+ data.tar.gz: e8e45998cb83236c3bb7cc3a981995ccd36764fdb6fbc1465e796a7966e3b354
5
5
  SHA512:
6
- metadata.gz: cc5941334d4f7a7241ba6629d5e27f08ac93ce6e704aaa31b90868a5353ff92143795b3b21cb6426d6b47172fa457de3e834899212b9b8ef1a8b28c4a1d81895
7
- data.tar.gz: 3d9113cc7e2ed5790723290ff9c635b15bb8d4ec6b649c55cf3aab406a8e12d3598dff59d2f2c12d310d256a3f535fda4f816ee66d196e752c5ffb9e8d9d33e3
6
+ metadata.gz: 39999ff8baa584c7dd35a9b090feca1c410eedffcec72aba429ab65fe3d983c17517dff97363f4209c303d36252e3f46e1ad569ab908f2362c677064f43b54dc
7
+ data.tar.gz: d2b8d38f83a0bccdda315c5148d90a8d0b93821424fdb11b106ea322bcea09e3537d42c371a5cd266aa0c63a0e7f73c2d6285f302c7e444cfbf59f98c9451909
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: release
3
+ description: Prepare a new ExisRay release — bump version, update CHANGELOG, commit, push to main, and create git tag.
4
+ argument-hint: <version> (e.g. 0.5.9)
5
+ allowed-tools: [Read, Edit, Write, Bash, Glob, Grep]
6
+ ---
7
+
8
+ # Release ExisRay $ARGUMENTS
9
+
10
+ Seguí estos pasos para preparar y publicar la versión `$ARGUMENTS`.
11
+
12
+ ## 1. Verificar cambios pendientes
13
+
14
+ Revisá qué cambios hay desde el último release:
15
+
16
+ ```bash
17
+ git log --oneline $(git describe --tags --abbrev=0)..HEAD
18
+ git diff --stat
19
+ ```
20
+
21
+ ## 2. Verificar que los tests pasan
22
+
23
+ ```bash
24
+ bundle exec rspec
25
+ ```
26
+
27
+ Si hay fallos, detenerse y resolverlos antes de continuar.
28
+
29
+ ## 3. Bump de versión
30
+
31
+ Editá `lib/exis_ray/version.rb`:
32
+ ```ruby
33
+ VERSION = "$ARGUMENTS"
34
+ ```
35
+
36
+ ## 4. Actualizar CHANGELOG.md
37
+
38
+ Agregá una nueva entrada al tope del CHANGELOG con el formato:
39
+
40
+ ```markdown
41
+ ## [$ARGUMENTS] - FECHA_HOY
42
+
43
+ ### Added / Changed / Fixed / Removed
44
+ - **Componente:** Descripción del cambio.
45
+ ```
46
+
47
+ Basate en los commits desde el último tag para identificar qué va en cada sección.
48
+
49
+ ## 5. Commit
50
+
51
+ ```bash
52
+ git add lib/exis_ray/version.rb CHANGELOG.md [otros archivos modificados]
53
+ git commit -m "chore: bump version to $ARGUMENTS and update CHANGELOG"
54
+ ```
55
+
56
+ ## 6. Push a main y tag
57
+
58
+ ```bash
59
+ git push origin HEAD:main
60
+ git tag v$ARGUMENTS
61
+ git push origin v$ARGUMENTS
62
+ ```
63
+
64
+ El tag dispara el build automático en RubyGems vía CI.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: opentelemetry
3
+ description: This skill should be used when adding new log fields, designing new telemetry events, or reviewing field naming in this gem. Activates when the user mentions "otel", "opentelemetry", "semantic conventions", "field naming", "span", "trace", or asks about how to name a new log field.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # OpenTelemetry Alignment — ExisRay
8
+
9
+ ExisRay sigue el **OpenTelemetry Log Data Model**. Cuando se agregan campos nuevos, priorizar los nombres de OTel Semantic Conventions antes de inventar nombres propios.
10
+
11
+ ## Mapeo de Campos ExisRay → OTel
12
+
13
+ | Campo ExisRay | OTel Semantic Convention | Tipo |
14
+ | :------------- | :-------------------------- | :----- |
15
+ | `body` | `body` | String |
16
+ | `level` | `severity_text` | String |
17
+ | `duration_s` | `duration` (en segundos) | Float |
18
+ | `method` | `http.request.method` | String |
19
+ | `status` | `http.response.status_code` | Integer|
20
+ | `path` | `url.path` | String |
21
+ | `user_id` | `user.id` | String/Integer |
22
+
23
+ ## Convenciones de Naming
24
+
25
+ - **`snake_case`** siempre para keys
26
+ - **Unidades en la key, número en el valor:**
27
+ - `duration_s=1.25` (Float) — nunca `duration="1.25s"`
28
+ - `duration_ms=42` (Integer)
29
+ - `record_count=500` (Integer)
30
+ - `memory_mb=128` (Integer)
31
+ - **`_human`** para representación legible opcional: `duration_human="2 minutes"`
32
+
33
+ ## Campos Reservados
34
+
35
+ No usar estos nombres para otros propósitos — tienen semántica fija en ExisRay:
36
+
37
+ `time`, `level`, `service`, `root_id`, `trace_id`, `source`, `correlation_id`,
38
+ `user_id`, `isp_id`, `sidekiq_job`, `task`, `body`, `tags`
39
+
40
+ ## Log Body vs Campos Estructurados
41
+
42
+ - **Texto libre** → va en `body` (OTel log body)
43
+ - **Datos estructurados** → elevar al nivel raíz via KV string o Hash
44
+
45
+ ```ruby
46
+ # Correcto — datos estructurados al nivel raíz
47
+ logger.info "component=sync event=complete status=success duration_s=1.25"
48
+ # → {"component":"sync","event":"complete","status":"success","duration_s":1.25}
49
+
50
+ # Correcto — texto libre va en body
51
+ logger.info "Esto es un mensaje de texto libre"
52
+ # → {"body":"Esto es un mensaje de texto libre"}
53
+ ```
54
+
55
+ ## Trace Context (AWS X-Ray compatible)
56
+
57
+ ExisRay usa el formato AWS X-Ray pero es compatible con OTel trace context:
58
+
59
+ - `root_id` → equivalente a `trace_id` en OTel (solo el ID raíz, sin prefijos)
60
+ - `trace_id` → header completo X-Ray: `Root=1-...;Self=...;CalledFrom=...`
61
+ - `source` → entrypoint de ejecución (no tiene equivalente directo en OTel)
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: rails-expert
3
+ description: This skill should be used when writing or reviewing any Rails integration code in this gem — Railtie, middleware, CurrentAttributes, initializers, ActiveSupport notifications, or compatibility across Rails 6/7/8. Activates when the user mentions "railtie", "middleware", "initializer", "after_initialize", "current_attributes", "cache_classes", "enable_reloading", "log_subscriber", "tagged_logging", or asks about Rails compatibility.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Rails Expert — ExisRay Context
8
+
9
+ Este skill cubre las integraciones de ExisRay con Rails y las reglas de compatibilidad entre versiones.
10
+
11
+ ## Compatibilidad de Versiones
12
+
13
+ ### Rails 7.1+ vs 6/7.0
14
+
15
+ **Reloading:**
16
+ ```ruby
17
+ # Correcto — compatible con Rails 6, 7 y 8
18
+ def cache_classes?
19
+ config = Rails.application.config
20
+ if config.respond_to?(:enable_reloading)
21
+ !config.enable_reloading # Rails 7.1+ (semántica inversa)
22
+ else
23
+ config.cache_classes # Rails 6/7.0
24
+ end
25
+ end
26
+ ```
27
+
28
+ **Notifications (ActiveSupport):**
29
+ ```ruby
30
+ # Correcto — siempre usar respond_to? guard
31
+ if notifier.respond_to?(:all_listeners_for)
32
+ notifier.all_listeners_for(pattern) # Rails 7.1+
33
+ else
34
+ notifier.listeners_for(pattern) # Rails 6/7.0
35
+ end
36
+ ```
37
+
38
+ ## Railtie — Orden de Inicialización
39
+
40
+ ExisRay usa tres puntos de inicialización con propósitos distintos:
41
+
42
+ ```
43
+ 1. initializer "exis_ray.configure_middleware"
44
+ → Inserta HttpMiddleware en el stack Rack
45
+ → Corre antes de que la app arranque
46
+
47
+ 2. initializer "exis_ray.configure_logging", after: :load_config_initializers
48
+ → Lee config/initializers/exis_ray.rb antes de decidir la estrategia
49
+ → Configura log_tags para modo texto
50
+
51
+ 3. config.after_initialize
52
+ → Todo lo demás: JsonFormatter, LogSubscriber, BugBunny, Sidekiq, ActiveResource
53
+ → Garantiza que TODAS las gemas están cargadas (defined?(::BugBunny) funciona)
54
+ ```
55
+
56
+ **Regla crítica:** Cualquier integración condicional (`defined?(::GemaExterna)`) DEBE ir en `after_initialize`, no en `initializer`. De lo contrario la condición puede evaluarse antes de que la gema esté cargada.
57
+
58
+ ## CurrentAttributes
59
+
60
+ `ExisRay::Tracer` extiende `ActiveSupport::CurrentAttributes` — es thread-local y se resetea automáticamente al final de cada request HTTP (via `ActionDispatch::Executor`).
61
+
62
+ Para contextos no-HTTP (Sidekiq, BugBunny, Rake), siempre hacer reset manual en `ensure`:
63
+ ```ruby
64
+ ensure
65
+ ExisRay::Tracer.reset rescue nil
66
+ end
67
+ ```
68
+
69
+ ## LogSubscriber
70
+
71
+ `ExisRay::LogSubscriber` usa `ActiveSupport::Notifications`. Solo se instala con `json_logs: true`.
72
+
73
+ ```ruby
74
+ ExisRay::LogSubscriber.install!
75
+ # Internamente: subscribe a process_action.action_controller
76
+ # y desuscribe los subscribers por defecto de Rails
77
+ ```
78
+
79
+ ## TaggedLogging (modo texto)
80
+
81
+ En modo texto (`json_logs: false`), el contexto se inyecta via `log_tags`:
82
+ ```ruby
83
+ app.config.log_tags << proc { ExisRay::Tracer.trace_id.presence || ExisRay::Tracer.root_id.presence }
84
+ ```
85
+ Solo funciona dentro del ciclo de un HTTP request. Procesos background (Sidekiq, BugBunny) no tienen este contexto en modo texto.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: readme-writer
3
+ description: This skill should be used when updating README.md or CHANGELOG.md after adding a new feature, fixing a bug, or releasing a version. Activates when the user mentions "readme", "changelog", "documentation", "release notes", "document this feature", or asks to update the docs.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # README & CHANGELOG Writer — ExisRay
8
+
9
+ ## README.md — Estructura y Estilo
10
+
11
+ El README de ExisRay sigue esta estructura fija. No reorganizar secciones existentes — solo agregar o actualizar.
12
+
13
+ ### Tono y Estilo
14
+ - Conciso y técnico — el lector es un dev Rails que quiere integrar la gema rápido
15
+ - Ejemplos de código reales, no abstractos
16
+ - Mostrar el output (JSON) cuando sea relevante para entender el valor
17
+ - Español para texto corrido, inglés para nombres de clases/métodos/campos
18
+
19
+ ### Secciones del README
20
+ 1. **Badges + descripción una línea**
21
+ 2. **Features** — lista bullets con las capacidades principales
22
+ 3. **Installation** — Gemfile + bundle
23
+ 4. **Configuration** — initializer con todos los options documentados
24
+ 5. **Usage por integración** — una subsección por integración (HTTP, Sidekiq, BugBunny, etc.)
25
+ 6. **Advanced** — subclassing Current, Reporter, LogSubscriber
26
+
27
+ ### Cómo documentar una nueva integración
28
+
29
+ ```markdown
30
+ ## E. NombreIntegración
31
+
32
+ Descripción en 1-2 oraciones de qué hace y por qué es útil.
33
+
34
+ ### Setup
35
+
36
+ \`\`\`ruby
37
+ # Código mínimo para activar la integración
38
+ \`\`\`
39
+
40
+ ### Output
41
+
42
+ \`\`\`json
43
+ {
44
+ "time": "...",
45
+ "root_id": "...",
46
+ "component": "...",
47
+ "event": "..."
48
+ }
49
+ \`\`\`
50
+ ```
51
+
52
+ ---
53
+
54
+ ## CHANGELOG.md — Formato
55
+
56
+ Seguir [Keep a Changelog](https://keepachangelog.com/). Cada entrada en orden cronológico inverso.
57
+
58
+ ### Estructura de una entrada
59
+
60
+ ```markdown
61
+ ## [X.Y.Z] - YYYY-MM-DD
62
+
63
+ ### Added
64
+ - **NombreFeature:** Descripción clara de qué se agregó y por qué es útil.
65
+ Mencionar el componente afectado y el beneficio para el usuario.
66
+
67
+ ### Changed
68
+ - **NombreComponente:** Qué cambió y por qué (motivación técnica o de UX).
69
+
70
+ ### Fixed
71
+ - **Descripción del bug:** Qué fallaba, en qué condición, cómo se resolvió.
72
+
73
+ ### Removed
74
+ - **NombreComponente:** Qué se eliminó y qué lo reemplaza (si aplica).
75
+ ```
76
+
77
+ ### Reglas
78
+ - Una entrada por item significativo — no agrupar cosas no relacionadas
79
+ - Mencionar el nombre del componente en **negrita** al inicio
80
+ - Describir el beneficio para el usuario, no solo el cambio técnico
81
+ - Si hay breaking change, marcarlo explícitamente: `⚠️ Breaking:`
82
+ - Versiones de patch (0.5.x): bugs y fixes pequeños
83
+ - Versiones minor (0.x.0): features nuevas, integraciones
84
+ - Versiones major (x.0.0): breaking changes
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: rubocop-omakase
3
+ description: This skill should be used when writing or reviewing Ruby code in this gem to ensure it follows RuboCop Rails Omakase conventions. Activates when writing new methods, refactoring existing code, or when the user mentions "rubocop", "style", "convention", "cop", or "omakase".
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # RuboCop Rails Omakase — ExisRay
8
+
9
+ Todo código nuevo o modificado debe seguir las convenciones de `rubocop-rails-omakase`. Solo aplicar a código que se está escribiendo o modificando — nunca sugerir fixes en código existente no tocado.
10
+
11
+ ## Reglas Principales
12
+
13
+ **Strings:**
14
+ ```ruby
15
+ # Frozen string literal obligatorio en todos los archivos
16
+ # frozen_string_literal: true
17
+
18
+ # Comillas dobles para strings
19
+ "hola mundo" # correcto
20
+ 'hola mundo' # incorrecto
21
+ ```
22
+
23
+ **Métodos:**
24
+ ```ruby
25
+ # Sin paréntesis en definición si no hay argumentos
26
+ def reset
27
+ # ...
28
+ end
29
+
30
+ # Con paréntesis cuando hay argumentos
31
+ def hydrate(trace_id:, source:)
32
+ # ...
33
+ end
34
+ ```
35
+
36
+ **Bloques:**
37
+ ```ruby
38
+ # { } para bloques de una línea
39
+ [1, 2].map { |n| n * 2 }
40
+
41
+ # do/end para bloques multilínea
42
+ [1, 2].each do |n|
43
+ puts n
44
+ puts n * 2
45
+ end
46
+ ```
47
+
48
+ **Condicionales:**
49
+ ```ruby
50
+ # Guard clauses para retornos tempranos
51
+ def process(value)
52
+ return unless value.present?
53
+ return "[FILTERED]" if sensitive?(value)
54
+
55
+ do_something(value)
56
+ end
57
+
58
+ # Ternario solo cuando es genuinamente simple
59
+ result = condition ? "yes" : "no"
60
+ ```
61
+
62
+ **Espaciado:**
63
+ ```ruby
64
+ # Línea en blanco después de definición de clase/módulo
65
+ class MyClass
66
+ # línea en blanco aquí si hay métodos
67
+
68
+ def my_method
69
+ end
70
+ end
71
+ ```
72
+
73
+ **Hash:**
74
+ ```ruby
75
+ # Ruby 3.1+ shorthand cuando key == variable
76
+ { name:, value: } # en lugar de { name: name, value: value }
77
+
78
+ # Rocket syntax solo para keys no-símbolo
79
+ { "string-key" => value }
80
+ { symbol_key: value }
81
+ ```
82
+
83
+ ## Patterns Comunes en ExisRay
84
+
85
+ ```ruby
86
+ # Módulos con solo métodos de clase — usar module_function o class << self
87
+ module ExisRay
88
+ module TaskMonitor
89
+ class << self
90
+ def run(task_name)
91
+ # ...
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Rescue en métodos que no deben interrumpir el flujo
98
+ def setup_context
99
+ do_risky_thing
100
+ rescue StandardError
101
+ # silenciar
102
+ end
103
+
104
+ # Lambdas con -> para bloques cortos
105
+ transformer = ->(value) { value.to_s.downcase }
106
+ ```
107
+
108
+ ## Verificación
109
+
110
+ Antes de proponer código, mentalmente verificar:
111
+ 1. `# frozen_string_literal: true` presente
112
+ 2. Strings con comillas dobles
113
+ 3. Guard clauses en lugar de if anidados
114
+ 4. `rescue StandardError` en operaciones de logging/tracing
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: yard
3
+ description: This skill should be used when writing or reviewing Ruby documentation in this gem. Activates when adding new methods, classes, or modules, or when the user mentions "yard", "documentation", "docstring", "@param", "@return", "@example", or asks how to document something.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # YARD Documentation — ExisRay Style
8
+
9
+ Toda clase, módulo y método público de ExisRay debe estar documentado con YARD. Los métodos privados solo se documentan si la lógica no es autoevidente.
10
+
11
+ ## Estructura de una Clase/Módulo
12
+
13
+ ```ruby
14
+ # Descripción en una línea.
15
+ #
16
+ # Párrafo de contexto opcional explicando el propósito y comportamiento general.
17
+ # Mencionar dependencias importantes o patrones de uso.
18
+ #
19
+ # @example Uso básico
20
+ # ExisRay::Tracer.hydrate(trace_id: header, source: 'http')
21
+ class ExisRay::Tracer < ActiveSupport::CurrentAttributes
22
+ ```
23
+
24
+ ## Métodos de Clase
25
+
26
+ ```ruby
27
+ # Descripción concisa del método en una línea.
28
+ #
29
+ # Párrafo opcional con detalles de comportamiento, casos edge, o efectos secundarios.
30
+ #
31
+ # @param trace_id [String] El header de traza entrante (formato AWS X-Ray).
32
+ # @param source [String] El entrypoint de ejecución ('http', 'sidekiq', 'task', 'system').
33
+ # @return [void]
34
+ def self.hydrate(trace_id:, source:)
35
+ ```
36
+
37
+ ## Tipos Comunes en ExisRay
38
+
39
+ | Tipo YARD | Cuándo usarlo |
40
+ |:----------|:-------------|
41
+ | `[String]` | Strings simples |
42
+ | `[String, nil]` | Puede ser nil |
43
+ | `[Boolean]` | true/false |
44
+ | `[Hash]` | Hash genérico |
45
+ | `[Hash{Symbol => Object}]` | Hash con tipos de keys/values |
46
+ | `[Class, nil]` | Clase o nil (para current_class, reporter_class) |
47
+ | `[void]` | Métodos sin valor de retorno significativo |
48
+ | `[Float]` | Duraciones en segundos |
49
+ | `[Integer]` | Duraciones en ms, counts |
50
+
51
+ ## Atributos con attr_accessor
52
+
53
+ ```ruby
54
+ # @!attribute [rw] trace_header
55
+ # @return [String] La key del header HTTP (formato Rack) para el Trace ID entrante.
56
+ # Por defecto es 'HTTP_X_AMZN_TRACE_ID'.
57
+ attr_accessor :trace_header
58
+ ```
59
+
60
+ ## Métodos Privados
61
+
62
+ Solo documentar si la lógica no es obvia:
63
+ ```ruby
64
+ # Limpia el Request ID para cumplir con los 24 caracteres hex de AWS X-Ray.
65
+ #
66
+ # @api private
67
+ # @return [String]
68
+ def self.clean_request_id
69
+ ```
70
+
71
+ ## Reglas
72
+
73
+ - Primera línea: siempre una oración completa terminada en punto
74
+ - `@example` cuando el uso no es obvio desde la firma
75
+ - `@api private` para métodos que usan `private_class_method`
76
+ - No documentar código que no fue modificado
77
+ - No agregar `@author` ni `@since` — no es el estilo del proyecto
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## [0.5.9] - 2026-03-31
2
+
3
+ ### Fixed
4
+ - **JSON Object Coercion:** `JsonFormatter` ahora intenta parsear como JSON cualquier valor string que comience con `{` o `[` al procesar KV strings. Esto permite que campos como `queue_opts` y `exchange_opts` emitidos por BugBunny como JSON compacto se serialicen como objetos reales en lugar de strings escapados.
5
+
6
+ ### Added
7
+ - **CLAUDE.md:** Documentación completa del proyecto actualizada a v0.5.9: arquitectura, componentes, métodos clave, campos auto-inyectados, reglas de ejecución y guía de testing en consola.
8
+ - **`.claude/skills/`:** Skills de proyecto para rails-expert, YARD, OpenTelemetry, RuboCop Omakase y README writer — disponibles automáticamente para cualquier dev que use Claude Code en este repo.
9
+ - **`.claude/commands/release`:** Comando `/release` para automatizar el flujo de versioning.
10
+
11
+ ## [0.5.8] - 2026-03-31
12
+
13
+ ### Added
14
+ - **`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.
15
+ - **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.
16
+
17
+ ### Changed
18
+ - **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.
19
+ - **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.
20
+
21
+ ### Removed
22
+ - **`ExisRay::BugBunny::ConsumerTracing`:** Concern eliminado. Reemplazado por `ConsumerTracingMiddleware` que cubre el ciclo completo del mensaje, no solo el action del controller.
23
+
1
24
  ## [0.5.7] - 2026-03-27
2
25
 
3
26
  ### Fixed
data/CLAUDE.md CHANGED
@@ -1,38 +1,158 @@
1
- # ExisRay Project Memory
2
-
3
- ## Architecture & Components
4
- - `ExisRay::Tracer`: Distributed tracing (AWS X-Ray). Uses `CurrentAttributes` for thread-safety.
5
- - `ExisRay::Current`: Business context (User, ISP). Abstract base class for host app subclassing.
6
- - `ExisRay::Reporter`: Sentry wrapper. Abstract base class for host app subclassing.
7
- - `ExisRay::JsonFormatter`: Core engine. Handles Hash, KV strings, and free-text with automatic masking and **Type Casting**.
8
- - `ExisRay::LogSubscriber`: Native HTTP logger since v0.4.0. Replaces Lograge.
9
- - `ExisRay::TaskMonitor`: Lifecycle manager for non-HTTP processes.
10
-
11
- ## Technical Knowledge & Compatibility
12
-
13
- ### Wispro Observability Spec (v1)
14
- - **Manifest:** All logging MUST follow the rules defined in `MANIFEST.md`.
15
- - **Metrics:** Always use `_s` suffix for durations (Float) and `count` for volumes (Integer). Never include units in values.
16
- - **Type-Awareness:** `JsonFormatter` automatically casts numeric KV values to Float/Integer. Emit raw numbers in KV strings.
17
- - **Automatic Fields:** Do not manually log `time`, `level`, `service`, `source`, `root_id`, `correlation_id`, `sidekiq_job` or `task`. These are handled by the library.
18
-
19
- ### Rails Compatibility (6, 7, 8)
20
- - **Reloading:** Use `cache_classes?` helper (checks `respond_to?(:enable_reloading)`) to avoid deprecation warnings in Rails 7.1+.
21
- - **Notifications:** Rails 7.1+ uses `all_listeners_for`, while 6/7.0 uses `listeners_for`. Always use `respond_to?` guards when manipulating subscribers.
22
-
23
- ### Distributed Tracing (AWS X-Ray)
24
- - **Propagation:** Use `propagation_trace_header` (standard HTTP format) for outgoing requests.
25
- - **Parsing:** `trace_header` (Rack format) is ONLY for incoming request parsing.
26
-
27
- ## Security & Privacy
28
- - **Sensitive Key Filtering:** `JsonFormatter` auto-filters keys matching `password|pass|passwd|secret|token|api_key|auth` replaced with `[FILTERED]`.
29
- - **Header Injection Prevention:** `ExisRay::Current` sanitizes values via `sanitize_header_value` before storing user/ISP identity.
30
- - **No PII in Logs:** Never log raw user data. Only IDs (`user_id`, `isp_id`) are permitted.
31
-
32
- ## Execution Rules
33
- - **No Lograge:** Do not suggest or re-add the Lograge dependency.
34
- - **Pure Data Logging:** Internal logs use KV strings (`component=exis_ray event=...`).
35
- - **OTel Compliance:** Always follow the guidelines in `.gemini/docs/TELEMETRY_GUIDELINES.md` and alignment in `MANIFEST.md`. Use OTel Semantic Conventions for new attributes.
36
- - **Resilience:** All logging operations must be wrapped in `rescue StandardError`.
37
- - **Automatic Fields:** NEVER manually log `time`, `level`, `service`, `source`, `root_id`, `correlation_id`, `sidekiq_job` or `task`. These are handled by the library.
38
- - **Source Values:** Valid values for `source` field: `http`, `sidekiq`, `task`, `system`.
1
+ # ExisRay Project Intelligence
2
+
3
+ ## Qué es esta gema
4
+
5
+ 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.
6
+
7
+ El estándar de logging que implementa está definido en `MANIFEST.md`. Ese documento es la fuente de verdad — cualquier duda sobre formato, campos, o semántica de niveles se resuelve ahí.
8
+
9
+ ---
10
+
11
+ ## Arquitectura & Componentes
12
+
13
+ ### Core
14
+ - **`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`.
15
+ - **`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.
16
+ - **`ExisRay::Current`** Contexto de negocio (user_id, isp_id, correlation_id). Clase abstracta la app host la subclasifica.
17
+ - **`ExisRay::Reporter`** Wrapper de Sentry. Clase abstracta la app host la subclasifica.
18
+ - **`ExisRay::Configuration`** — Configuración global con defaults para AWS X-Ray.
19
+
20
+ ### Integraciones HTTP
21
+ - **`ExisRay::HttpMiddleware`** Rack middleware. Hidrata el Tracer con el header entrante. Se inserta automáticamente después de `ActionDispatch::RequestId`.
22
+ - **`ExisRay::LogSubscriber`** — Logger nativo de requests HTTP. Reemplaza Lograge. Solo activo con `json_logs: true`.
23
+ - **`ExisRay::FaradayMiddleware`** Inyecta `propagation_trace_header` en requests salientes via Faraday.
24
+ - **`ExisRay::ActiveResourceInstrumentation`** Ídem para ActiveResource.
25
+
26
+ ### Integraciones Sidekiq
27
+ - **`ExisRay::Sidekiq::ClientMiddleware`** Inyecta `exis_ray_trace` en el payload del job antes de encolarlo.
28
+ - **`ExisRay::Sidekiq::ServerMiddleware`** Hidrata el Tracer al inicio de cada job. Genera root_id nuevo si el job no trae trace.
29
+
30
+ ### Integraciones BugBunny (RabbitMQ)
31
+ - **`ExisRay::BugBunny::PublisherTracing`** — Middleware para `BugBunny::Client`/`BugBunny::Resource`. Inyecta `propagation_trace_header` en cada mensaje publicado.
32
+ - **`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.
33
+ - **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.
34
+
35
+ ### Procesos en Background
36
+ - **`ExisRay::TaskMonitor`** Lifecycle manager para Rake/Cron. Genera root_id, loguea `task_started`/`task_finished` con `duration_s` y `status`.
37
+
38
+ ---
39
+
40
+ ## Métodos Clave
41
+
42
+ ```ruby
43
+ # Hidratar el Tracer (HTTP, Sidekiq, BugBunny consumer)
44
+ ExisRay::Tracer.hydrate(trace_id: header_string, source: 'http')
45
+
46
+ # Sincronizar correlation_id al Current configurado
47
+ ExisRay.sync_correlation_id
48
+
49
+ # Generar header de propagación para el siguiente servicio
50
+ ExisRay::Tracer.generate_trace_header
51
+ # => "Root=1-abc123-...;Self=...;CalledFrom=wispro_agent;TotalTimeSoFar=42ms"
52
+
53
+ # Acceder a la configuración
54
+ ExisRay.configuration.propagation_trace_header # => 'X-Amzn-Trace-Id'
55
+ ExisRay.configuration.json_logs? # => true/false
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Campos Auto-Inyectados
61
+
62
+ `JsonFormatter` inyecta estos campos automáticamente. **Nunca** incluirlos manualmente en logs:
63
+
64
+ | Campo | Condición |
65
+ |:------|:----------|
66
+ | `time` | Siempre |
67
+ | `level` | Siempre |
68
+ | `service` | Siempre |
69
+ | `root_id` | Cuando hay trace context activo |
70
+ | `trace_id` | Cuando hay trace context activo |
71
+ | `source` | Cuando hay trace context activo |
72
+ | `correlation_id` | Cuando `Current.correlation_id` está presente |
73
+ | `user_id` | Cuando `Current.user_id` está presente |
74
+ | `isp_id` | Cuando `Current.isp_id` está presente |
75
+ | `sidekiq_job` | Solo en procesos Sidekiq |
76
+ | `task` | Solo en procesos TaskMonitor |
77
+ | `tags` | Solo si hay Rails tagged logging activo |
78
+
79
+ ---
80
+
81
+ ## Reglas de Ejecución
82
+
83
+ ### Logging
84
+ - Todo log interno usa KV strings: `component=exis_ray event=algo`
85
+ - `component` siempre en `snake_case`
86
+ - DEBUG siempre en block form: `logger.debug { "k=#{v}" }`
87
+ - Nunca `Kernel#warn` ni `$stderr`
88
+ - Toda operación de logging envuelta en `rescue StandardError`
89
+ - Duraciones con `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
90
+
91
+ ### Seguridad
92
+ - Claves sensibles (`password|pass|passwd|secret|token|api_key|auth`) → `[FILTERED]`
93
+ - Nunca loguear PII. Solo `user_id`, `isp_id`
94
+
95
+ ### Source válidos
96
+ `http` | `sidekiq` | `task` | `system`
97
+
98
+ ### Propagación de headers
99
+ - **Entrante HTTP:** `trace_header` (formato Rack: `HTTP_X_AMZN_TRACE_ID`) — solo en `HttpMiddleware`
100
+ - **Saliente (todos los transportes):** `propagation_trace_header` (formato HTTP: `X-Amzn-Trace-Id`)
101
+
102
+ ### Prohibiciones
103
+ - No Lograge — reemplazado por `LogSubscriber`
104
+ - No loguear manualmente: `time`, `level`, `service`, `source`, `root_id`, `trace_id`, `correlation_id`, `sidekiq_job`, `task`
105
+ - No referenciar `.gemini/` — obsoleto
106
+
107
+ ---
108
+
109
+ ## Compatibilidad Rails
110
+
111
+ - **Reloading:** Usar `cache_classes?` helper (verifica `respond_to?(:enable_reloading)`) para Rails 7.1+
112
+ - **Notifications:** Rails 7.1+ usa `all_listeners_for`, Rails 6/7.0 usa `listeners_for` — siempre usar `respond_to?` guards
113
+ - **Soporte:** Rails 6, 7 y 8
114
+
115
+ ---
116
+
117
+ ## Integración Automática (Railtie)
118
+
119
+ El `Railtie` en `after_initialize` detecta y auto-instrumenta sin intervención del desarrollador:
120
+
121
+ | Gema detectada | Qué hace |
122
+ |:---------------|:---------|
123
+ | `BugBunny` | Registra `PublisherTracing` no — ese va en el cliente. Registra `ConsumerTracingMiddleware` y los hooks RPC |
124
+ | `Sidekiq` | Registra client + server middleware |
125
+ | `ActiveResource` | Prepend de `ActiveResourceInstrumentation` |
126
+ | `Faraday` | Disponible como middleware opcional |
127
+
128
+ ---
129
+
130
+ ## Configuración Mínima
131
+
132
+ ```ruby
133
+ # config/initializers/exis_ray.rb
134
+ ExisRay.configure do |config|
135
+ config.log_format = :json # :text por defecto
136
+ config.trace_header = 'HTTP_X_AMZN_TRACE_ID'
137
+ config.propagation_trace_header = 'X-Amzn-Trace-Id'
138
+ config.current_class = 'Current'
139
+ config.reporter_class = 'Reporter'
140
+ end
141
+
142
+ # BugBunny publisher — debe agregarse manualmente al cliente
143
+ BugBunny::Client.new(pool: pool) do |stack|
144
+ stack.use ExisRay::BugBunny::PublisherTracing
145
+ end
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Testing en Consola
151
+
152
+ ```ruby
153
+ # Inicializar trace context para probar desde rails console
154
+ ExisRay::Tracer.hydrate(
155
+ trace_id: "Root=1-#{Time.now.to_i.to_s(16)}-#{SecureRandom.hex(12)}",
156
+ source: 'system'
157
+ )
158
+ ```
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
  ```
@@ -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
@@ -148,7 +148,7 @@ module ExisRay
148
148
  end
149
149
 
150
150
  # Filtra un valor si la clave se considera sensible.
151
- # Si no es sensible, intenta castear el valor a número si corresponde.
151
+ # Si no es sensible, intenta castear el valor a número o a objeto JSON si corresponde.
152
152
  #
153
153
  # @param key [String, Symbol]
154
154
  # @param value [Object]
@@ -159,10 +159,24 @@ module ExisRay
159
159
  case value
160
160
  when /\A\d+\z/ then value.to_i
161
161
  when /\A\d+\.\d+\z/ then value.to_f
162
- else value
162
+ else try_parse_json(value)
163
163
  end
164
164
  end
165
165
 
166
+ # Intenta parsear un string como JSON si parece un objeto o array.
167
+ # Permite que valores como queue_opts={"exclusive":false} se emitan como
168
+ # objetos JSON en lugar de strings escapados.
169
+ #
170
+ # @param value [Object]
171
+ # @return [Object] El objeto parseado o el valor original si no es JSON válido.
172
+ def try_parse_json(value)
173
+ return value unless value.is_a?(String) && (value.start_with?("{") || value.start_with?("["))
174
+
175
+ JSON.parse(value)
176
+ rescue JSON::ParserError
177
+ value
178
+ end
179
+
166
180
  # Filtra recursivamente un Hash que contenga claves sensibles.
167
181
  # Maneja valores anidados de tipo Hash o Array.
168
182
  #
@@ -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.7"
5
+ VERSION = "0.5.9"
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.7
4
+ version: 0.5.9
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-27 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
@@ -45,6 +45,12 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".claude/commands/release.md"
49
+ - ".claude/skills/opentelemetry/SKILL.md"
50
+ - ".claude/skills/rails-expert/SKILL.md"
51
+ - ".claude/skills/readme-writer/SKILL.md"
52
+ - ".claude/skills/rubocop-omakase/SKILL.md"
53
+ - ".claude/skills/yard/SKILL.md"
48
54
  - ".gemini/docs/TELEMETRY_GUIDELINES.md"
49
55
  - CHANGELOG.md
50
56
  - CLAUDE.md
@@ -54,7 +60,7 @@ files:
54
60
  - Rakefile
55
61
  - lib/exis_ray.rb
56
62
  - lib/exis_ray/active_resource_instrumentation.rb
57
- - lib/exis_ray/bug_bunny/consumer_tracing.rb
63
+ - lib/exis_ray/bug_bunny/consumer_tracing_middleware.rb
58
64
  - lib/exis_ray/bug_bunny/publisher_tracing.rb
59
65
  - lib/exis_ray/configuration.rb
60
66
  - lib/exis_ray/current.rb
@@ -1,63 +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 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