bug_bunny 4.8.1 → 4.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/CLAUDE.md +13 -7
- data/README.md +5 -3
- data/lib/bug_bunny/consumer.rb +21 -5
- data/lib/bug_bunny/controller.rb +1 -1
- data/lib/bug_bunny/otel.rb +47 -0
- data/lib/bug_bunny/producer.rb +13 -4
- data/lib/bug_bunny/request.rb +14 -2
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -0
- data/skill/SKILL.md +25 -2
- data/skill/references/client-middleware.md +17 -0
- data/skill/references/consumer.md +25 -7
- data/skills.lock +10 -1
- data/skills.yml +34 -9
- data/spec/integration/consumer_middleware_spec.rb +23 -2
- data/spec/unit/consumer_spec.rb +138 -6
- data/spec/unit/otel_spec.rb +54 -0
- data/spec/unit/producer_spec.rb +187 -0
- data/spec/unit/request_spec.rb +51 -0
- metadata +7 -15
- data/.agents/skills/documentation-writer/SKILL.md +0 -45
- data/.agents/skills/gem-release/SKILL.md +0 -114
- data/.agents/skills/quality-code/SKILL.md +0 -51
- data/.agents/skills/rabbitmq-expert/SKILL.md +0 -1555
- data/.agents/skills/sentry/SKILL.md +0 -135
- data/.agents/skills/sentry/references/api-endpoints.md +0 -147
- data/.agents/skills/sentry/scripts/sentry.rb +0 -194
- data/.agents/skills/skill-builder/SKILL.md +0 -232
- data/.agents/skills/skill-manager/SKILL.md +0 -172
- data/.agents/skills/skill-manager/scripts/sync.rb +0 -310
- data/.agents/skills/yard/SKILL.md +0 -311
- data/.agents/skills/yard/references/tipos.md +0 -144
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c70dd01d8d666fe56dbb95bc925ee431133cc0c342dc113a0775a4dddd4ce135
|
|
4
|
+
data.tar.gz: 2917d1105969a8173a8b9f70944d9b5839122a56185a5334db0e1c3a04594d2c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 241b7e0e684aa866d98940a2a3bc587ed13cf1da65c5708ff983c439078c40d823f13cac54ee8bf6d735dda138050c598c8cbf06759605523b8b2207de0e8e4d
|
|
7
|
+
data.tar.gz: 0b4f509d46dbe3ebd43bf8421bd9938bd2aa5d68ca23e639c3c1d232d5ad9d41b55cec0a4a0096cf8481e46c5d06163ce959d0f0167636d9084bf5d8aa3c9ba2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.9.1] - 2026-04-06
|
|
4
|
+
|
|
5
|
+
### Correcciones
|
|
6
|
+
- Corregir `ArgumentError` en `Controller#process` con ActiveSupport 8.1: `Rails.logger.tagged` ahora pasa el logger como argumento al bloque (`yield self`), lo que causa error de aridad en lambdas estrictos. Se reemplaza `lambda do` por `proc do` en `core_execution` para ignorar argumentos extra. Compatible con Rails 6, 7 y 8. — @Gabriel
|
|
7
|
+
|
|
8
|
+
## [4.9.0] - 2026-04-05
|
|
9
|
+
|
|
10
|
+
### ✨ New Features
|
|
11
|
+
* **OTel messaging semantic conventions:** BugBunny ahora emite los campos del estándar [OpenTelemetry semantic conventions for messaging](https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/) tanto en los headers AMQP de publish/reply como en los log events del consumer. Los campos emitidos son `messaging.system` (`"rabbitmq"`), `messaging.operation` (`"publish"` / `"process"`), `messaging.destination.name`, `messaging.rabbitmq.destination.routing_key` y `messaging.message.id` (cuando hay `correlation_id`). Permite que dashboards OTel-native (Tempo, Jaeger, Honeycomb) rendericen correctamente los spans de RabbitMQ y que ExisRay los consuma automáticamente desde `properties.headers`.
|
|
12
|
+
* **`BugBunny::OTel` module:** Nuevo módulo con las constantes de las claves OTel y el helper `messaging_headers` para construir el hash de campos. Los headers del usuario pueden sobrescribir valores OTel como escape hatch, pero `x-http-method` sigue siendo inmutable.
|
|
13
|
+
|
|
3
14
|
## [4.8.1] - 2026-04-04
|
|
4
15
|
|
|
5
16
|
### Mejoras internas
|
data/CLAUDE.md
CHANGED
|
@@ -6,32 +6,38 @@ BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre
|
|
|
6
6
|
|
|
7
7
|
**Problema que resuelve:** Eliminar el acoplamiento directo entre microservicios via HTTP, usando RabbitMQ como bus de mensajes con la misma ergonomía de un framework web.
|
|
8
8
|
|
|
9
|
+
## Documentación
|
|
10
|
+
|
|
11
|
+
- **Para humanos**: `docs/` (5 archivos) + `README.md`. Ver README para índice.
|
|
12
|
+
- **Para agentes AI**: `skill/SKILL.md` + `skill/references/`. Es la skill empaquetada que otros proyectos consumen via `skill-manager sync`.
|
|
13
|
+
- **Nunca referenciar `skill/` desde `docs/` o `README.md`** — son audiencias distintas.
|
|
14
|
+
|
|
9
15
|
## Knowledge Base
|
|
10
16
|
- Las skills en `.agents/skills/` incluyen conocimiento de dependencias.
|
|
11
17
|
- Leer la skill de una dependencia ANTES de responder sobre ella.
|
|
12
18
|
- Rebuild: `ruby .agents/skills/skill-manager/scripts/sync.rb`
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
### Entorno
|
|
15
21
|
- Versión de Ruby: leer `.ruby-version`
|
|
16
22
|
- Versión de Rails y gemas: leer `Gemfile.lock`
|
|
17
23
|
- Gestor de Ruby: chruby (no usar rvm ni rbenv)
|
|
18
24
|
- Package manager: Bundler
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
### RuboCop
|
|
21
27
|
- Usamos rubocop-rails-omakase como base.
|
|
22
28
|
- Correr `bundle exec rubocop -a` antes de commitear.
|
|
23
29
|
- No deshabilitar cops sin justificación en el PR.
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
### YARD
|
|
26
32
|
- Documentación incremental: si tocás un método, documentalo con YARD.
|
|
27
33
|
- Consultar la skill `yard` para tags y tipos correctos.
|
|
28
34
|
- Verificar cobertura: `bundle exec yard stats --list-undoc`
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
### Testing
|
|
31
37
|
- Framework: RSpec
|
|
32
38
|
- Correr: `bundle exec rspec`
|
|
33
39
|
- Todo código nuevo debe tener tests.
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
41
|
+
### Releases o Nuevas versiones
|
|
42
|
+
- Usar `/gem-release` para publicar nuevas versiones.
|
|
43
|
+
- El GitHub Action publica a RubyGems automáticamente al pushear un tag `v*`.
|
data/README.md
CHANGED
|
@@ -214,15 +214,17 @@ BugBunny.consumer_middlewares.use TracingMiddleware
|
|
|
214
214
|
|
|
215
215
|
## Observability
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
BugBunny implementa de forma nativa las [OpenTelemetry semantic conventions for messaging](https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/), inyectando automáticamente campos como `messaging_system`, `messaging_operation`, `messaging_destination_name` y `messaging_message_id` tanto en los headers AMQP como en los log events estructurados.
|
|
218
|
+
|
|
219
|
+
Todos los eventos internos se emiten como logs `key=value` compatibles con Datadog, CloudWatch, ELK y ExisRay.
|
|
218
220
|
|
|
219
221
|
```
|
|
220
|
-
component=bug_bunny event=consumer.message_processed status=200 duration_s=0.012 controller=NodesController action=show
|
|
222
|
+
component=bug_bunny event=consumer.message_processed status=200 duration_s=0.012 messaging_operation=process controller=NodesController action=show
|
|
221
223
|
component=bug_bunny event=consumer.execution_error error_class=RuntimeError error_message="..." duration_s=0.003
|
|
222
224
|
component=bug_bunny event=consumer.connection_error attempt_count=2 retry_in_s=10 error_message="..."
|
|
223
225
|
```
|
|
224
226
|
|
|
225
|
-
|
|
227
|
+
Las claves sensibles (`password`, `token`, `secret`, `api_key`, `authorization`, etc.) se filtran automáticamente a `[FILTERED]` en toda la salida de logs.
|
|
226
228
|
|
|
227
229
|
---
|
|
228
230
|
|
data/lib/bug_bunny/consumer.rb
CHANGED
|
@@ -152,6 +152,15 @@ module BugBunny
|
|
|
152
152
|
def process_message(delivery_info, properties, body)
|
|
153
153
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
154
154
|
|
|
155
|
+
# Campos OTel semantic conventions para los log events del consumer.
|
|
156
|
+
# Se mergean con ** en los safe_log de recepción y procesamiento.
|
|
157
|
+
otel_fields = BugBunny::OTel.messaging_headers(
|
|
158
|
+
operation: 'process',
|
|
159
|
+
destination: delivery_info.exchange,
|
|
160
|
+
routing_key: delivery_info.routing_key,
|
|
161
|
+
message_id: properties.correlation_id
|
|
162
|
+
)
|
|
163
|
+
|
|
155
164
|
# 1. Validación de Headers (URL path)
|
|
156
165
|
path = properties.type || (properties.headers && properties.headers['path'])
|
|
157
166
|
|
|
@@ -166,7 +175,7 @@ module BugBunny
|
|
|
166
175
|
http_method = (headers_hash['x-http-method'] || headers_hash['method'] || 'GET').to_s.upcase
|
|
167
176
|
|
|
168
177
|
safe_log(:info, 'consumer.message_received', method: http_method, path: path,
|
|
169
|
-
routing_key: delivery_info.routing_key)
|
|
178
|
+
routing_key: delivery_info.routing_key, **otel_fields)
|
|
170
179
|
safe_log(:debug, 'consumer.message_received_body', body: body.truncate(200))
|
|
171
180
|
|
|
172
181
|
# ===================================================================
|
|
@@ -239,10 +248,11 @@ module BugBunny
|
|
|
239
248
|
session.channel.ack(delivery_info.delivery_tag)
|
|
240
249
|
|
|
241
250
|
safe_log(:info, 'consumer.message_processed',
|
|
242
|
-
|
|
251
|
+
response_status: response_payload[:status],
|
|
243
252
|
duration_s: duration_s(start_time),
|
|
244
253
|
controller: controller_class_name,
|
|
245
|
-
action: route_info[:action]
|
|
254
|
+
action: route_info[:action],
|
|
255
|
+
**otel_fields)
|
|
246
256
|
rescue StandardError => e
|
|
247
257
|
safe_log(:error, 'consumer.execution_error', duration_s: duration_s(start_time), **exception_metadata(e))
|
|
248
258
|
safe_log(:debug, 'consumer.execution_error_backtrace', backtrace: e.backtrace.first(5).join(' | '))
|
|
@@ -257,14 +267,20 @@ module BugBunny
|
|
|
257
267
|
# @param correlation_id [String] ID para correlacionar la respuesta con la petición original.
|
|
258
268
|
# @return [void]
|
|
259
269
|
def reply(payload, reply_to, correlation_id)
|
|
260
|
-
safe_log(:debug, 'consumer.rpc_reply', reply_to: reply_to,
|
|
270
|
+
safe_log(:debug, 'consumer.rpc_reply', reply_to: reply_to, messaging_message_id: correlation_id)
|
|
271
|
+
otel_headers = BugBunny::OTel.messaging_headers(
|
|
272
|
+
operation: 'publish',
|
|
273
|
+
destination: '',
|
|
274
|
+
routing_key: reply_to,
|
|
275
|
+
message_id: correlation_id
|
|
276
|
+
)
|
|
261
277
|
extra_headers = BugBunny.configuration.rpc_reply_headers&.call || {}
|
|
262
278
|
session.channel.default_exchange.publish(
|
|
263
279
|
payload.to_json,
|
|
264
280
|
routing_key: reply_to,
|
|
265
281
|
correlation_id: correlation_id,
|
|
266
282
|
content_type: 'application/json',
|
|
267
|
-
headers: extra_headers
|
|
283
|
+
headers: otel_headers.transform_keys(&:to_s).merge(extra_headers)
|
|
268
284
|
)
|
|
269
285
|
end
|
|
270
286
|
|
data/lib/bug_bunny/controller.rb
CHANGED
|
@@ -165,7 +165,7 @@ module BugBunny
|
|
|
165
165
|
current_arounds = resolve_callbacks(self.class.around_actions, action_name)
|
|
166
166
|
|
|
167
167
|
# Definir el núcleo de ejecución
|
|
168
|
-
core_execution =
|
|
168
|
+
core_execution = proc do
|
|
169
169
|
return unless run_before_actions(action_name)
|
|
170
170
|
|
|
171
171
|
raise NameError, "Action '#{action_name}' not found in #{self.class.name}" unless respond_to?(action_name)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BugBunny
|
|
4
|
+
# Helpers para emitir campos siguiendo las OTel semantic conventions for messaging.
|
|
5
|
+
# https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/
|
|
6
|
+
#
|
|
7
|
+
# Se usa tanto en el lado publisher (inyección en headers AMQP) como en el consumer
|
|
8
|
+
# (enriquecimiento de log events estructurados). Centraliza las claves para evitar
|
|
9
|
+
# strings mágicos dispersos y facilitar los tests.
|
|
10
|
+
module OTel
|
|
11
|
+
# Clave: sistema de mensajería. Siempre `"rabbitmq"` en BugBunny.
|
|
12
|
+
# Flat-naming siguiendo el patrón de ExisRay (underscore sin dots).
|
|
13
|
+
SYSTEM = :messaging_system
|
|
14
|
+
# Clave: tipo de operación (`publish`, `receive`, `process`).
|
|
15
|
+
OPERATION = :messaging_operation
|
|
16
|
+
# Clave: nombre del exchange destino.
|
|
17
|
+
DESTINATION = :messaging_destination_name
|
|
18
|
+
# Clave: routing key del mensaje (específica de RabbitMQ).
|
|
19
|
+
ROUTING_KEY = :messaging_routing_key
|
|
20
|
+
# Clave: identificador único del mensaje. En BugBunny se mapea a `correlation_id`.
|
|
21
|
+
MESSAGE_ID = :messaging_message_id
|
|
22
|
+
|
|
23
|
+
# Valor constante para {SYSTEM}.
|
|
24
|
+
SYSTEM_VALUE = 'rabbitmq'
|
|
25
|
+
|
|
26
|
+
# Construye el hash de campos OTel para messaging.
|
|
27
|
+
#
|
|
28
|
+
# Los campos son aptos tanto para inyectar en headers AMQP como para mergear
|
|
29
|
+
# en kwargs de log events estructurados.
|
|
30
|
+
#
|
|
31
|
+
# @param operation [String] Una de: `"publish"`, `"receive"`, `"process"`.
|
|
32
|
+
# @param destination [String, nil] Nombre del exchange destino (puede ser `""` para default exchange).
|
|
33
|
+
# @param routing_key [String, nil] Routing key final del mensaje.
|
|
34
|
+
# @param message_id [String, nil] Identificador del mensaje. Se omite si es `nil`.
|
|
35
|
+
# @return [Hash{String=>String}] Hash con los campos OTel de messaging.
|
|
36
|
+
def self.messaging_headers(operation:, destination:, routing_key:, message_id: nil)
|
|
37
|
+
fields = {
|
|
38
|
+
SYSTEM => SYSTEM_VALUE,
|
|
39
|
+
OPERATION => operation,
|
|
40
|
+
DESTINATION => destination.to_s,
|
|
41
|
+
ROUTING_KEY => routing_key.to_s
|
|
42
|
+
}
|
|
43
|
+
fields[MESSAGE_ID] = message_id.to_s if message_id
|
|
44
|
+
fields
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/bug_bunny/producer.rb
CHANGED
|
@@ -79,7 +79,7 @@ module BugBunny
|
|
|
79
79
|
begin
|
|
80
80
|
fire(request)
|
|
81
81
|
|
|
82
|
-
safe_log(:debug, 'producer.rpc_waiting',
|
|
82
|
+
safe_log(:debug, 'producer.rpc_waiting', messaging_message_id: cid, timeout_s: wait_timeout)
|
|
83
83
|
|
|
84
84
|
# Bloqueamos el hilo aquí hasta que llegue la respuesta o expire el timeout
|
|
85
85
|
result = future.value(wait_timeout)
|
|
@@ -88,7 +88,8 @@ module BugBunny
|
|
|
88
88
|
|
|
89
89
|
BugBunny.configuration.on_rpc_reply&.call(result[:headers])
|
|
90
90
|
|
|
91
|
-
safe_log(:debug, 'producer.rpc_response_received',
|
|
91
|
+
safe_log(:debug, 'producer.rpc_response_received',
|
|
92
|
+
messaging_system: 'rabbitmq', messaging_operation: 'receive', messaging_message_id: cid)
|
|
92
93
|
|
|
93
94
|
parse_response(result[:body])
|
|
94
95
|
ensure
|
|
@@ -109,13 +110,21 @@ module BugBunny
|
|
|
109
110
|
rk = request.final_routing_key
|
|
110
111
|
id = request.correlation_id
|
|
111
112
|
|
|
113
|
+
otel_fields = BugBunny::OTel.messaging_headers(
|
|
114
|
+
operation: 'publish',
|
|
115
|
+
destination: request.exchange,
|
|
116
|
+
routing_key: rk,
|
|
117
|
+
message_id: id
|
|
118
|
+
)
|
|
119
|
+
|
|
112
120
|
# 📊 LOGGING DE OBSERVABILIDAD: Calculamos las opciones finales para mostrarlas en consola
|
|
113
121
|
final_x_opts = BugBunny::Session::DEFAULT_EXCHANGE_OPTIONS
|
|
114
122
|
.merge(BugBunny.configuration.exchange_options || {})
|
|
115
123
|
.merge(request.exchange_options || {})
|
|
116
124
|
|
|
117
|
-
safe_log(:info, 'producer.publish', method: verb, path: target,
|
|
118
|
-
safe_log(:debug, 'producer.publish_detail',
|
|
125
|
+
safe_log(:info, 'producer.publish', method: verb, path: target, **otel_fields)
|
|
126
|
+
safe_log(:debug, 'producer.publish_detail', messaging_destination_name: request.exchange,
|
|
127
|
+
exchange_opts: final_x_opts)
|
|
119
128
|
safe_log(:debug, 'producer.publish_payload', payload: payload.truncate(300)) if payload.is_a?(String)
|
|
120
129
|
end
|
|
121
130
|
|
data/lib/bug_bunny/request.rb
CHANGED
|
@@ -86,10 +86,22 @@ module BugBunny
|
|
|
86
86
|
# **Importante:** Inyecta el verbo HTTP en los headers bajo la clave `x-http-method`.
|
|
87
87
|
# Esto permite al Consumer enrutar correctamente a la acción del controlador.
|
|
88
88
|
#
|
|
89
|
+
# También inyecta los campos de OTel semantic conventions for messaging
|
|
90
|
+
# (ver {BugBunny::OTel}) con `operation=publish`. Los headers del usuario
|
|
91
|
+
# pueden sobrescribir los valores OTel (escape hatch); `x-http-method`
|
|
92
|
+
# nunca se pisa porque es lo último en el merge.
|
|
93
|
+
#
|
|
89
94
|
# @return [Hash] Opciones listas para pasar a `exchange.publish`.
|
|
90
95
|
def amqp_options
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
otel_headers = BugBunny::OTel.messaging_headers(
|
|
97
|
+
operation: 'publish',
|
|
98
|
+
destination: exchange,
|
|
99
|
+
routing_key: final_routing_key,
|
|
100
|
+
message_id: correlation_id
|
|
101
|
+
)
|
|
102
|
+
# Orden del merge: OTel base -> headers del usuario -> x-http-method (inmutable)
|
|
103
|
+
# OTel keys son symbols internamente; los stringificamos para Bunny AMQP headers.
|
|
104
|
+
final_headers = otel_headers.transform_keys(&:to_s).merge(headers).merge('x-http-method' => method.to_s.upcase)
|
|
93
105
|
|
|
94
106
|
{
|
|
95
107
|
type: final_type,
|
data/lib/bug_bunny/version.rb
CHANGED
data/lib/bug_bunny.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'bug_bunny/version'
|
|
|
6
6
|
require_relative 'bug_bunny/exception'
|
|
7
7
|
require_relative 'bug_bunny/configuration'
|
|
8
8
|
require_relative 'bug_bunny/observability'
|
|
9
|
+
require_relative 'bug_bunny/otel'
|
|
9
10
|
require_relative 'bug_bunny/routing/route_set'
|
|
10
11
|
require_relative 'bug_bunny/middleware/base'
|
|
11
12
|
require_relative 'bug_bunny/middleware/stack'
|
data/skill/SKILL.md
CHANGED
|
@@ -68,8 +68,31 @@ Skill de conocimiento completo sobre BugBunny. Consultame para cualquier pregunt
|
|
|
68
68
|
| `BugBunny::Routing::RouteSet` | DSL de rutas: `resources`, `namespace`, `member`, `collection`. |
|
|
69
69
|
| `BugBunny::Observability` | Mixin de logging estructurado. `safe_log` nunca lanza excepciones. Filtra keys sensibles. |
|
|
70
70
|
| `BugBunny::Middleware::Stack` | Builder de middlewares client-side (onion architecture tipo Faraday). |
|
|
71
|
-
|
|
|
72
|
-
|
|
|
71
|
+
| BugBunny::Request | Value object del mensaje saliente con metadata AMQP completa. |
|
|
72
|
+
| BugBunny::OTel | Helpers para emitir campos siguiendo las OTel semantic conventions for messaging. |
|
|
73
|
+
| BugBunny::Railtie | Integración Rails: autoload de `app/rabbit`, fork safety (Puma, Spring). |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Observability: OpenTelemetry
|
|
78
|
+
|
|
79
|
+
BugBunny implementa las [OpenTelemetry semantic conventions for messaging](https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/) de forma nativa para garantizar la trazabilidad entre servicios en entornos distribuidos.
|
|
80
|
+
|
|
81
|
+
### Campos Estándar (Flat-naming)
|
|
82
|
+
|
|
83
|
+
| Campo | Valor / Origen | Propósito |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `messaging_system` | `"rabbitmq"` | Identifica el broker. |
|
|
86
|
+
| `messaging_operation` | `"publish"`, `"receive"`, `"process"` | Tipo de interacción. |
|
|
87
|
+
| `messaging_destination_name` | `exchange_name` | Exchange destino (o `""` para default). |
|
|
88
|
+
| `messaging_routing_key` | `routing_key` | Clave de ruteo final. |
|
|
89
|
+
| `messaging_message_id` | `correlation_id` | ID único para correlación y traza. |
|
|
90
|
+
|
|
91
|
+
### Inyección y Extracción
|
|
92
|
+
|
|
93
|
+
- **Publisher:** Inyecta estos campos en los headers AMQP bajo el prefijo `messaging_`. El usuario puede sobrescribirlos como *escape hatch* desde `headers`.
|
|
94
|
+
- **Consumer:** Extrae los campos de los logs estructurados sin mutar los headers originales. Los eventos `consumer.message_received` y `consumer.message_processed` incluyen estos campos automáticamente.
|
|
95
|
+
- **RPC Reply:** El consumer inyecta los mismos campos en el reply para cerrar el ciclo de traza del lado del cliente.
|
|
73
96
|
|
|
74
97
|
---
|
|
75
98
|
|
|
@@ -107,6 +107,23 @@ El método `call` del `Base` invoca `on_request`, delega a `@app.call`, y luego
|
|
|
107
107
|
|
|
108
108
|
**JsonResponse** — Auto-parsea `response['body']` de String a Hash/Array. Aplica `HashWithIndifferentAccess` si disponible.
|
|
109
109
|
|
|
110
|
+
## OpenTelemetry: Publisher Injection
|
|
111
|
+
|
|
112
|
+
El `Producer` (vía `Request#amqp_options`) inyecta automáticamente los campos de OTel semantic conventions en los headers AMQP del mensaje saliente.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# Headers inyectados automáticamente
|
|
116
|
+
{
|
|
117
|
+
'messaging_system' => 'rabbitmq',
|
|
118
|
+
'messaging_operation' => 'publish',
|
|
119
|
+
'messaging_destination_name' => 'exchange_name',
|
|
120
|
+
'messaging_routing_key' => 'rk',
|
|
121
|
+
'messaging_message_id' => 'uuid'
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
El orden de merge es: **OTel base** → **headers del usuario** → **x-http-method**. Esto permite al desarrollador sobrescribir valores de OTel si es necesario, pero garantiza que el ruteo interno (`x-http-method`) se mantenga íntegro.
|
|
126
|
+
|
|
110
127
|
## Request Object
|
|
111
128
|
|
|
112
129
|
Value object con toda la metadata AMQP:
|
|
@@ -18,13 +18,31 @@ consumer = BugBunny::Consumer.subscribe(
|
|
|
18
18
|
## Flujo de Procesamiento
|
|
19
19
|
|
|
20
20
|
1. Escucha en la queue con `manual_ack: true`.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
24
|
-
5.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
27
|
-
8.
|
|
21
|
+
2. Extrae campos **OTel messaging** del mensaje para logs estructurados (sin mutar headers).
|
|
22
|
+
3. Valida que el mensaje tenga header `type` (path).
|
|
23
|
+
4. Parsea el método HTTP de headers (`x-http-method` o `method`).
|
|
24
|
+
5. Emite log `consumer.message_received` con campos OTel (`messaging_operation: 'process'`).
|
|
25
|
+
6. Reconoce la ruta con `BugBunny.routes.recognize(method, path)`.
|
|
26
|
+
7. Resuelve el controlador validando herencia de `BugBunny::Controller`.
|
|
27
|
+
8. Ejecuta consumer middlewares → controller callbacks → acción.
|
|
28
|
+
9. Responde via `reply_to` si está presente (RPC), inyectando campos OTel (`messaging_operation: 'publish'`).
|
|
29
|
+
10. Emite log `consumer.message_processed` con campos OTel y duraciones.
|
|
30
|
+
11. Hace `ack` del mensaje. En caso de error, `reject`.
|
|
31
|
+
|
|
32
|
+
## Observability: OTel Fields
|
|
33
|
+
|
|
34
|
+
El consumer construye automáticamente el hash de campos OTel al inicio de `process_message`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
otel_fields = BugBunny::OTel.messaging_headers(
|
|
38
|
+
operation: 'process',
|
|
39
|
+
destination: delivery_info.exchange,
|
|
40
|
+
routing_key: delivery_info.routing_key,
|
|
41
|
+
message_id: properties.correlation_id
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Estos campos se mergean en todos los eventos de log del ciclo de vida del mensaje, permitiendo que ExisRay los rastree sin necesidad de propagarlos manualmente en los headers del usuario.
|
|
28
46
|
|
|
29
47
|
## Lifecycle
|
|
30
48
|
|
data/skills.lock
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
synced_at: '2026-04-
|
|
2
|
+
synced_at: '2026-04-06 14:08:54'
|
|
3
3
|
skills:
|
|
4
|
+
- name: agent-review
|
|
5
|
+
scope: local
|
|
6
|
+
path: "/Users/gabriel/src/gems/bug_bunny/.agents/skills/agent-review"
|
|
7
|
+
- name: ai-reports
|
|
8
|
+
scope: local
|
|
9
|
+
path: "/Users/gabriel/src/gems/bug_bunny/.agents/skills/ai-reports"
|
|
4
10
|
- name: documentation-writer
|
|
5
11
|
scope: local
|
|
6
12
|
path: "/Users/gabriel/src/gems/bug_bunny/.agents/skills/documentation-writer"
|
|
13
|
+
- name: find-skills
|
|
14
|
+
scope: local
|
|
15
|
+
path: "/Users/gabriel/src/gems/bug_bunny/.agents/skills/find-skills"
|
|
7
16
|
- name: gem-release
|
|
8
17
|
scope: local
|
|
9
18
|
path: "/Users/gabriel/src/gems/bug_bunny/.agents/skills/gem-release"
|
data/skills.yml
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
mcps:
|
|
2
2
|
- github
|
|
3
|
+
- clickup
|
|
3
4
|
skills:
|
|
4
|
-
|
|
5
|
+
skill-manager:
|
|
5
6
|
repo: sequre/ai_knowledge
|
|
6
|
-
|
|
7
|
+
scope: local
|
|
8
|
+
yard:
|
|
7
9
|
repo: sequre/ai_knowledge
|
|
8
|
-
|
|
10
|
+
scope: local
|
|
11
|
+
quality-code:
|
|
9
12
|
repo: sequre/ai_knowledge
|
|
10
|
-
|
|
13
|
+
scope: local
|
|
14
|
+
gem-release:
|
|
11
15
|
repo: sequre/ai_knowledge
|
|
12
|
-
|
|
16
|
+
scope: local
|
|
17
|
+
skill-builder:
|
|
13
18
|
repo: sequre/ai_knowledge
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
scope: local
|
|
20
|
+
ai-reports:
|
|
21
|
+
repo: sequre/ai_knowledge
|
|
22
|
+
scope: local
|
|
23
|
+
environment:
|
|
24
|
+
space_id: "${AI_REPORTS_SPACE_ID}"
|
|
25
|
+
bug_reports_list_id: "${AI_REPORTS_BUG_REPORTS_LIST_ID}"
|
|
26
|
+
improvements_list_id: "${AI_REPORTS_IMPROVEMENTS_LIST_ID}"
|
|
27
|
+
agent-review:
|
|
28
|
+
repo: sequre/ai_knowledge
|
|
29
|
+
scope: local
|
|
30
|
+
environment:
|
|
31
|
+
space_id: "${AGENT_REVIEW_SAPCE_ID}"
|
|
32
|
+
list_id: "${AGENT_LIST_ID}"
|
|
33
|
+
documentation-writer:
|
|
18
34
|
repo: github/awesome-copilot
|
|
19
35
|
path: skills/documentation-writer
|
|
36
|
+
scope: local
|
|
37
|
+
find-skills:
|
|
38
|
+
repo: vercel-labs/skills
|
|
39
|
+
path: skills/find-skills
|
|
40
|
+
scope: local
|
|
41
|
+
rabbitmq-expert:
|
|
42
|
+
repo: martinholovsky/claude-skills-generator
|
|
43
|
+
path: skills/rabbitmq-expert
|
|
44
|
+
scope: local
|
|
@@ -24,7 +24,7 @@ class TrackingMiddleware < BugBunny::ConsumerMiddleware::Base
|
|
|
24
24
|
def call(delivery_info, properties, body)
|
|
25
25
|
self.class.calls << {
|
|
26
26
|
routing_key: delivery_info.routing_key,
|
|
27
|
-
headers:
|
|
27
|
+
headers: properties.headers
|
|
28
28
|
}
|
|
29
29
|
@app.call(delivery_info, properties, body)
|
|
30
30
|
end
|
|
@@ -45,7 +45,7 @@ RSpec.describe 'Consumer Middleware Stack', :integration do
|
|
|
45
45
|
BugBunny.configure { |c| c.controller_namespace = 'BugBunny::Controllers' }
|
|
46
46
|
# Limpiamos el middleware para no afectar otros specs
|
|
47
47
|
BugBunny.configuration.instance_variable_set(:@consumer_middlewares,
|
|
48
|
-
|
|
48
|
+
BugBunny::ConsumerMiddleware::Stack.new)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
it 'ejecuta el middleware antes de process_message' do
|
|
@@ -82,5 +82,26 @@ RSpec.describe 'Consumer Middleware Stack', :integration do
|
|
|
82
82
|
BugBunny.configuration.rpc_reply_headers = nil
|
|
83
83
|
BugBunny.configuration.on_rpc_reply = nil
|
|
84
84
|
end
|
|
85
|
+
|
|
86
|
+
it 'incluye los campos OTel semantic conventions en el reply' do
|
|
87
|
+
received_headers = nil
|
|
88
|
+
|
|
89
|
+
BugBunny.configuration.rpc_reply_headers = -> { { 'X-Test-Header' => 'from-consumer' } }
|
|
90
|
+
BugBunny.configuration.on_rpc_reply = ->(headers) { received_headers = headers }
|
|
91
|
+
|
|
92
|
+
with_running_worker(queue: queue, exchange: exchange, routing_key: 'ping') do
|
|
93
|
+
client.request('ping', method: :get, exchange: exchange, exchange_type: 'topic', routing_key: 'ping')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
expect(received_headers).to include(
|
|
97
|
+
'messaging_system' => 'rabbitmq',
|
|
98
|
+
'messaging_operation' => 'publish',
|
|
99
|
+
'X-Test-Header' => 'from-consumer'
|
|
100
|
+
)
|
|
101
|
+
expect(received_headers['messaging_message_id']).not_to be_nil
|
|
102
|
+
ensure
|
|
103
|
+
BugBunny.configuration.rpc_reply_headers = nil
|
|
104
|
+
BugBunny.configuration.on_rpc_reply = nil
|
|
105
|
+
end
|
|
85
106
|
end
|
|
86
107
|
end
|