bug_bunny 3.0.1 → 3.0.2
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 +21 -0
- data/README.md +166 -197
- data/lib/bug_bunny/config.rb +10 -0
- data/lib/bug_bunny/consumer.rb +68 -44
- data/lib/bug_bunny/controller.rb +131 -57
- data/lib/bug_bunny/producer.rb +10 -2
- data/lib/bug_bunny/resource.rb +133 -33
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -1
- metadata +21 -19
data/lib/bug_bunny/consumer.rb
CHANGED
|
@@ -8,20 +8,37 @@ require 'cgi'
|
|
|
8
8
|
module BugBunny
|
|
9
9
|
# Consumidor de mensajes y Router RPC estilo REST.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# Esta clase se encarga de escuchar una cola específica, deserializar los mensajes,
|
|
12
|
+
# interpretar los headers REST (`x-http-method`, `type`) y despacharlos al
|
|
13
|
+
# controlador correspondiente.
|
|
14
|
+
#
|
|
15
|
+
# También gestiona el ciclo de vida de la respuesta RPC, asegurando que siempre
|
|
16
|
+
# se envíe una contestación (éxito o error) para evitar timeouts en el cliente.
|
|
13
17
|
class Consumer
|
|
18
|
+
# @return [BugBunny::Session] La sesión de RabbitMQ wrapper.
|
|
14
19
|
attr_reader :session
|
|
15
20
|
|
|
21
|
+
# Método de conveniencia para instanciar y suscribir en un solo paso.
|
|
22
|
+
# @param connection [Bunny::Session] Conexión activa.
|
|
23
|
+
# @param args [Hash] Argumentos para {#subscribe}.
|
|
16
24
|
def self.subscribe(connection:, **args)
|
|
17
25
|
new(connection).subscribe(**args)
|
|
18
26
|
end
|
|
19
27
|
|
|
28
|
+
# Inicializa el consumidor.
|
|
29
|
+
# @param connection [Bunny::Session] Conexión nativa de Bunny.
|
|
20
30
|
def initialize(connection)
|
|
21
31
|
@session = BugBunny::Session.new(connection)
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
# Inicia la suscripción a la cola.
|
|
34
|
+
# Inicia la suscripción a la cola y el procesamiento de mensajes.
|
|
35
|
+
#
|
|
36
|
+
# @param queue_name [String] Nombre de la cola a escuchar.
|
|
37
|
+
# @param exchange_name [String] Exchange al que se bindeará la cola.
|
|
38
|
+
# @param routing_key [String] Routing key para el binding.
|
|
39
|
+
# @param exchange_type [String] Tipo de exchange ('direct', 'topic', etc).
|
|
40
|
+
# @param queue_opts [Hash] Opciones de declaración de la cola (durable, auto_delete).
|
|
41
|
+
# @param block [Boolean] Si es true, bloquea el hilo principal (loop).
|
|
25
42
|
def subscribe(queue_name:, exchange_name:, routing_key:, exchange_type: 'direct', queue_opts: {}, block: true)
|
|
26
43
|
x = session.exchange(name: exchange_name, type: exchange_type)
|
|
27
44
|
q = session.queue(queue_name, queue_opts)
|
|
@@ -41,8 +58,16 @@ module BugBunny
|
|
|
41
58
|
|
|
42
59
|
private
|
|
43
60
|
|
|
44
|
-
# Procesa
|
|
45
|
-
#
|
|
61
|
+
# Procesa un mensaje individual.
|
|
62
|
+
#
|
|
63
|
+
# 1. Parsea headers y body.
|
|
64
|
+
# 2. Enruta al controlador/acción.
|
|
65
|
+
# 3. Envía la respuesta RPC si es necesario.
|
|
66
|
+
# 4. Maneja excepciones y envía errores formateados al cliente.
|
|
67
|
+
#
|
|
68
|
+
# @param delivery_info [Bunny::DeliveryInfo] Metadatos de entrega.
|
|
69
|
+
# @param properties [Bunny::MessageProperties] Headers y propiedades AMQP.
|
|
70
|
+
# @param body [String] Payload del mensaje.
|
|
46
71
|
def process_message(delivery_info, properties, body)
|
|
47
72
|
if properties.type.nil? || properties.type.empty?
|
|
48
73
|
BugBunny.configuration.logger.error("[Consumer] Missing 'type'. Rejected.")
|
|
@@ -50,11 +75,9 @@ module BugBunny
|
|
|
50
75
|
return
|
|
51
76
|
end
|
|
52
77
|
|
|
53
|
-
# 1. Leemos el verbo HTTP desde el header (Default: GET)
|
|
54
|
-
# Nota: Bunny devuelve los headers en propiedades.headers
|
|
55
78
|
http_method = properties.headers ? (properties.headers['x-http-method'] || 'GET') : 'GET'
|
|
56
79
|
|
|
57
|
-
#
|
|
80
|
+
# Inferencia de rutas (Router)
|
|
58
81
|
route_info = router_dispatch(http_method, properties.type)
|
|
59
82
|
|
|
60
83
|
headers = {
|
|
@@ -69,78 +92,78 @@ module BugBunny
|
|
|
69
92
|
reply_to: properties.reply_to
|
|
70
93
|
}
|
|
71
94
|
|
|
72
|
-
#
|
|
95
|
+
# Instanciación dinámica del controlador
|
|
96
|
+
# Ej: "users" -> Rabbit::Controllers::UsersController
|
|
73
97
|
controller_class_name = "rabbit/controllers/#{route_info[:controller]}".camelize
|
|
74
98
|
controller_class = controller_class_name.constantize
|
|
75
99
|
|
|
100
|
+
# Ejecución del pipeline del controlador
|
|
76
101
|
response_payload = controller_class.call(headers: headers, body: body)
|
|
77
102
|
|
|
103
|
+
# Respuesta RPC (Éxito)
|
|
78
104
|
if properties.reply_to
|
|
79
105
|
reply(response_payload, properties.reply_to, properties.correlation_id)
|
|
80
106
|
end
|
|
81
107
|
|
|
82
108
|
session.channel.ack(delivery_info.delivery_tag)
|
|
109
|
+
|
|
83
110
|
rescue NameError => e
|
|
84
|
-
|
|
111
|
+
# Caso: Controlador o Acción no existen (404/501)
|
|
112
|
+
BugBunny.configuration.logger.error("[Consumer] Routing Error: #{e.message}")
|
|
113
|
+
|
|
114
|
+
# FIX CRÍTICO: Responder con error para evitar Timeout en el cliente
|
|
115
|
+
if properties.reply_to
|
|
116
|
+
error_payload = { status: 501, body: { error: "Routing Error", detail: e.message } }
|
|
117
|
+
reply(error_payload, properties.reply_to, properties.correlation_id)
|
|
118
|
+
end
|
|
119
|
+
|
|
85
120
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
121
|
+
|
|
86
122
|
rescue StandardError => e
|
|
123
|
+
# Caso: Crash interno de la aplicación (500)
|
|
87
124
|
BugBunny.configuration.logger.error("[Consumer] Execution Error: #{e.message}")
|
|
88
|
-
|
|
125
|
+
|
|
126
|
+
# FIX CRÍTICO: Responder con 500 para evitar Timeout
|
|
89
127
|
if properties.reply_to
|
|
90
|
-
|
|
128
|
+
error_payload = { status: 500, body: { error: "Internal Server Error", detail: e.message } }
|
|
129
|
+
reply(error_payload, properties.reply_to, properties.correlation_id)
|
|
91
130
|
end
|
|
131
|
+
|
|
132
|
+
session.channel.reject(delivery_info.delivery_tag, false)
|
|
92
133
|
end
|
|
93
134
|
|
|
94
|
-
#
|
|
135
|
+
# Simula el Router de Rails.
|
|
136
|
+
# Convierte Verbo + Path en Controlador + Acción + ID.
|
|
95
137
|
#
|
|
96
|
-
# @
|
|
97
|
-
# @param path [String] URL Path (ej: 'users/1').
|
|
98
|
-
# @return [Hash] {controller, action, id, params}
|
|
138
|
+
# @return [Hash] { controller, action, id, params }
|
|
99
139
|
def router_dispatch(method, path)
|
|
100
140
|
uri = URI.parse("http://dummy/#{path}")
|
|
101
|
-
segments = uri.path.split('/').reject(&:empty?)
|
|
141
|
+
segments = uri.path.split('/').reject(&:empty?)
|
|
102
142
|
query_params = uri.query ? CGI.parse(uri.query).transform_values(&:first) : {}
|
|
103
143
|
|
|
104
|
-
controller_name = segments[0]
|
|
105
|
-
id = segments[1]
|
|
144
|
+
controller_name = segments[0]
|
|
145
|
+
id = segments[1]
|
|
106
146
|
|
|
107
|
-
# Lógica de Inferencia Rails Standard
|
|
108
|
-
# GET users -> index
|
|
109
|
-
# GET users/1 -> show
|
|
110
|
-
# POST users -> create
|
|
111
|
-
# PUT users/1 -> update
|
|
112
|
-
# DELETE users/1 -> destroy
|
|
113
147
|
action = case method.to_s.upcase
|
|
114
|
-
when 'GET'
|
|
115
|
-
|
|
116
|
-
when '
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
'update'
|
|
120
|
-
when 'DELETE'
|
|
121
|
-
'destroy'
|
|
122
|
-
else
|
|
123
|
-
id || 'index' # Fallback para verbos custom
|
|
148
|
+
when 'GET' then id ? 'show' : 'index'
|
|
149
|
+
when 'POST' then 'create'
|
|
150
|
+
when 'PUT', 'PATCH' then 'update'
|
|
151
|
+
when 'DELETE' then 'destroy'
|
|
152
|
+
else id || 'index'
|
|
124
153
|
end
|
|
125
154
|
|
|
126
|
-
# Soporte para Member Actions
|
|
127
|
-
# Path: users/1/activate -> segments: [users, 1, activate]
|
|
155
|
+
# Soporte para Custom Member Actions (POST users/1/promote)
|
|
128
156
|
if segments.size >= 3
|
|
129
157
|
id = segments[1]
|
|
130
158
|
action = segments[2]
|
|
131
159
|
end
|
|
132
160
|
|
|
133
|
-
# Inyectar ID en params para acceso unificado en el controller
|
|
134
161
|
query_params['id'] = id if id
|
|
135
162
|
|
|
136
|
-
{
|
|
137
|
-
controller: controller_name,
|
|
138
|
-
action: action,
|
|
139
|
-
id: id,
|
|
140
|
-
params: query_params
|
|
141
|
-
}
|
|
163
|
+
{ controller: controller_name, action: action, id: id, params: query_params }
|
|
142
164
|
end
|
|
143
165
|
|
|
166
|
+
# Envía la respuesta a la cola temporal del cliente (Direct Reply-to).
|
|
144
167
|
def reply(payload, reply_to, correlation_id)
|
|
145
168
|
session.channel.default_exchange.publish(
|
|
146
169
|
payload.to_json,
|
|
@@ -150,6 +173,7 @@ module BugBunny
|
|
|
150
173
|
)
|
|
151
174
|
end
|
|
152
175
|
|
|
176
|
+
# Tarea de fondo para asegurar que la cola sigue existiendo.
|
|
153
177
|
def start_health_check(q_name)
|
|
154
178
|
Concurrent::TimerTask.new(execution_interval: 60) do
|
|
155
179
|
session.channel.queue_declare(q_name, passive: true)
|
data/lib/bug_bunny/controller.rb
CHANGED
|
@@ -6,109 +6,183 @@ module BugBunny
|
|
|
6
6
|
# Clase base para Controladores de Mensajes.
|
|
7
7
|
#
|
|
8
8
|
# Provee una abstracción similar a ActionController para manejar peticiones RPC.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Incluye soporte para `before_action`, manejo de excepciones declarativo (`rescue_from`)
|
|
10
|
+
# y normalización de parámetros.
|
|
11
11
|
class Controller
|
|
12
12
|
include ActiveModel::Model
|
|
13
13
|
include ActiveModel::Attributes
|
|
14
14
|
|
|
15
|
-
# @return [Hash] Metadatos
|
|
15
|
+
# @return [Hash] Metadatos del mensaje (headers AMQP, routing info).
|
|
16
16
|
attribute :headers
|
|
17
17
|
|
|
18
|
-
# @return [ActiveSupport::HashWithIndifferentAccess] Parámetros unificados.
|
|
18
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] Parámetros unificados (Body + Query + Route).
|
|
19
19
|
attribute :params
|
|
20
20
|
|
|
21
|
-
# @return [String] Cuerpo crudo si no es JSON.
|
|
21
|
+
# @return [String] Cuerpo crudo si el payload no es JSON.
|
|
22
22
|
attribute :raw_string
|
|
23
23
|
|
|
24
|
-
# @return [Hash]
|
|
24
|
+
# @return [Hash, nil] La respuesta renderizada { status, body }.
|
|
25
25
|
attr_reader :rendered_response
|
|
26
26
|
|
|
27
|
+
# --- INFRAESTRUCTURA DE FILTROS (Before Actions) ---
|
|
28
|
+
|
|
27
29
|
# @api private
|
|
28
30
|
def self.before_actions
|
|
29
|
-
@before_actions ||= Hash.new { |
|
|
31
|
+
@before_actions ||= Hash.new { |h, k| h[k] = [] }
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
# Registra un callback
|
|
34
|
+
# Registra un callback que se ejecutará antes de las acciones.
|
|
35
|
+
#
|
|
36
|
+
# @param method_name [Symbol] Nombre del método a ejecutar.
|
|
37
|
+
# @param options [Hash] Opciones de filtro (:only).
|
|
38
|
+
# @example
|
|
39
|
+
# before_action :set_user, only: [:show, :update]
|
|
33
40
|
def self.before_action(method_name, **options)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
before_actions[
|
|
41
|
+
only = Array(options[:only]).map(&:to_sym)
|
|
42
|
+
target_actions = only.empty? ? [:_all_actions] : only
|
|
43
|
+
|
|
44
|
+
target_actions.each do |action|
|
|
45
|
+
before_actions[action] << method_name
|
|
39
46
|
end
|
|
40
47
|
end
|
|
41
48
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
# @
|
|
49
|
+
# --- INFRAESTRUCTURA DE MANEJO DE ERRORES (Rescue From) ---
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
def self.rescue_handlers
|
|
53
|
+
@rescue_handlers ||= []
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Registra un manejador para una o más excepciones.
|
|
57
|
+
# Los manejadores se evalúan en orden inverso (el último registrado tiene prioridad).
|
|
58
|
+
#
|
|
59
|
+
# @param klasses [Class] Clases de excepción a capturar.
|
|
60
|
+
# @param with [Symbol] Nombre del método manejador.
|
|
61
|
+
# @param block [Proc] Bloque manejador.
|
|
62
|
+
#
|
|
63
|
+
# @example Con método
|
|
64
|
+
# rescue_from User::NotAuthorized, with: :deny_access
|
|
65
|
+
#
|
|
66
|
+
# @example Con bloque
|
|
67
|
+
# rescue_from ActiveRecord::RecordNotFound do |e|
|
|
68
|
+
# render status: :not_found, json: { error: e.message }
|
|
69
|
+
# end
|
|
70
|
+
def self.rescue_from(*klasses, with: nil, &block)
|
|
71
|
+
handler = with || block
|
|
72
|
+
raise ArgumentError, "Need a handler. Supply 'with: :method' or a block." unless handler
|
|
73
|
+
|
|
74
|
+
klasses.each do |klass|
|
|
75
|
+
# Insertamos al principio para que las últimas definiciones tengan prioridad (LIFO)
|
|
76
|
+
rescue_handlers.unshift([klass, handler])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# --- PIPELINE DE EJECUCIÓN ---
|
|
81
|
+
|
|
82
|
+
# Punto de entrada principal llamado por el Consumer.
|
|
83
|
+
# Instancia el controlador y procesa el mensaje.
|
|
84
|
+
#
|
|
85
|
+
# @param headers [Hash] Metadatos del mensaje.
|
|
86
|
+
# @param body [Hash, String] Payload deserializado.
|
|
87
|
+
# @return [Hash] La respuesta final { status, body }.
|
|
45
88
|
def self.call(headers:, body: {})
|
|
46
|
-
|
|
47
|
-
|
|
89
|
+
new(headers: headers).process(body)
|
|
90
|
+
end
|
|
48
91
|
|
|
49
|
-
|
|
92
|
+
# Ejecuta el ciclo de vida de la petición: Params -> Filtros -> Acción.
|
|
93
|
+
# Captura cualquier error y delega al sistema `rescue_from`.
|
|
94
|
+
#
|
|
95
|
+
# @param body [Hash, String] Payload.
|
|
96
|
+
# @return [Hash] Respuesta RPC.
|
|
97
|
+
def process(body)
|
|
98
|
+
prepare_params(body)
|
|
99
|
+
action_name = headers[:action].to_sym
|
|
50
100
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
101
|
+
# 1. Ejecutar Before Actions (si retorna false, hubo render/halt)
|
|
102
|
+
return rendered_response unless run_before_actions(action_name)
|
|
103
|
+
|
|
104
|
+
# 2. Ejecutar Acción
|
|
105
|
+
if respond_to?(action_name)
|
|
106
|
+
public_send(action_name)
|
|
54
107
|
else
|
|
55
|
-
raise NameError, "Action '#{
|
|
108
|
+
raise NameError, "Action '#{action_name}' not found in #{self.class.name}"
|
|
56
109
|
end
|
|
57
110
|
|
|
58
|
-
|
|
111
|
+
# 3. Respuesta por defecto (204 No Content) si la acción no llamó a render
|
|
112
|
+
rendered_response || { status: 204, body: nil }
|
|
113
|
+
|
|
59
114
|
rescue StandardError => e
|
|
60
|
-
|
|
115
|
+
handle_exception(e)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Busca un manejador registrado para la excepción y lo ejecuta.
|
|
121
|
+
# Si no hay ninguno, loguea y devuelve 500.
|
|
122
|
+
def handle_exception(exception)
|
|
123
|
+
# Buscamos el primer handler compatible con la clase del error
|
|
124
|
+
handler_entry = self.class.rescue_handlers.find { |klass, _| exception.is_a?(klass) }
|
|
125
|
+
|
|
126
|
+
if handler_entry
|
|
127
|
+
_, handler = handler_entry
|
|
128
|
+
|
|
129
|
+
# Ejecutamos el handler en el contexto de la INSTANCIA
|
|
130
|
+
if handler.is_a?(Symbol)
|
|
131
|
+
send(handler, exception)
|
|
132
|
+
elsif handler.respond_to?(:call)
|
|
133
|
+
instance_exec(exception, &handler)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Si el handler hizo render, retornamos esa respuesta
|
|
137
|
+
return rendered_response if rendered_response
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# === FALLBACK POR DEFECTO ===
|
|
141
|
+
# Si el error no fue rescatado por el usuario, actuamos como red de seguridad.
|
|
142
|
+
BugBunny.configuration.logger.error("Controller Error (#{exception.class}): #{exception.message}")
|
|
143
|
+
BugBunny.configuration.logger.error(exception.backtrace.join("\n"))
|
|
144
|
+
|
|
145
|
+
{ status: 500, body: { error: exception.message, type: exception.class.name } }
|
|
61
146
|
end
|
|
62
147
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
148
|
+
# Ejecuta la cadena de filtros before_action.
|
|
149
|
+
def run_before_actions(action_name)
|
|
150
|
+
chain = (self.class.before_actions[:_all_actions] || []) +
|
|
151
|
+
(self.class.before_actions[action_name] || [])
|
|
152
|
+
|
|
153
|
+
chain.uniq.each do |method_name|
|
|
154
|
+
send(method_name)
|
|
155
|
+
# Si un filtro llamó a 'render', detenemos la cadena (halt)
|
|
156
|
+
return false if rendered_response
|
|
157
|
+
end
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Construye la respuesta RPC normalizada.
|
|
162
|
+
#
|
|
163
|
+
# @param status [Symbol, Integer] Código HTTP (ej: :ok, 200, :not_found).
|
|
164
|
+
# @param json [Object] Objeto a serializar en el body.
|
|
66
165
|
def render(status:, json: nil)
|
|
67
166
|
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[status] || 200
|
|
68
167
|
@rendered_response = { status: code, body: json }
|
|
69
168
|
end
|
|
70
169
|
|
|
71
|
-
#
|
|
170
|
+
# Normaliza y fusiona parámetros de múltiples fuentes.
|
|
72
171
|
# Prioridad: Body > ID Ruta > Query Params.
|
|
73
|
-
#
|
|
74
|
-
# @param body [Hash, String] Payload.
|
|
75
172
|
def prepare_params(body)
|
|
76
|
-
self.params
|
|
77
|
-
|
|
78
|
-
# 1. Query Params (de la URL ?active=true)
|
|
79
|
-
if headers[:query_params].present?
|
|
80
|
-
params.merge!(headers[:query_params])
|
|
81
|
-
end
|
|
173
|
+
self.params = {}.with_indifferent_access
|
|
82
174
|
|
|
83
|
-
|
|
175
|
+
params.merge!(headers[:query_params]) if headers[:query_params].present?
|
|
84
176
|
params[:id] = headers[:id] if headers[:id].present?
|
|
85
177
|
|
|
86
|
-
# 3. Payload Body (JSON)
|
|
87
178
|
if body.is_a?(Hash)
|
|
88
179
|
params.merge!(body)
|
|
89
|
-
elsif body.is_a?(String) && headers[:content_type]
|
|
180
|
+
elsif body.is_a?(String) && headers[:content_type].to_s.include?('json')
|
|
90
181
|
parsed = JSON.parse(body) rescue nil
|
|
91
182
|
params.merge!(parsed) if parsed
|
|
92
183
|
else
|
|
93
184
|
self.raw_string = body
|
|
94
185
|
end
|
|
95
186
|
end
|
|
96
|
-
|
|
97
|
-
# Ejecuta callbacks. Retorna false si hubo `render` (halt).
|
|
98
|
-
# @api private
|
|
99
|
-
def run_callbacks
|
|
100
|
-
current = headers[:action].to_sym
|
|
101
|
-
chain = self.class.before_actions[:_all_actions] + self.class.before_actions[current]
|
|
102
|
-
chain.each do |method|
|
|
103
|
-
send(method)
|
|
104
|
-
return false if @rendered_response
|
|
105
|
-
end
|
|
106
|
-
true
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def self.rescue_from(e)
|
|
110
|
-
BugBunny.configuration.logger.error("Controller Error: #{e.message}")
|
|
111
|
-
{ status: 500, error: e.message }
|
|
112
|
-
end
|
|
113
187
|
end
|
|
114
188
|
end
|
data/lib/bug_bunny/producer.rb
CHANGED
|
@@ -37,7 +37,14 @@ module BugBunny
|
|
|
37
37
|
payload = serialize_message(request.body)
|
|
38
38
|
opts = request.amqp_options
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
# LOG ESTRUCTURADO Y LEGIBLE
|
|
41
|
+
# Muestra claramente: Verbo, Recurso, Exchange (y su tipo) y la Routing Key usada.
|
|
42
|
+
verb = request.method.to_s.upcase
|
|
43
|
+
target = request.path
|
|
44
|
+
ex_info = "'#{request.exchange}' (Type: #{request.exchange_type})"
|
|
45
|
+
rk = request.final_routing_key
|
|
46
|
+
|
|
47
|
+
BugBunny.configuration.logger.info("[BugBunny] [#{verb}] '/#{target}' | Exchange: #{ex_info} | Routing Key: '#{rk}'")
|
|
41
48
|
|
|
42
49
|
x.publish(payload, opts.merge(routing_key: request.final_routing_key))
|
|
43
50
|
end
|
|
@@ -76,7 +83,8 @@ module BugBunny
|
|
|
76
83
|
response_payload = future.value(wait_timeout)
|
|
77
84
|
|
|
78
85
|
if response_payload.nil?
|
|
79
|
-
|
|
86
|
+
# CORRECCIÓN: Usamos request.path y request.method en lugar de request.action
|
|
87
|
+
raise BugBunny::RequestTimeout, "Timeout waiting for RPC: #{request.path} [#{request.method}]"
|
|
80
88
|
end
|
|
81
89
|
|
|
82
90
|
parse_response(response_payload)
|