karafka 2.0.22 → 2.0.24

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +2 -0
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +39 -1
  6. data/Gemfile.lock +9 -9
  7. data/README.md +3 -3
  8. data/config/{errors.yml → locales/errors.yml} +1 -1
  9. data/config/locales/pro_errors.yml +18 -0
  10. data/docker-compose.yml +3 -0
  11. data/karafka.gemspec +2 -3
  12. data/lib/karafka/active_job/job_options_contract.rb +1 -1
  13. data/lib/karafka/admin.rb +84 -6
  14. data/lib/karafka/app.rb +15 -4
  15. data/lib/karafka/base_consumer.rb +43 -1
  16. data/lib/karafka/connection/client.rb +4 -4
  17. data/lib/karafka/connection/listener.rb +1 -1
  18. data/lib/karafka/contracts/config.rb +1 -1
  19. data/lib/karafka/contracts/consumer_group.rb +1 -1
  20. data/lib/karafka/contracts/server_cli_options.rb +1 -1
  21. data/lib/karafka/contracts/topic.rb +1 -1
  22. data/lib/karafka/instrumentation/logger_listener.rb +32 -0
  23. data/lib/karafka/instrumentation/notifications.rb +3 -0
  24. data/lib/karafka/messages/message.rb +14 -2
  25. data/lib/karafka/messages/parser.rb +14 -0
  26. data/lib/karafka/pro/active_job/job_options_contract.rb +1 -1
  27. data/lib/karafka/pro/encryption/cipher.rb +58 -0
  28. data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
  29. data/lib/karafka/pro/encryption/errors.rb +24 -0
  30. data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
  31. data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
  32. data/lib/karafka/pro/encryption/setup/config.rb +48 -0
  33. data/lib/karafka/pro/encryption.rb +47 -0
  34. data/lib/karafka/pro/loader.rb +23 -2
  35. data/lib/karafka/pro/processing/strategies/aj_dlq_mom.rb +2 -2
  36. data/lib/karafka/pro/processing/strategies/aj_lrj_mom_vp.rb +2 -2
  37. data/lib/karafka/pro/processing/strategies/aj_mom_vp.rb +1 -1
  38. data/lib/karafka/pro/processing/strategies/default.rb +5 -1
  39. data/lib/karafka/pro/processing/strategies/dlq.rb +4 -2
  40. data/lib/karafka/pro/processing/strategies/dlq_lrj.rb +3 -1
  41. data/lib/karafka/pro/processing/strategies/dlq_lrj_mom.rb +1 -1
  42. data/lib/karafka/pro/processing/strategies/dlq_mom.rb +2 -2
  43. data/lib/karafka/pro/processing/strategies/lrj.rb +4 -2
  44. data/lib/karafka/pro/processing/strategies/lrj_mom.rb +2 -2
  45. data/lib/karafka/pro/processing/strategies/mom.rb +1 -1
  46. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +2 -2
  47. data/lib/karafka/pro/routing/features/long_running_job/contract.rb +2 -2
  48. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +2 -2
  49. data/lib/karafka/processing/coordinator.rb +15 -0
  50. data/lib/karafka/processing/executor.rb +1 -1
  51. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +2 -2
  52. data/lib/karafka/processing/strategies/default.rb +7 -1
  53. data/lib/karafka/processing/strategies/dlq.rb +4 -2
  54. data/lib/karafka/processing/strategies/dlq_mom.rb +2 -2
  55. data/lib/karafka/processing/strategies/mom.rb +1 -1
  56. data/lib/karafka/processing/worker.rb +1 -1
  57. data/lib/karafka/railtie.rb +3 -0
  58. data/lib/karafka/routing/builder.rb +1 -1
  59. data/lib/karafka/routing/consumer_group.rb +3 -3
  60. data/lib/karafka/routing/features/active_job/contract.rb +1 -1
  61. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +1 -1
  62. data/lib/karafka/routing/features/manual_offset_management/contract.rb +1 -1
  63. data/lib/karafka/server.rb +14 -14
  64. data/lib/karafka/setup/config.rb +15 -2
  65. data/lib/karafka/status.rb +27 -9
  66. data/lib/karafka/templates/karafka.rb.erb +1 -2
  67. data/lib/karafka/time_trackers/pause.rb +3 -1
  68. data/lib/karafka/version.rb +1 -1
  69. data.tar.gz.sig +0 -0
  70. metadata +17 -23
  71. metadata.gz.sig +2 -4
  72. data/lib/karafka/instrumentation.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a096a44a1a2988ff394215b8d63f6f2e33b2a3e5100f68f75fcf33eeda4b490
4
- data.tar.gz: 2af0875b550f37ef9ea47b2a3011a3ed3e535adc3f6632ab10510d91bc69590c
3
+ metadata.gz: 25eb837923f78f1bf35402bf24469228d8f5af12090b2d406718a0077ce42598
4
+ data.tar.gz: 23520ecab430080061046e683c0dcb47316d1216d1ad9843894caefff76e24cc
5
5
  SHA512:
6
- metadata.gz: a81cf3305482e0e925f3a5aa48d978585766826e0990081d74f5f56bc535ae0a8a6be3a4ec986e6a7702d70d0d207ca9acc71b7aeb3909557b44518413ff9bc6
7
- data.tar.gz: 6c66140b258135367a120a5dac4eb8a08f4dc94e24fb3a714b32ec6ad909bc9f85bf5e160aefe5f5d307c9d554c6ecab61cbdebf315ce6953476303e9783122c
6
+ metadata.gz: 3e84c4c4dedd7a160ebcbd6cff53e0dfcf601a31eb19469d1a86eabb3f33507f3b1871c8f4489b743651118ed47e52925f12dba4fc67e462bd8be04d71dfa3bb
7
+ data.tar.gz: 8b9754f5566c5ef213c4803cfba2a4263553a5112e23fd12e42a963fd493f8f20c1409b12303628519ba7ef5bcf07aa09fabc6dd4ffc2a2114a6bad8f94cbc72
checksums.yaml.gz.sig CHANGED
Binary file
@@ -46,6 +46,7 @@ jobs:
46
46
  run: \curl -sSL https://api.coditsu.io/run/ci | bash
47
47
 
48
48
  specs:
49
+ timeout-minutes: 30
49
50
  runs-on: ubuntu-latest
50
51
  needs: diffend
51
52
  strategy:
@@ -84,6 +85,7 @@ jobs:
84
85
  run: bin/rspecs
85
86
 
86
87
  integrations:
88
+ timeout-minutes: 30
87
89
  runs-on: ubuntu-latest
88
90
  needs: diffend
89
91
  strategy:
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 3.1.3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.0.24 (2022-12-19)
4
+ - **[Feature]** Provide out of the box encryption support for Pro.
5
+ - [Improvement] Add instrumentation upon `#pause`.
6
+ - [Improvement] Add instrumentation upon retries.
7
+ - [Improvement] Assign `#id` to consumers similar to other entities for ease of debugging.
8
+ - [Improvement] Add retries and pausing to the default `LoggerListener`.
9
+ - [Improvement] Introduce a new final `terminated` state that will kick in prior to exit but after all the instrumentation and other things are done.
10
+ - [Improvement] Ensure that state transitions are thread-safe and ensure state transitions can occur in one direction.
11
+ - [Improvement] Optimize status methods proxying to `Karafka::App`.
12
+ - [Improvement] Allow for easier state usage by introducing explicit `#to_s` for reporting.
13
+ - [Improvement] Change auto-generated id from `SecureRandom#uuid` to `SecureRandom#hex(6)`
14
+ - [Improvement] Emit statistic every 5 seconds by default.
15
+ - [Improvement] Introduce general messages parser that can be swapped when needed.
16
+ - [Fix] Do not trigger code reloading when `consumer_persistence` is enabled.
17
+ - [Fix] Shutdown producer after all the consumer components are down and the status is stopped. This will ensure, that any instrumentation related Kafka messaging can still operate.
18
+
19
+ ### Upgrade notes
20
+
21
+ If you want to disable `librdkafka` statistics because you do not use them at all, update the `kafka` `statistics.interval.ms` setting and set it to `0`:
22
+
23
+ ```ruby
24
+ class KarafkaApp < Karafka::App
25
+ setup do |config|
26
+ # Other settings...
27
+ config.kafka = {
28
+ 'statistics.interval.ms': 0
29
+ }
30
+ end
31
+ end
32
+ ```
33
+
34
+ ## 2.0.23 (2022-12-07)
35
+ - [Maintenance] Align with `waterdrop` and `karafka-core`
36
+ - [Improvement] Provide `Admin#read_topic` API to get topic data without subscribing.
37
+ - [Improvement] Upon an end user `#pause`, do not commit the offset in automatic offset management mode. This will prevent from a scenario where pause is needed but during it a rebalance occurs and a different assigned process starts not from the pause location but from the automatic offset that may be different. This still allows for using the `#mark_as_consumed`.
38
+ - [Fix] Fix a scenario where manual `#pause` would be overwritten by a resume initiated by the strategy.
39
+ - [Fix] Fix a scenario where manual `#pause` in LRJ would cause infinite pause.
40
+
3
41
  ## 2.0.22 (2022-12-02)
4
42
  - [Improvement] Load Pro components upon Karafka require so they can be altered prior to setup.
5
43
  - [Improvement] Do not run LRJ jobs that were added to the jobs queue but were revoked meanwhile.
@@ -418,7 +456,7 @@ There are several things in the plan already for 2.1 and beyond, including a web
418
456
  - Small integration specs refactoring + specs for pausing scenarios
419
457
 
420
458
  ## 2.0.0-alpha6 (2022-04-17)
421
- - Fix a bug, where upon missing boot file and Rails, railtie would fail with a generic exception (#818)
459
+ - Fix a bug, where upon missing boot file and Rails, railtie would fail with a generic exception (#818)
422
460
  - Fix an issue with parallel pristine specs colliding with each other during `bundle install` (#820)
423
461
  - Replace `consumer.consume` with `consumer.consumed` event to match the behaviour
424
462
  - Make sure, that offset committing happens before the `consumer.consumed` event is propagated
data/Gemfile.lock CHANGED
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.0.22)
5
- karafka-core (>= 2.0.4, < 3.0.0)
6
- rdkafka (>= 0.12)
4
+ karafka (2.0.24)
5
+ karafka-core (>= 2.0.7, < 3.0.0)
7
6
  thor (>= 0.20)
8
- waterdrop (>= 2.4.1, < 3.0.0)
7
+ waterdrop (>= 2.4.7, < 3.0.0)
9
8
  zeitwerk (~> 2.3)
10
9
 
11
10
  GEM
@@ -30,8 +29,9 @@ GEM
30
29
  activesupport (>= 5.0)
31
30
  i18n (1.12.0)
32
31
  concurrent-ruby (~> 1.0)
33
- karafka-core (2.0.4)
32
+ karafka-core (2.0.7)
34
33
  concurrent-ruby (>= 1.1)
34
+ rdkafka (>= 0.12)
35
35
  mini_portile2 (2.8.0)
36
36
  minitest (5.16.3)
37
37
  rake (13.0.6)
@@ -48,7 +48,7 @@ GEM
48
48
  rspec-expectations (3.12.0)
49
49
  diff-lcs (>= 1.2.0, < 2.0)
50
50
  rspec-support (~> 3.12.0)
51
- rspec-mocks (3.12.0)
51
+ rspec-mocks (3.12.1)
52
52
  diff-lcs (>= 1.2.0, < 2.0)
53
53
  rspec-support (~> 3.12.0)
54
54
  rspec-support (3.12.0)
@@ -61,13 +61,13 @@ GEM
61
61
  thor (1.2.1)
62
62
  tzinfo (2.0.5)
63
63
  concurrent-ruby (~> 1.0)
64
- waterdrop (2.4.2)
65
- karafka-core (>= 2.0.2, < 3.0.0)
66
- rdkafka (>= 0.10)
64
+ waterdrop (2.4.7)
65
+ karafka-core (>= 2.0.7, < 3.0.0)
67
66
  zeitwerk (~> 2.3)
68
67
  zeitwerk (2.6.6)
69
68
 
70
69
  PLATFORMS
70
+ x86_64-darwin-21
71
71
  x86_64-linux
72
72
 
73
73
  DEPENDENCIES
data/README.md CHANGED
@@ -72,10 +72,10 @@ Karafka.producer.produce_sync(topic: 'example', payload: { 'ping' => 'pong' }.to
72
72
  ```bash
73
73
  bundle exec karafka server
74
74
 
75
- [7616dc24-505a-417f-b87b-6bf8fc2d98c5] Polled 1 message in 1000ms
76
- [dcf3a8d8-0bd9-433a-8f63-b70a0cdb0732] Consume job for ExampleConsumer on example started
75
+ [86d47f0b92f7] Polled 1 message in 1000ms
76
+ [3732873c8a74] Consume job for ExampleConsumer on example started
77
77
  {"ping"=>"pong"}
78
- [dcf3a8d8-0bd9-433a-8f63-b70a0cdb0732] Consume job for ExampleConsumer on example finished in 0ms
78
+ [3732873c8a74] Consume job for ExampleConsumer on example finished in 0ms
79
79
  ```
80
80
 
81
81
  ## Want to Upgrade? LGPL is not for you? Want to help?
@@ -63,7 +63,7 @@ en:
63
63
 
64
64
  pro_topic:
65
65
  virtual_partitions.partitioner_respond_to_call: needs to be defined and needs to respond to `#call`
66
- virtual_partitions.max_partitions_format: needs to be equl or more than 1
66
+ virtual_partitions.max_partitions_format: needs to be equal or more than 1
67
67
  manual_offset_management_not_with_virtual_partitions: cannot be used together with Virtual Partitions
68
68
  long_running_job.active_format: needs to be either true or false
69
69
  dead_letter_queue_not_with_virtual_partitions: cannot be used together with Virtual Partitions
@@ -0,0 +1,18 @@
1
+ en:
2
+ validations:
3
+ topic:
4
+ virtual_partitions.partitioner_respond_to_call: needs to be defined and needs to respond to `#call`
5
+ virtual_partitions.max_partitions_format: needs to be equal or more than 1
6
+ manual_offset_management_not_with_virtual_partitions: cannot be used together with Virtual Partitions
7
+ long_running_job.active_format: needs to be either true or false
8
+ dead_letter_queue_not_with_virtual_partitions: cannot be used together with Virtual Partitions
9
+
10
+ config:
11
+ encryption.active_format: 'needs to be either true or false'
12
+ encryption.public_key_invalid: 'is not a valid public RSA key'
13
+ encryption.public_key_needs_to_be_public: 'is a private RSA key not a public one'
14
+ encryption.private_keys_format: 'needs to be a hash of version and private key value'
15
+ encryption.private_keys_need_to_be_private: 'all keys need to be private'
16
+ encryption.version_format: must be a non-empty string
17
+ encryption.public_key_format: 'is not a valid public RSA key'
18
+ encryption.private_keys_invalid: 'contains an invalid private RSA key string'
data/docker-compose.yml CHANGED
@@ -3,8 +3,10 @@ services:
3
3
  zookeeper:
4
4
  container_name: karafka_20_zookeeper
5
5
  image: wurstmeister/zookeeper
6
+ restart: on-failure
6
7
  ports:
7
8
  - '2181:2181'
9
+
8
10
  kafka:
9
11
  container_name: karafka_20_kafka
10
12
  image: wurstmeister/kafka
@@ -22,3 +24,4 @@ services:
22
24
  benchmarks_00_10:10:1"
23
25
  volumes:
24
26
  - /var/run/docker.sock:/var/run/docker.sock
27
+ restart: on-failure
data/karafka.gemspec CHANGED
@@ -21,10 +21,9 @@ Gem::Specification.new do |spec|
21
21
  without having to focus on things that are not your business domain.
22
22
  DESC
23
23
 
24
- spec.add_dependency 'karafka-core', '>= 2.0.4', '< 3.0.0'
25
- spec.add_dependency 'rdkafka', '>= 0.12'
24
+ spec.add_dependency 'karafka-core', '>= 2.0.7', '< 3.0.0'
26
25
  spec.add_dependency 'thor', '>= 0.20'
27
- spec.add_dependency 'waterdrop', '>= 2.4.1', '< 3.0.0'
26
+ spec.add_dependency 'waterdrop', '>= 2.4.7', '< 3.0.0'
28
27
  spec.add_dependency 'zeitwerk', '~> 2.3'
29
28
 
30
29
  spec.required_ruby_version = '>= 2.7.0'
@@ -10,7 +10,7 @@ module Karafka
10
10
  configure do |config|
11
11
  config.error_messages = YAML.safe_load(
12
12
  File.read(
13
- File.join(Karafka.gem_root, 'config', 'errors.yml')
13
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
14
14
  )
15
15
  ).fetch('en').fetch('validations').fetch('job_options')
16
16
  end
data/lib/karafka/admin.rb CHANGED
@@ -9,7 +9,70 @@ module Karafka
9
9
  # @note It always uses the primary defined cluster and does not support multi-cluster work.
10
10
  # If you need this, just replace the cluster info for the time you use this
11
11
  module Admin
12
+ # A fake admin topic representation that we use for messages fetched using this API
13
+ # We cannot use the topics directly because we may want to request data from topics that we
14
+ # do not have in the routing
15
+ Topic = Struct.new(:name, :deserializer)
16
+
17
+ # Defaults for config
18
+ CONFIG_DEFAULTS = {
19
+ 'group.id': 'karafka_admin',
20
+ # We want to know when there is no more data not to end up with an endless loop
21
+ 'enable.partition.eof': true,
22
+ 'statistics.interval.ms': 0
23
+ }.freeze
24
+
25
+ private_constant :Topic, :CONFIG_DEFAULTS
26
+
12
27
  class << self
28
+ # Allows us to read messages from the topic
29
+ #
30
+ # @param name [String, Symbol] topic name
31
+ # @param partition [Integer] partition
32
+ # @param count [Integer] how many messages we want to get at most
33
+ # @param offset [Integer] offset from which we should start. If -1 is provided (default) we
34
+ # will start from the latest offset
35
+ #
36
+ # @return [Array<Karafka::Messages::Message>] array with messages
37
+ def read_topic(name, partition, count, offset = -1)
38
+ messages = []
39
+ tpl = Rdkafka::Consumer::TopicPartitionList.new
40
+
41
+ with_consumer do |consumer|
42
+ if offset.negative?
43
+ offsets = consumer.query_watermark_offsets(name, partition)
44
+ offset = offsets.last - count
45
+ end
46
+
47
+ offset = offset.negative? ? 0 : offset
48
+
49
+ tpl.add_topic_and_partitions_with_offsets(name, partition => offset)
50
+ consumer.assign(tpl)
51
+
52
+ # We should poll as long as we don't have all the messages that we need or as long as
53
+ # we do not read all the messages from the topic
54
+ loop do
55
+ break if messages.size >= count
56
+
57
+ message = consumer.poll(200)
58
+ messages << message if message
59
+ rescue Rdkafka::RdkafkaError => e
60
+ # End of partition
61
+ break if e.code == :partition_eof
62
+
63
+ raise e
64
+ end
65
+ end
66
+
67
+ messages.map do |message|
68
+ Messages::Builders::Message.call(
69
+ message,
70
+ Topic.new(name, Karafka::App.config.deserializer),
71
+ Time.now
72
+ )
73
+ end
74
+ end
75
+
13
76
  # Creates Kafka topic with given settings
14
77
  #
15
78
  # @param name [String] topic name
@@ -52,15 +115,30 @@ module Karafka
52
115
 
53
116
  # Creates admin instance and yields it. After usage it closes the admin instance
54
117
  def with_admin
55
- # Admin needs a producer config
56
- config = Karafka::Setup::AttributesMap.producer(Karafka::App.config.kafka.dup)
57
-
58
- admin = ::Rdkafka::Config.new(config).admin
59
- result = yield(admin)
60
- result
118
+ admin = config(:producer).admin
119
+ yield(admin)
61
120
  ensure
62
121
  admin&.close
63
122
  end
123
+
124
+ # Creates consumer instance and yields it. After usage it closes the consumer instance
125
+ def with_consumer
126
+ consumer = config(:consumer).consumer
127
+ yield(consumer)
128
+ ensure
129
+ consumer&.close
130
+ end
131
+
132
+ # @param type [Symbol] type of config we want
133
+ # @return [::Rdkafka::Config] rdkafka config
134
+ def config(type)
135
+ config_hash = Karafka::Setup::AttributesMap.public_send(
136
+ type,
137
+ Karafka::App.config.kafka.dup.merge(CONFIG_DEFAULTS)
138
+ )
139
+
140
+ ::Rdkafka::Config.new(config_hash)
141
+ end
64
142
  end
65
143
  end
66
144
  end
data/lib/karafka/app.rb CHANGED
@@ -35,10 +35,21 @@ module Karafka
35
35
  # Just a nicer name for the consumer groups
36
36
  alias routes consumer_groups
37
37
 
38
- Status.instance_methods(false).each do |delegated|
39
- define_method(delegated) do
40
- App.config.internal.status.send(delegated)
41
- end
38
+ # Allow for easier status management via `Karafka::App` by aliasing status methods here
39
+ Status::STATES.each do |state, transition|
40
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
41
+ def #{state}
42
+ App.config.internal.status.#{state}
43
+ end
44
+
45
+ def #{state}?
46
+ App.config.internal.status.#{state}?
47
+ end
48
+
49
+ def #{transition}
50
+ App.config.internal.status.#{transition}
51
+ end
52
+ RUBY
42
53
  end
43
54
 
44
55
  # Methods that should be delegated to Karafka module
@@ -4,6 +4,8 @@
4
4
  module Karafka
5
5
  # Base consumer from which all Karafka consumers should inherit
6
6
  class BaseConsumer
7
+ # @return [String] id of the current consumer
8
+ attr_reader :id
7
9
  # @return [Karafka::Routing::Topic] topic to which a given consumer is subscribed
8
10
  attr_accessor :topic
9
11
  # @return [Karafka::Messages::Messages] current messages batch
@@ -15,6 +17,11 @@ module Karafka
15
17
  # @return [Waterdrop::Producer] producer instance
16
18
  attr_accessor :producer
17
19
 
20
+ # Creates new consumer and assigns it an id
21
+ def initialize
22
+ @id = SecureRandom.hex(6)
23
+ end
24
+
18
25
  # Can be used to run preparation code prior to the job being enqueued
19
26
  #
20
27
  # @private
@@ -190,7 +197,10 @@ module Karafka
190
197
  # @param offset [Integer] offset from which we want to restart the processing
191
198
  # @param timeout [Integer, nil] how long in milliseconds do we want to pause or nil to use the
192
199
  # default exponential pausing strategy defined for retries
193
- def pause(offset, timeout = nil)
200
+ # @param manual_pause [Boolean] Flag to differentiate between user pause and system/strategy
201
+ # based pause. While they both pause in exactly the same way, the strategy application
202
+ # may need to differentiate between them.
203
+ def pause(offset, timeout = nil, manual_pause = true)
194
204
  timeout ? coordinator.pause_tracker.pause(timeout) : coordinator.pause_tracker.pause
195
205
 
196
206
  client.pause(
@@ -198,6 +208,20 @@ module Karafka
198
208
  messages.metadata.partition,
199
209
  offset
200
210
  )
211
+
212
+ # Indicate, that user took a manual action of pausing
213
+ coordinator.manual_pause if manual_pause
214
+
215
+ Karafka.monitor.instrument(
216
+ 'consumer.consuming.pause',
217
+ caller: self,
218
+ manual: manual_pause,
219
+ topic: messages.metadata.topic,
220
+ partition: messages.metadata.partition,
221
+ offset: offset,
222
+ timeout: coordinator.pause_tracker.current_timeout,
223
+ attempt: coordinator.pause_tracker.attempt
224
+ )
201
225
  end
202
226
 
203
227
  # Resumes processing of the current topic partition
@@ -226,5 +250,23 @@ module Karafka
226
250
  def revoked?
227
251
  coordinator.revoked?
228
252
  end
253
+
254
+ # Pauses the processing from the last offset to retry on given message
255
+ # @private
256
+ def retry_after_pause
257
+ pause(coordinator.seek_offset, nil, false)
258
+
259
+ # Instrumentation needs to run **after** `#pause` invocation because we rely on the states
260
+ # set by `#pause`
261
+ Karafka.monitor.instrument(
262
+ 'consumer.consuming.retry',
263
+ caller: self,
264
+ topic: messages.metadata.topic,
265
+ partition: messages.metadata.partition,
266
+ offset: coordinator.seek_offset,
267
+ timeout: coordinator.pause_tracker.current_timeout,
268
+ attempt: coordinator.pause_tracker.attempt
269
+ )
270
+ end
229
271
  end
230
272
  end
@@ -308,8 +308,8 @@ module Karafka
308
308
  @closed = true
309
309
 
310
310
  # Remove callbacks runners that were registered
311
- ::Karafka::Instrumentation.statistics_callbacks.delete(@subscription_group.id)
312
- ::Karafka::Instrumentation.error_callbacks.delete(@subscription_group.id)
311
+ ::Karafka::Core::Instrumentation.statistics_callbacks.delete(@subscription_group.id)
312
+ ::Karafka::Core::Instrumentation.error_callbacks.delete(@subscription_group.id)
313
313
 
314
314
  @kafka.close
315
315
  @buffer.clear
@@ -397,7 +397,7 @@ module Karafka
397
397
  @name = consumer.name
398
398
 
399
399
  # Register statistics runner for this particular type of callbacks
400
- ::Karafka::Instrumentation.statistics_callbacks.add(
400
+ ::Karafka::Core::Instrumentation.statistics_callbacks.add(
401
401
  @subscription_group.id,
402
402
  Instrumentation::Callbacks::Statistics.new(
403
403
  @subscription_group.id,
@@ -408,7 +408,7 @@ module Karafka
408
408
  )
409
409
 
410
410
  # Register error tracking callback
411
- ::Karafka::Instrumentation.error_callbacks.add(
411
+ ::Karafka::Core::Instrumentation.error_callbacks.add(
412
412
  @subscription_group.id,
413
413
  Instrumentation::Callbacks::Error.new(
414
414
  @subscription_group.id,
@@ -21,7 +21,7 @@ module Karafka
21
21
  def initialize(consumer_group_coordinator, subscription_group, jobs_queue)
22
22
  proc_config = ::Karafka::App.config.internal.processing
23
23
 
24
- @id = SecureRandom.uuid
24
+ @id = SecureRandom.hex(6)
25
25
  @consumer_group_coordinator = consumer_group_coordinator
26
26
  @subscription_group = subscription_group
27
27
  @jobs_queue = jobs_queue
@@ -12,7 +12,7 @@ module Karafka
12
12
  configure do |config|
13
13
  config.error_messages = YAML.safe_load(
14
14
  File.read(
15
- File.join(Karafka.gem_root, 'config', 'errors.yml')
15
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
16
16
  )
17
17
  ).fetch('en').fetch('validations').fetch('config')
18
18
  end
@@ -7,7 +7,7 @@ module Karafka
7
7
  configure do |config|
8
8
  config.error_messages = YAML.safe_load(
9
9
  File.read(
10
- File.join(Karafka.gem_root, 'config', 'errors.yml')
10
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
11
11
  )
12
12
  ).fetch('en').fetch('validations').fetch('consumer_group')
13
13
  end
@@ -7,7 +7,7 @@ module Karafka
7
7
  configure do |config|
8
8
  config.error_messages = YAML.safe_load(
9
9
  File.read(
10
- File.join(Karafka.gem_root, 'config', 'errors.yml')
10
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
11
11
  )
12
12
  ).fetch('en').fetch('validations').fetch('server_cli_options')
13
13
  end
@@ -7,7 +7,7 @@ module Karafka
7
7
  configure do |config|
8
8
  config.error_messages = YAML.safe_load(
9
9
  File.read(
10
- File.join(Karafka.gem_root, 'config', 'errors.yml')
10
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
11
11
  )
12
12
  ).fetch('en').fetch('validations').fetch('topic')
13
13
  end
@@ -63,6 +63,38 @@ module Karafka
63
63
  info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} finished in #{time}ms"
64
64
  end
65
65
 
66
+ # Prints info about a pause occurrence. Irrelevant if user or system initiated.
67
+ #
68
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
69
+ def on_consumer_consuming_pause(event)
70
+ topic = event[:topic]
71
+ partition = event[:partition]
72
+ offset = event[:offset]
73
+ consumer = event[:caller]
74
+ timeout = event[:timeout]
75
+
76
+ info <<~MSG.tr("\n", ' ').strip!
77
+ [#{consumer.id}] Pausing partition #{partition} of topic #{topic}
78
+ on offset #{offset} for #{timeout} ms.
79
+ MSG
80
+ end
81
+
82
+ # Prints info about retry of processing after an error
83
+ #
84
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
85
+ def on_consumer_consuming_retry(event)
86
+ topic = event[:topic]
87
+ partition = event[:partition]
88
+ offset = event[:offset]
89
+ consumer = event[:caller]
90
+ timeout = event[:timeout]
91
+
92
+ info <<~MSG.tr("\n", ' ').strip!
93
+ [#{consumer.id}] Retrying of #{consumer.class} after #{timeout} ms
94
+ on partition #{partition} of topic #{topic} from offset #{offset}
95
+ MSG
96
+ end
97
+
66
98
  # Logs info about system signals that Karafka received and prints backtrace for threads in
67
99
  # case of ttin
68
100
  #
@@ -22,8 +22,11 @@ module Karafka
22
22
  app.quieting
23
23
  app.stopping
24
24
  app.stopped
25
+ app.terminated
25
26
 
26
27
  consumer.consumed
28
+ consumer.consuming.pause
29
+ consumer.consuming.retry
27
30
  consumer.revoked
28
31
  consumer.shutdown
29
32
 
@@ -9,7 +9,19 @@ module Karafka
9
9
  class Message
10
10
  extend Forwardable
11
11
 
12
- attr_reader :raw_payload, :metadata
12
+ class << self
13
+ # @return [Object] general parser
14
+ # @note We cache it here for performance reasons. It is 2.5x times faster than getting it
15
+ # via the config chain.
16
+ def parser
17
+ @parser ||= App.config.internal.messages.parser
18
+ end
19
+ end
20
+
21
+ attr_reader :metadata
22
+ # raw payload needs to be mutable as we want to have option to change it in the parser
23
+ # prior to the final deserialization
24
+ attr_accessor :raw_payload
13
25
 
14
26
  def_delegators :metadata, *Metadata.members
15
27
 
@@ -42,7 +54,7 @@ module Karafka
42
54
 
43
55
  # @return [Object] deserialized data
44
56
  def deserialize
45
- metadata.deserializer.call(self)
57
+ self.class.parser.call(self)
46
58
  end
47
59
  end
48
60
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Messages
5
+ # Default message parser. The only thing it does, is calling the deserializer
6
+ class Parser
7
+ # @param message [::Karafka::Messages::Message]
8
+ # @return [Object] deserialized payload
9
+ def call(message)
10
+ message.metadata.deserializer.call(message)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -20,7 +20,7 @@ module Karafka
20
20
  configure do |config|
21
21
  config.error_messages = YAML.safe_load(
22
22
  File.read(
23
- File.join(Karafka.gem_root, 'config', 'errors.yml')
23
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
24
24
  )
25
25
  ).fetch('en').fetch('validations').fetch('job_options')
26
26
  end