lepus 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67f07eb800872caf73d2f74330322cfb4682f740cf60734405f399f815368a9f
4
- data.tar.gz: 96257edb75021e9154cef73212e26f450f508ad7aa0fb5659756efebb8b3cb5a
3
+ metadata.gz: a9eb1d12b4ce05f9c4e9070d7083b68417b7a41b7cdb2b1a99cad2931c9879fe
4
+ data.tar.gz: 1a6eafc5a47b48a228279e5ede1a06acfea053b04cf686e8984719633d60e66b
5
5
  SHA512:
6
- metadata.gz: eab85b9d115eade80732103040ca2e0db25e8146e0dc957005e469ec60776f0a0693a59bd5359bc618a1bb69fafd7b8b0ac19502029355703629f1d5a7040e89
7
- data.tar.gz: 652b48322bb2372a261c497da6e60d4ea218f90d1ea2ae41da32024da74f6c5d4d288a4249af8279825aba6bf20b385748692190c2db871a63ab35e8e4338d78
6
+ metadata.gz: 40432e83f566507da124995e9a60130e10dbf54cc499ad8d3a6598369c0b8d3d9b78ab28be9fa6ca82812951092e28baead30d15d8b83feb6eeed9c6a30ebe32
7
+ data.tar.gz: '08948dbc7e9a927e2d26787a81c1106c1afb392aed5373248c54a02909a96d8d21b7e9f8c42a90e3ac4bc6179bfe1e5cc0c77c483be7d4808293bc6c51259213'
@@ -9,7 +9,7 @@ on:
9
9
  jobs:
10
10
  rubocop:
11
11
  name: "Rubocop"
12
- runs-on: ubuntu-latest
12
+ runs-on: ubuntu-24.04
13
13
  steps:
14
14
  - uses: actions/checkout@v4
15
15
  - uses: ruby/setup-ruby@v1
@@ -8,7 +8,7 @@ on:
8
8
 
9
9
  jobs:
10
10
  ruby-2-7:
11
- runs-on: ubuntu-latest
11
+ runs-on: ubuntu-24.04
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
@@ -32,7 +32,7 @@ jobs:
32
32
  run: bundle exec rspec
33
33
 
34
34
  ruby-3-0:
35
- runs-on: ubuntu-latest
35
+ runs-on: ubuntu-24.04
36
36
  strategy:
37
37
  fail-fast: false
38
38
  matrix:
data/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.2.2 - 2026-10-30
8
+ * Added: `lepus start --except A,B` (and `Supervisor.new(except:)`) runs every consumer but the ones listed
9
+
10
+ ## 0.2.1 - 2026-10-30
11
+ * Added: `process_delivery.lepus` instrumentation around every consumed message, with consumer, exchange, routing key, redelivered flag and result
12
+ * Added: `consumer_error.lepus` instrumentation when `#perform` raises, before the message is rejected
13
+ * Added: `Lepus::LogSubscriber` logs processed deliveries (info) and consumer errors (error)
14
+ * Fixed: `Lepus.instrument` without a block no longer raises when ActiveSupport is not loaded
15
+
7
16
  ## 0.2.0 - 2026-10-28
8
17
  * Added: named producer connections via `config.connection(name, url:, **bunny_options)`; `rabbitmq_url` remains the `:default` connection
9
18
  * Added: `connection:` option on `Producer.configure`, `Producer.publish` and `Lepus::Publisher.new`, with one producer connection pool per name
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lepus (0.2.0)
4
+ lepus (0.2.2)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
data/README.md CHANGED
@@ -567,6 +567,21 @@ bundle exec lepus start MyConsumer1 MyConsumer2 --debug
567
567
 
568
568
  Each consumer will run in a separate process, and a supervisor will monitor them. If a consumer crashes, the supervisor will restart it.
569
569
 
570
+ To run every consumer except some, for example to move one to its own deployment, use `--except` with a comma-separated list:
571
+
572
+ ```bash
573
+ bundle exec lepus start --except MyConsumer1,MyConsumer2
574
+ ```
575
+
576
+ ### Instrumentation and logging
577
+
578
+ Every consumed message emits a `process_delivery.lepus` event (`consumer`, `exchange`, `routing_key`, `redelivered`, `result`), and an exception raised by `#perform` emits `consumer_error.lepus` (with `error`) before the message is rejected. In Rails, `Lepus::LogSubscriber` logs both to `Lepus.logger`:
579
+
580
+ ```
581
+ Lepus-0.2.1 Processed OrdersConsumer (12.3ms) exchange: "orders", routing_key: "orders.created", redelivered: false, result: :ack
582
+ Lepus-0.2.1 Error in OrdersConsumer (0.0ms) exchange: "orders", routing_key: "orders.created", redelivered: false, error: "RuntimeError boom"
583
+ ```
584
+
570
585
  ### Puma Plugin
571
586
 
572
587
  We provide a Puma plugin if you want to run the Lepus's supervisor together with Puma and have Puma monitor and manage it. You just need to add
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.2.0)
4
+ lepus (0.2.2)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.2.0)
4
+ lepus (0.2.2)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.2.0)
4
+ lepus (0.2.2)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lepus (0.2.0)
4
+ lepus (0.2.2)
5
5
  base64
6
6
  bunny
7
7
  concurrent-ruby
data/lib/lepus/cli.rb CHANGED
@@ -10,6 +10,7 @@ module Lepus
10
10
  method_option :logfile, type: :string, default: nil
11
11
  method_option :pidfile, type: :string, default: nil
12
12
  method_option :require_file, type: :string, aliases: "-r", default: nil
13
+ method_option :except, type: :array, default: nil, desc: "Consumers to leave out"
13
14
 
14
15
  desc "start FirstConsumer SecondConsumer ... NthConsumer", "Run Consumer"
15
16
  default_command :start
@@ -17,9 +18,10 @@ module Lepus
17
18
  def start(*consumers)
18
19
  opts = (@options || {}).transform_keys(&:to_sym)
19
20
 
20
- if (list = consumers.flat_map { |c| c.split(",") }.map(&:strip).uniq.sort).any?
21
+ if (list = consumer_names(consumers)).any?
21
22
  opts[:consumers] = list
22
23
  end
24
+ opts[:except] = consumer_names(opts[:except]) if opts[:except]
23
25
 
24
26
  if (logfile = opts.delete(:logfile))
25
27
  Lepus.logger = Logger.new(logfile)
@@ -78,5 +80,11 @@ module Lepus
78
80
  MSG
79
81
  end
80
82
  end
83
+
84
+ private
85
+
86
+ def consumer_names(names)
87
+ names.flat_map { |name| name.split(",") }.map(&:strip).reject(&:empty?).uniq.sort
88
+ end
81
89
  end
82
90
  end
@@ -98,26 +98,9 @@ module Lepus
98
98
  message = Message.coerce(delivery_info, metadata, payload)
99
99
  message.consumer_class = self.class
100
100
 
101
- combined_chain = MiddlewareChain.combine(
102
- Lepus.config.consumer_middleware_chain,
103
- self.class.middleware_chain
104
- )
105
-
106
- combined_chain.execute(message) do |msg|
107
- perform(msg).tap do |result|
108
- verify_result(result)
109
- end
101
+ Lepus.instrument(:process_delivery, **delivery_attributes(delivery_info)) do |event|
102
+ event[:result] = deliver(message, delivery_info)
110
103
  end
111
- rescue Lepus::InvalidConsumerReturnError
112
- raise
113
- rescue Exception # rubocop:disable Lint/RescueException
114
- on_delivery_error
115
- # In testing mode, re-raise exceptions if consumer_raise_errors? is enabled
116
- if defined?(Lepus::Testing) && Lepus::Testing.consumer_raise_errors?
117
- raise
118
- end
119
-
120
- reject!
121
104
  end
122
105
 
123
106
  # Returns whether the last delivery resulted in an error.
@@ -194,6 +177,39 @@ module Lepus
194
177
  def on_delivery_error
195
178
  end
196
179
 
180
+ def deliver(message, delivery_info)
181
+ combined_chain = MiddlewareChain.combine(
182
+ Lepus.config.consumer_middleware_chain,
183
+ self.class.middleware_chain
184
+ )
185
+
186
+ combined_chain.execute(message) do |msg|
187
+ perform(msg).tap do |result|
188
+ verify_result(result)
189
+ end
190
+ end
191
+ rescue Lepus::InvalidConsumerReturnError
192
+ raise
193
+ rescue Exception => error # rubocop:disable Lint/RescueException
194
+ Lepus.instrument(:consumer_error, error: error, **delivery_attributes(delivery_info))
195
+ on_delivery_error
196
+ # In testing mode, re-raise exceptions if consumer_raise_errors? is enabled
197
+ if defined?(Lepus::Testing) && Lepus::Testing.consumer_raise_errors?
198
+ raise
199
+ end
200
+
201
+ reject!
202
+ end
203
+
204
+ def delivery_attributes(delivery_info)
205
+ {
206
+ consumer: self.class.name,
207
+ exchange: delivery_info&.exchange,
208
+ routing_key: delivery_info&.routing_key,
209
+ redelivered: delivery_info&.redelivered
210
+ }
211
+ end
212
+
197
213
  def verify_result(result)
198
214
  return if %i[ack reject requeue nack].include?(result)
199
215
 
@@ -100,6 +100,14 @@ class Lepus::LogSubscriber < ActiveSupport::LogSubscriber
100
100
  end
101
101
  end
102
102
 
103
+ def process_delivery(event)
104
+ info formatted_event(event, action: "Processed #{event.payload[:consumer]}", **event.payload.slice(:exchange, :routing_key, :redelivered, :result))
105
+ end
106
+
107
+ def consumer_error(event)
108
+ error formatted_event(event, action: "Error in #{event.payload[:consumer]}", **event.payload.slice(:exchange, :routing_key, :redelivered), error: formatted_error(event.payload[:error]))
109
+ end
110
+
103
111
  private
104
112
 
105
113
  def formatted_event(event, action:, **attributes)
@@ -21,11 +21,13 @@ module Lepus
21
21
  # @param pidfile [String] The path to the pidfile where the supervisor's PID will be stored. Default is "tmp/pids/lepus.pid".
22
22
  # @param shutdown_timeout [Integer] The timeout in seconds to wait for child processes to terminate gracefully before forcing termination. Default is 5 seconds.
23
23
  # @param consumers [Array<String, Class>] An optional list of consumer class names (as strings or constants) to be run by this supervisor. If not provided, all discovered consumer classes will be used.
24
+ # @param except [Array<String, Class>] An optional list of consumer class names (as strings or constants) to leave out.
24
25
  def initialize(require_file: nil, pidfile: "tmp/pids/lepus.pid", shutdown_timeout: 5, **kwargs)
25
26
  @pidfile_path = pidfile
26
27
  @require_file = require_file
27
28
  @shutdown_timeout = shutdown_timeout.to_i
28
29
  @consumer_class_names = Array(kwargs[:consumers]).map(&:to_s) if kwargs.key?(:consumers)
30
+ @excluded_consumer_names = Array(kwargs[:except]).map(&:to_s)
29
31
 
30
32
  @forks = {}
31
33
  @pipes = {}
@@ -79,7 +81,7 @@ module Lepus
79
81
  @consumer_class_names.map { |name| Lepus::Primitive::String.new(name).constantize }
80
82
  else
81
83
  Lepus::Consumer.descendants
82
- end.reject(&:abstract_class?)
84
+ end.reject { |klass| klass.abstract_class? || @excluded_consumer_names.include?(klass.name) }
83
85
  end
84
86
 
85
87
  def boot
data/lib/lepus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lepus
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
data/lib/lepus.rb CHANGED
@@ -77,7 +77,7 @@ module Lepus
77
77
  def instrument(channel, **options, &block)
78
78
  if defined?(ActiveSupport::Notifications)
79
79
  ActiveSupport::Notifications.instrument("#{channel}.lepus", **options, &block)
80
- else
80
+ elsif block
81
81
  yield(options.dup)
82
82
  end
83
83
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lepus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcos G. Zimmermann
8
8
  autorequire:
9
9
  bindir: exec
10
10
  cert_chain: []
11
- date: 2026-10-28 00:00:00.000000000 Z
11
+ date: 2026-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64