karafka 2.0.0 → 2.1.0

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 (246) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/workflows/ci.yml +40 -16
  5. data/.rspec +4 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +459 -796
  8. data/Gemfile +3 -2
  9. data/Gemfile.lock +44 -32
  10. data/LICENSE +1 -1
  11. data/README.md +22 -17
  12. data/bin/integrations +86 -26
  13. data/bin/karafka +1 -13
  14. data/bin/rspecs +6 -0
  15. data/bin/verify_license_integrity +37 -0
  16. data/certs/cert_chain.pem +26 -0
  17. data/config/{errors.yml → locales/errors.yml} +34 -6
  18. data/config/locales/pro_errors.yml +34 -0
  19. data/docker-compose.yml +4 -30
  20. data/karafka.gemspec +17 -10
  21. data/lib/active_job/karafka.rb +3 -7
  22. data/lib/active_job/queue_adapters/karafka_adapter.rb +7 -1
  23. data/lib/karafka/active_job/consumer.rb +29 -6
  24. data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
  25. data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
  26. data/lib/karafka/active_job/current_attributes.rb +42 -0
  27. data/lib/karafka/active_job/dispatcher.rb +34 -3
  28. data/lib/karafka/active_job/job_options_contract.rb +13 -2
  29. data/lib/karafka/admin.rb +246 -0
  30. data/lib/karafka/app.rb +30 -7
  31. data/lib/karafka/base_consumer.rb +124 -82
  32. data/lib/karafka/cli/base.rb +20 -0
  33. data/lib/karafka/cli/console.rb +13 -8
  34. data/lib/karafka/cli/info.rb +3 -2
  35. data/lib/karafka/cli/server.rb +55 -8
  36. data/lib/karafka/cli/topics.rb +146 -0
  37. data/lib/karafka/connection/client.rb +118 -61
  38. data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
  39. data/lib/karafka/connection/listener.rb +78 -30
  40. data/lib/karafka/connection/listeners_batch.rb +18 -2
  41. data/lib/karafka/connection/rebalance_manager.rb +3 -0
  42. data/lib/karafka/contracts/config.rb +1 -1
  43. data/lib/karafka/contracts/consumer_group.rb +22 -9
  44. data/lib/karafka/contracts/server_cli_options.rb +71 -11
  45. data/lib/karafka/contracts/topic.rb +65 -0
  46. data/lib/karafka/contracts.rb +1 -1
  47. data/lib/karafka/embedded.rb +36 -0
  48. data/lib/karafka/errors.rb +6 -4
  49. data/lib/karafka/helpers/colorize.rb +6 -0
  50. data/lib/karafka/instrumentation/callbacks/error.rb +3 -4
  51. data/lib/karafka/instrumentation/callbacks/statistics.rb +14 -4
  52. data/lib/karafka/instrumentation/logger_listener.rb +151 -22
  53. data/lib/karafka/instrumentation/notifications.rb +26 -7
  54. data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
  55. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -1
  56. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +153 -0
  57. data/lib/karafka/instrumentation/vendors/datadog/{listener.rb → metrics_listener.rb} +62 -35
  58. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
  59. data/lib/karafka/licenser.rb +53 -50
  60. data/lib/karafka/messages/builders/batch_metadata.rb +8 -8
  61. data/lib/karafka/messages/builders/messages.rb +6 -2
  62. data/lib/karafka/messages/message.rb +14 -2
  63. data/lib/karafka/messages/messages.rb +5 -0
  64. data/lib/karafka/messages/parser.rb +14 -0
  65. data/lib/karafka/patches/rdkafka/bindings.rb +132 -0
  66. data/lib/karafka/pro/active_job/consumer.rb +10 -9
  67. data/lib/karafka/pro/active_job/dispatcher.rb +28 -3
  68. data/lib/karafka/pro/active_job/job_options_contract.rb +16 -3
  69. data/lib/karafka/pro/encryption/cipher.rb +58 -0
  70. data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
  71. data/lib/karafka/pro/encryption/errors.rb +24 -0
  72. data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
  73. data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
  74. data/lib/karafka/pro/encryption/setup/config.rb +48 -0
  75. data/lib/karafka/pro/encryption.rb +47 -0
  76. data/lib/karafka/pro/iterator.rb +253 -0
  77. data/lib/karafka/pro/loader.rb +52 -26
  78. data/lib/karafka/pro/performance_tracker.rb +10 -6
  79. data/lib/karafka/pro/processing/collapser.rb +62 -0
  80. data/lib/karafka/pro/processing/coordinator.rb +92 -16
  81. data/lib/karafka/pro/processing/filters/base.rb +61 -0
  82. data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
  83. data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
  84. data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
  85. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
  86. data/lib/karafka/pro/processing/filters_applier.rb +104 -0
  87. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +7 -5
  88. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
  89. data/lib/karafka/pro/processing/jobs_builder.rb +22 -4
  90. data/lib/karafka/pro/processing/partitioner.rb +33 -24
  91. data/lib/karafka/pro/processing/scheduler.rb +27 -8
  92. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +68 -0
  93. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +74 -0
  94. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
  95. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
  96. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +64 -0
  97. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +68 -0
  98. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
  99. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
  100. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
  101. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +64 -0
  102. data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
  103. data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
  104. data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
  105. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +79 -0
  106. data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
  107. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
  108. data/lib/karafka/pro/processing/strategies/base.rb +26 -0
  109. data/lib/karafka/pro/processing/strategies/default.rb +105 -0
  110. data/lib/karafka/pro/processing/strategies/dlq/default.rb +131 -0
  111. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
  112. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
  113. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
  114. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
  115. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
  116. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
  117. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
  118. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
  119. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +63 -0
  120. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +63 -0
  121. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
  122. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
  123. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
  124. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
  125. data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
  126. data/lib/karafka/pro/processing/strategies/ftr/default.rb +104 -0
  127. data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
  128. data/lib/karafka/pro/processing/strategies/lrj/default.rb +87 -0
  129. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
  130. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
  131. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
  132. data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
  133. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +80 -0
  134. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
  135. data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
  136. data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
  137. data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
  138. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
  139. data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
  140. data/lib/karafka/pro/processing/strategies/vp/default.rb +104 -0
  141. data/lib/karafka/pro/{contracts/base.rb → processing/strategies.rb} +6 -5
  142. data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
  143. data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
  144. data/lib/karafka/pro/routing/features/base.rb +24 -0
  145. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +50 -0
  146. data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
  147. data/lib/karafka/pro/routing/{builder_extensions.rb → features/delaying/config.rb} +8 -11
  148. data/lib/karafka/pro/routing/features/delaying/contract.rb +38 -0
  149. data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
  150. data/lib/karafka/pro/routing/features/delaying.rb +29 -0
  151. data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
  152. data/lib/karafka/pro/routing/features/expiring/contract.rb +38 -0
  153. data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
  154. data/lib/karafka/pro/routing/features/expiring.rb +27 -0
  155. data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
  156. data/lib/karafka/pro/routing/features/filtering/contract.rb +41 -0
  157. data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
  158. data/lib/karafka/pro/routing/features/filtering.rb +27 -0
  159. data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
  160. data/lib/karafka/pro/routing/features/long_running_job/contract.rb +37 -0
  161. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
  162. data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
  163. data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
  164. data/lib/karafka/pro/routing/features/throttling/contract.rb +41 -0
  165. data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
  166. data/lib/karafka/pro/routing/features/throttling.rb +30 -0
  167. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
  168. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +52 -0
  169. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
  170. data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
  171. data/lib/karafka/process.rb +23 -8
  172. data/lib/karafka/processing/coordinator.rb +102 -23
  173. data/lib/karafka/processing/coordinators_buffer.rb +5 -1
  174. data/lib/karafka/processing/executor.rb +51 -21
  175. data/lib/karafka/processing/executors_buffer.rb +10 -26
  176. data/lib/karafka/processing/jobs/base.rb +6 -2
  177. data/lib/karafka/processing/jobs/consume.rb +8 -5
  178. data/lib/karafka/processing/jobs/idle.rb +24 -0
  179. data/lib/karafka/processing/jobs_builder.rb +2 -3
  180. data/lib/karafka/processing/jobs_queue.rb +14 -8
  181. data/lib/karafka/processing/partitioner.rb +3 -1
  182. data/lib/karafka/processing/result.rb +14 -1
  183. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
  184. data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
  185. data/lib/karafka/processing/strategies/base.rb +52 -0
  186. data/lib/karafka/processing/strategies/default.rb +142 -0
  187. data/lib/karafka/processing/strategies/dlq.rb +88 -0
  188. data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
  189. data/lib/karafka/processing/strategies/mom.rb +29 -0
  190. data/lib/karafka/processing/strategy_selector.rb +47 -0
  191. data/lib/karafka/processing/worker.rb +8 -3
  192. data/lib/karafka/railtie.rb +24 -12
  193. data/lib/karafka/routing/activity_manager.rb +84 -0
  194. data/lib/karafka/routing/builder.rb +20 -0
  195. data/lib/karafka/routing/consumer_group.rb +38 -4
  196. data/lib/karafka/routing/consumer_mapper.rb +0 -10
  197. data/lib/karafka/routing/features/active_job/builder.rb +33 -0
  198. data/lib/karafka/routing/features/active_job/config.rb +15 -0
  199. data/lib/karafka/routing/features/active_job/contract.rb +41 -0
  200. data/lib/karafka/routing/features/active_job/topic.rb +33 -0
  201. data/lib/karafka/routing/features/active_job.rb +13 -0
  202. data/lib/karafka/routing/features/base/expander.rb +53 -0
  203. data/lib/karafka/routing/features/base.rb +34 -0
  204. data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
  205. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +42 -0
  206. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
  207. data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
  208. data/lib/karafka/routing/features/declaratives/config.rb +18 -0
  209. data/lib/karafka/routing/features/declaratives/contract.rb +30 -0
  210. data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
  211. data/lib/karafka/routing/features/declaratives.rb +14 -0
  212. data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
  213. data/lib/karafka/routing/features/manual_offset_management/contract.rb +24 -0
  214. data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
  215. data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
  216. data/lib/karafka/routing/proxy.rb +18 -20
  217. data/lib/karafka/routing/router.rb +27 -2
  218. data/lib/karafka/routing/subscription_group.rb +43 -5
  219. data/lib/karafka/routing/subscription_groups_builder.rb +6 -1
  220. data/lib/karafka/routing/topic.rb +17 -5
  221. data/lib/karafka/routing/topics.rb +8 -0
  222. data/lib/karafka/runner.rb +1 -0
  223. data/lib/karafka/serialization/json/deserializer.rb +1 -0
  224. data/lib/karafka/server.rb +58 -28
  225. data/lib/karafka/setup/attributes_map.rb +344 -0
  226. data/lib/karafka/setup/config.rb +48 -19
  227. data/lib/karafka/status.rb +35 -7
  228. data/lib/karafka/templates/karafka.rb.erb +14 -6
  229. data/lib/karafka/time_trackers/base.rb +1 -6
  230. data/lib/karafka/time_trackers/pause.rb +49 -19
  231. data/lib/karafka/time_trackers/poll.rb +7 -3
  232. data/lib/karafka/version.rb +1 -1
  233. data/lib/karafka.rb +56 -3
  234. data/renovate.json +6 -0
  235. data.tar.gz.sig +0 -0
  236. metadata +189 -61
  237. metadata.gz.sig +0 -0
  238. data/bin/wait_for_kafka +0 -20
  239. data/certs/mensfeld.pem +0 -25
  240. data/lib/karafka/active_job/routing/extensions.rb +0 -31
  241. data/lib/karafka/contracts/consumer_group_topic.rb +0 -42
  242. data/lib/karafka/instrumentation.rb +0 -21
  243. data/lib/karafka/pro/base_consumer.rb +0 -82
  244. data/lib/karafka/pro/contracts/consumer_group.rb +0 -34
  245. data/lib/karafka/pro/contracts/consumer_group_topic.rb +0 -33
  246. data/lib/karafka/pro/routing/topic_extensions.rb +0 -38
@@ -7,25 +7,85 @@ 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
14
14
 
15
- optional(:consumer_groups) { |cg| cg.is_a?(Array) && !cg.empty? }
15
+ %i[
16
+ include
17
+ exclude
18
+ ].each do |action|
19
+ optional(:"#{action}_consumer_groups") { |cg| cg.is_a?(Array) }
20
+ optional(:"#{action}_subscription_groups") { |sg| sg.is_a?(Array) }
21
+ optional(:"#{action}_topics") { |topics| topics.is_a?(Array) }
16
22
 
17
- virtual do |data, errors|
18
- next unless errors.empty?
19
- next unless data.key?(:consumer_groups)
23
+ virtual do |data, errors|
24
+ next unless errors.empty?
25
+
26
+ value = data.fetch(:"#{action}_consumer_groups")
27
+
28
+ # If there were no consumer_groups declared in the server cli, it means that we will
29
+ # run all of them and no need to validate them here at all
30
+ next if value.empty?
31
+ next if (value - Karafka::App.consumer_groups.map(&:name)).empty?
32
+
33
+ # Found unknown consumer groups
34
+ [[[:"#{action}_consumer_groups"], :consumer_groups_inclusion]]
35
+ end
36
+
37
+ virtual do |data, errors|
38
+ next unless errors.empty?
39
+
40
+ value = data.fetch(:"#{action}_subscription_groups")
41
+
42
+ # If there were no subscription_groups declared in the server cli, it means that we will
43
+ # run all of them and no need to validate them here at all
44
+ next if value.empty?
45
+
46
+ subscription_groups = Karafka::App
47
+ .consumer_groups
48
+ .map(&:subscription_groups)
49
+ .flatten
50
+ .map(&:name)
51
+
52
+ next if (value - subscription_groups).empty?
20
53
 
21
- value = data.fetch(:consumer_groups)
54
+ # Found unknown subscription groups
55
+ [[[:"#{action}_subscription_groups"], :subscription_groups_inclusion]]
56
+ end
57
+
58
+ virtual do |data, errors|
59
+ next unless errors.empty?
60
+
61
+ value = data.fetch(:"#{action}_topics")
62
+
63
+ # If there were no topics declared in the server cli, it means that we will
64
+ # run all of them and no need to validate them here at all
65
+ next if value.empty?
66
+
67
+ topics = Karafka::App
68
+ .consumer_groups
69
+ .map(&:subscription_groups)
70
+ .flatten
71
+ .map(&:topics)
72
+ .map { |gtopics| gtopics.map(&:name) }
73
+ .flatten
74
+
75
+ next if (value - topics).empty?
76
+
77
+ # Found unknown topics
78
+ [[[:"#{action}_topics"], :topics_inclusion]]
79
+ end
80
+ end
81
+
82
+ # Makes sure we have anything to subscribe to when we start the server
83
+ virtual do |_, errors|
84
+ next unless errors.empty?
22
85
 
23
- # If there were no consumer_groups declared in the server cli, it means that we will
24
- # run all of them and no need to validate them here at all
25
- next if value.nil?
26
- next if (value - Karafka::App.config.internal.routing.builder.map(&:name)).empty?
86
+ next unless Karafka::App.subscription_groups.empty?
27
87
 
28
- [[%i[consumer_groups], :consumer_groups_inclusion]]
88
+ [[%i[include_topics], :topics_missing]]
29
89
  end
30
90
  end
31
91
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Contracts
5
+ # Consumer group topic validation rules.
6
+ class Topic < Base
7
+ configure do |config|
8
+ config.error_messages = YAML.safe_load(
9
+ File.read(
10
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
11
+ )
12
+ ).fetch('en').fetch('validations').fetch('topic')
13
+ end
14
+
15
+ required(:deserializer) { |val| !val.nil? }
16
+ required(:id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
17
+ required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
18
+ required(:max_messages) { |val| val.is_a?(Integer) && val >= 1 }
19
+ required(:initial_offset) { |val| %w[earliest latest].include?(val) }
20
+ required(:max_wait_time) { |val| val.is_a?(Integer) && val >= 10 }
21
+ required(:name) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
22
+ required(:active) { |val| [true, false].include?(val) }
23
+ required(:subscription_group) { |val| val.is_a?(String) && !val.empty? }
24
+
25
+ # Consumer needs to be present only if topic is active
26
+ # We allow not to define consumer for non-active because they may be only used via admin
27
+ # api or other ways and not consumed with consumer
28
+ virtual do |data, errors|
29
+ next unless errors.empty?
30
+ next if data.fetch(:consumer)
31
+ next unless data.fetch(:active)
32
+
33
+ [[%w[consumer], :missing]]
34
+ end
35
+
36
+ virtual do |data, errors|
37
+ next unless errors.empty?
38
+
39
+ value = data.fetch(:kafka)
40
+
41
+ begin
42
+ # This will trigger rdkafka validations that we catch and re-map the info and use dry
43
+ # compatible format
44
+ Rdkafka::Config.new(value).send(:native_config)
45
+
46
+ nil
47
+ rescue Rdkafka::Config::ConfigError => e
48
+ [[%w[kafka], e.message]]
49
+ end
50
+ end
51
+
52
+ virtual do |data, errors|
53
+ next unless errors.empty?
54
+ next unless ::Karafka::App.config.strict_topics_namespacing
55
+
56
+ value = data.fetch(:name)
57
+ namespacing_chars_count = value.chars.find_all { |c| ['.', '_'].include?(c) }.uniq.count
58
+
59
+ next if namespacing_chars_count <= 1
60
+
61
+ [[%w[name], :inconsistent_namespacing]]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -5,6 +5,6 @@ module Karafka
5
5
  module Contracts
6
6
  # Regexp for validating format of groups and topics
7
7
  # @note It is not nested inside of the contracts, as it is used by couple of them
8
- TOPIC_REGEXP = /\A(\w|-|\.)+\z/
8
+ TOPIC_REGEXP = /^[A-Za-z0-9\-_.]+$/
9
9
  end
10
10
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Allows to start and stop Karafka as part of a different process
5
+ module Embedded
6
+ class << self
7
+ # Starts Karafka without supervision and without ownership of signals in a background thread
8
+ # so it won't interrupt other things running
9
+ def start
10
+ Thread.new do
11
+ Karafka::Process.tags.add(:execution_mode, 'embedded')
12
+ Karafka::Server.start
13
+ end
14
+ end
15
+
16
+ # Stops Karafka upon any event
17
+ #
18
+ # @note This method is blocking because we want to wait until Karafka is stopped with final
19
+ # process shutdown
20
+ def stop
21
+ # Stop needs to be blocking to wait for all the things to finalize
22
+ Karafka::Server.stop
23
+ end
24
+
25
+ # Quiets Karafka upon any event
26
+ #
27
+ # @note This method is not blocking and will not wait for Karafka to fully quiet.
28
+ # It will trigger the quiet procedure but won't wait.
29
+ #
30
+ # @note Please keep in mind you need to `#stop` to actually stop the server anyhow.
31
+ def quiet
32
+ Karafka::Server.quiet
33
+ end
34
+ end
35
+ end
36
+ end
@@ -41,11 +41,13 @@ module Karafka
41
41
  # Raised when the license token is not valid
42
42
  InvalidLicenseTokenError = Class.new(BaseError)
43
43
 
44
- # Used to instrument this error into the error notifications
45
- # We do not raise it so we won't crash deployed systems
46
- ExpiredLicenseTokenError = Class.new(BaseError)
44
+ # This should never happen. Please open an issue if it does.
45
+ InvalidCoordinatorStateError = Class.new(BaseError)
46
+
47
+ # This should never happen. Please open an issue if it does.
48
+ StrategyNotFoundError = Class.new(BaseError)
47
49
 
48
50
  # This should never happen. Please open an issue if it does.
49
- InvalidCoordinatorState = Class.new(BaseError)
51
+ InvalidRealOffsetUsage = Class.new(BaseError)
50
52
  end
51
53
  end
@@ -15,6 +15,12 @@ module Karafka
15
15
  def red(string)
16
16
  "\033[0;31m#{string}\033[0m"
17
17
  end
18
+
19
+ # @param string [String] string we want to have in yellow
20
+ # @return [String] yellow string
21
+ def yellow(string)
22
+ "\033[1;33m#{string}\033[0m"
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -9,12 +9,10 @@ module Karafka
9
9
  # @param subscription_group_id [String] id of the current subscription group instance
10
10
  # @param consumer_group_id [String] id of the current consumer group
11
11
  # @param client_name [String] rdkafka client name
12
- # @param monitor [WaterDrop::Instrumentation::Monitor] monitor we are using
13
- def initialize(subscription_group_id, consumer_group_id, client_name, monitor)
12
+ def initialize(subscription_group_id, consumer_group_id, client_name)
14
13
  @subscription_group_id = subscription_group_id
15
14
  @consumer_group_id = consumer_group_id
16
15
  @client_name = client_name
17
- @monitor = monitor
18
16
  end
19
17
 
20
18
  # Runs the instrumentation monitor with error
@@ -26,8 +24,9 @@ module Karafka
26
24
  # Same as with statistics (mor explanation there)
27
25
  return unless @client_name == client_name
28
26
 
29
- @monitor.instrument(
27
+ ::Karafka.monitor.instrument(
30
28
  'error.occurred',
29
+ caller: self,
31
30
  subscription_group_id: @subscription_group_id,
32
31
  consumer_group_id: @consumer_group_id,
33
32
  type: 'librdkafka.error',
@@ -10,12 +10,10 @@ module Karafka
10
10
  # @param subscription_group_id [String] id of the current subscription group
11
11
  # @param consumer_group_id [String] id of the current consumer group
12
12
  # @param client_name [String] rdkafka client name
13
- # @param monitor [WaterDrop::Instrumentation::Monitor] monitor we are using
14
- def initialize(subscription_group_id, consumer_group_id, client_name, monitor)
13
+ def initialize(subscription_group_id, consumer_group_id, client_name)
15
14
  @subscription_group_id = subscription_group_id
16
15
  @consumer_group_id = consumer_group_id
17
16
  @client_name = client_name
18
- @monitor = monitor
19
17
  @statistics_decorator = ::Karafka::Core::Monitoring::StatisticsDecorator.new
20
18
  end
21
19
 
@@ -28,12 +26,24 @@ module Karafka
28
26
  # all the time.
29
27
  return unless @client_name == statistics['name']
30
28
 
31
- @monitor.instrument(
29
+ ::Karafka.monitor.instrument(
32
30
  'statistics.emitted',
33
31
  subscription_group_id: @subscription_group_id,
34
32
  consumer_group_id: @consumer_group_id,
35
33
  statistics: @statistics_decorator.call(statistics)
36
34
  )
35
+ # We need to catch and handle any potential errors coming from the instrumentation pipeline
36
+ # as otherwise, in case of statistics which run in the main librdkafka thread, any crash
37
+ # will hang the whole process.
38
+ rescue StandardError => e
39
+ ::Karafka.monitor.instrument(
40
+ 'error.occurred',
41
+ caller: self,
42
+ subscription_group_id: @subscription_group_id,
43
+ consumer_group_id: @consumer_group_id,
44
+ type: 'statistics.emitted.error',
45
+ error: e
46
+ )
37
47
  end
38
48
  end
39
49
  end
@@ -14,9 +14,11 @@ module Karafka
14
14
  fatal
15
15
  ].freeze
16
16
 
17
+ private_constant :USED_LOG_LEVELS
18
+
17
19
  # Logs each messages fetching attempt
18
20
  #
19
- # @param event [Dry::Events::Event] event details including payload
21
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
20
22
  def on_connection_listener_fetch_loop(event)
21
23
  listener = event[:caller]
22
24
  debug "[#{listener.id}] Polling messages..."
@@ -24,7 +26,7 @@ module Karafka
24
26
 
25
27
  # Logs about messages that we've received from Kafka
26
28
  #
27
- # @param event [Dry::Events::Event] event details including payload
29
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
28
30
  def on_connection_listener_fetch_loop_received(event)
29
31
  listener = event[:caller]
30
32
  time = event[:time]
@@ -40,31 +42,79 @@ module Karafka
40
42
 
41
43
  # Prints info about the fact that a given job has started
42
44
  #
43
- # @param event [Dry::Events::Event] event details including payload
45
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
44
46
  def on_worker_process(event)
45
47
  job = event[:job]
46
48
  job_type = job.class.to_s.split('::').last
47
49
  consumer = job.executor.topic.consumer
48
50
  topic = job.executor.topic.name
49
- info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} started"
51
+ partition = job.executor.partition
52
+ info "[#{job.id}] #{job_type} job for #{consumer} on #{topic}/#{partition} started"
50
53
  end
51
54
 
52
55
  # Prints info about the fact that a given job has finished
53
56
  #
54
- # @param event [Dry::Events::Event] event details including payload
57
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
55
58
  def on_worker_processed(event)
56
59
  job = event[:job]
57
60
  time = event[:time]
58
61
  job_type = job.class.to_s.split('::').last
59
62
  consumer = job.executor.topic.consumer
60
63
  topic = job.executor.topic.name
61
- info "[#{job.id}] #{job_type} job for #{consumer} on #{topic} finished in #{time}ms"
64
+ partition = job.executor.partition
65
+ info <<~MSG.tr("\n", ' ').strip!
66
+ [#{job.id}] #{job_type} job for #{consumer}
67
+ on #{topic}/#{partition} finished in #{time}ms
68
+ MSG
69
+ end
70
+
71
+ # Prints info about a consumer pause occurrence. Irrelevant if user or system initiated.
72
+ #
73
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
74
+ def on_client_pause(event)
75
+ topic = event[:topic]
76
+ partition = event[:partition]
77
+ offset = event[:offset]
78
+ client = event[:caller]
79
+
80
+ info <<~MSG.tr("\n", ' ').strip!
81
+ [#{client.id}] Pausing on topic #{topic}/#{partition} on offset #{offset}
82
+ MSG
83
+ end
84
+
85
+ # Prints information about resuming of processing of a given topic partition
86
+ #
87
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
88
+ def on_client_resume(event)
89
+ topic = event[:topic]
90
+ partition = event[:partition]
91
+ client = event[:caller]
92
+
93
+ info <<~MSG.tr("\n", ' ').strip!
94
+ [#{client.id}] Resuming on topic #{topic}/#{partition}
95
+ MSG
96
+ end
97
+
98
+ # Prints info about retry of processing after an error
99
+ #
100
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
101
+ def on_consumer_consuming_retry(event)
102
+ topic = event[:topic]
103
+ partition = event[:partition]
104
+ offset = event[:offset]
105
+ consumer = event[:caller]
106
+ timeout = event[:timeout]
107
+
108
+ info <<~MSG.tr("\n", ' ').strip!
109
+ [#{consumer.id}] Retrying of #{consumer.class} after #{timeout} ms
110
+ on topic #{topic}/#{partition} from offset #{offset}
111
+ MSG
62
112
  end
63
113
 
64
114
  # Logs info about system signals that Karafka received and prints backtrace for threads in
65
115
  # case of ttin
66
116
  #
67
- # @param event [Dry::Events::Event] event details including payload
117
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
68
118
  def on_process_notice_signal(event)
69
119
  info "Received #{event[:signal]} system signal"
70
120
 
@@ -85,37 +135,96 @@ module Karafka
85
135
  end
86
136
  end
87
137
 
88
- # Logs info that we're initializing Karafka app.
89
- #
90
- # @param _event [Dry::Events::Event] event details including payload
91
- def on_app_initializing(_event)
92
- info 'Initializing Karafka framework'
93
- end
94
-
95
138
  # Logs info that we're running Karafka app.
96
139
  #
97
- # @param _event [Dry::Events::Event] event details including payload
140
+ # @param _event [Karafka::Core::Monitoring::Event] event details including payload
98
141
  def on_app_running(_event)
99
- info 'Running Karafka server'
142
+ info "Running in #{RUBY_DESCRIPTION}"
143
+ info "Running Karafka #{Karafka::VERSION} server"
144
+
145
+ return if Karafka.pro?
146
+
147
+ info 'See LICENSE and the LGPL-3.0 for licensing details'
148
+ end
149
+
150
+ # @param _event [Karafka::Core::Monitoring::Event] event details including payload
151
+ def on_app_quieting(_event)
152
+ info 'Switching to quiet mode. New messages will not be processed'
153
+ end
154
+
155
+ # @param _event [Karafka::Core::Monitoring::Event] event details including payload
156
+ def on_app_quiet(_event)
157
+ info 'Reached quiet mode. No messages will be processed anymore'
100
158
  end
101
159
 
102
160
  # Logs info that we're going to stop the Karafka server.
103
161
  #
104
- # @param _event [Dry::Events::Event] event details including payload
162
+ # @param _event [Karafka::Core::Monitoring::Event] event details including payload
105
163
  def on_app_stopping(_event)
106
164
  info 'Stopping Karafka server'
107
165
  end
108
166
 
109
167
  # Logs info that we stopped the Karafka server.
110
168
  #
111
- # @param _event [Dry::Events::Event] event details including payload
169
+ # @param _event [Karafka::Core::Monitoring::Event] event details including payload
112
170
  def on_app_stopped(_event)
113
171
  info 'Stopped Karafka server'
114
172
  end
115
173
 
174
+ # Logs info when we have dispatched a message the the DLQ
175
+ #
176
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
177
+ def on_dead_letter_queue_dispatched(event)
178
+ consumer = event[:caller]
179
+ topic = consumer.topic.name
180
+ message = event[:message]
181
+ offset = message.offset
182
+ dlq_topic = consumer.topic.dead_letter_queue.topic
183
+ partition = message.partition
184
+
185
+ info <<~MSG.tr("\n", ' ').strip!
186
+ [#{consumer.id}] Dispatched message #{offset}
187
+ from #{topic}/#{partition}
188
+ to DLQ topic: #{dlq_topic}
189
+ MSG
190
+ end
191
+
192
+ # Logs info about throttling event
193
+ #
194
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
195
+ def on_filtering_throttled(event)
196
+ consumer = event[:caller]
197
+ topic = consumer.topic.name
198
+ # Here we get last message before throttle
199
+ message = event[:message]
200
+ partition = message.partition
201
+ offset = message.offset
202
+
203
+ info <<~MSG.tr("\n", ' ').strip!
204
+ [#{consumer.id}] Throttled and will resume
205
+ from message #{offset}
206
+ on #{topic}/#{partition}
207
+ MSG
208
+ end
209
+
210
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
211
+ def on_filtering_seek(event)
212
+ consumer = event[:caller]
213
+ topic = consumer.topic.name
214
+ # Message to which we seek
215
+ message = event[:message]
216
+ partition = message.partition
217
+ offset = message.offset
218
+
219
+ info <<~MSG.tr("\n", ' ').strip!
220
+ [#{consumer.id}] Post-filtering seeking to message #{offset}
221
+ on #{topic}/#{partition}
222
+ MSG
223
+ end
224
+
116
225
  # There are many types of errors that can occur in many places, but we provide a single
117
226
  # handler for all of them to simplify error instrumentation.
118
- # @param event [Dry::Events::Event] event details including payload
227
+ # @param event [Karafka::Core::Monitoring::Event] event details including payload
119
228
  def on_error_occurred(event)
120
229
  type = event[:type]
121
230
  error = event[:error]
@@ -128,6 +237,18 @@ module Karafka
128
237
  when 'consumer.revoked.error'
129
238
  error "Consumer on revoked failed due to an error: #{error}"
130
239
  error details
240
+ when 'consumer.before_enqueue.error'
241
+ error "Consumer before enqueue failed due to an error: #{error}"
242
+ error details
243
+ when 'consumer.before_consume.error'
244
+ error "Consumer before consume failed due to an error: #{error}"
245
+ error details
246
+ when 'consumer.after_consume.error'
247
+ error "Consumer after consume failed due to an error: #{error}"
248
+ error details
249
+ when 'consumer.idle.error'
250
+ error "Consumer idle failed due to an error: #{error}"
251
+ error details
131
252
  when 'consumer.shutdown.error'
132
253
  error "Consumer on shutdown failed due to an error: #{error}"
133
254
  error details
@@ -137,9 +258,6 @@ module Karafka
137
258
  when 'connection.listener.fetch_loop.error'
138
259
  error "Listener fetch loop error: #{error}"
139
260
  error details
140
- when 'licenser.expired'
141
- error error
142
- error details
143
261
  when 'runner.call.error'
144
262
  fatal "Runner crashed due to an error: #{error}"
145
263
  fatal details
@@ -148,6 +266,17 @@ module Karafka
148
266
  when 'librdkafka.error'
149
267
  error "librdkafka internal error occurred: #{error}"
150
268
  error details
269
+ # Those can occur when emitted statistics are consumed by the end user and the processing
270
+ # of statistics fails. The statistics are emitted from librdkafka main loop thread and
271
+ # any errors there crash the whole thread
272
+ when 'statistics.emitted.error'
273
+ error "statistics.emitted processing failed due to an error: #{error}"
274
+ error details
275
+ # Those will only occur when retries in the client fail and when they did not stop after
276
+ # back-offs
277
+ when 'connection.client.poll.error'
278
+ error "Data polling error occurred: #{error}"
279
+ error details
151
280
  else
152
281
  # This should never happen. Please contact the maintainers
153
282
  raise Errors::UnsupportedCaseError, event
@@ -17,31 +17,50 @@ module Karafka
17
17
  # complete list of all the events. Please use the #available_events on fully loaded
18
18
  # Karafka system to determine all of the events you can use.
19
19
  EVENTS = %w[
20
+ active_job.consume
21
+ active_job.consumed
22
+
20
23
  app.initialized
21
24
  app.running
25
+ app.quieting
26
+ app.quiet
22
27
  app.stopping
23
28
  app.stopped
29
+ app.terminated
30
+
31
+ client.pause
32
+ client.resume
33
+
34
+ connection.listener.before_fetch_loop
35
+ connection.listener.fetch_loop
36
+ connection.listener.fetch_loop.received
24
37
 
38
+ consumer.consume
25
39
  consumer.consumed
40
+ consumer.consuming.pause
41
+ consumer.consuming.retry
42
+ consumer.idle
43
+ consumer.revoke
26
44
  consumer.revoked
45
+ consumer.shutting_down
27
46
  consumer.shutdown
28
47
 
48
+ dead_letter_queue.dispatched
49
+
50
+ filtering.throttled
51
+ filtering.seek
52
+
29
53
  process.notice_signal
30
54
 
31
- connection.listener.before_fetch_loop
32
- connection.listener.fetch_loop
33
- connection.listener.fetch_loop.received
55
+ statistics.emitted
34
56
 
35
57
  worker.process
36
58
  worker.processed
37
-
38
- statistics.emitted
59
+ worker.completed
39
60
 
40
61
  error.occurred
41
62
  ].freeze
42
63
 
43
- private_constant :EVENTS
44
-
45
64
  # @return [Karafka::Instrumentation::Monitor] monitor instance for system instrumentation
46
65
  def initialize
47
66
  super
@@ -4,22 +4,13 @@ module Karafka
4
4
  module Instrumentation
5
5
  # Listener that sets a proc title with a nice descriptive value
6
6
  class ProctitleListener
7
- # Updates proc title to an initializing one
8
- # @param _event [Karafka::Core::Monitoring::Event] event details including payload
9
- def on_app_initializing(_event)
10
- setproctitle('initializing')
11
- end
12
-
13
- # Updates proc title to a running one
14
- # @param _event [Karafka::Core::Monitoring::Event] event details including payload
15
- def on_app_running(_event)
16
- setproctitle('running')
17
- end
18
-
19
- # Updates proc title to a stopping one
20
- # @param _event [Karafka::Core::Monitoring::Event] event details including payload
21
- def on_app_stopping(_event)
22
- setproctitle('stopping')
7
+ Status::STATES.each_key do |state|
8
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
9
+ # Updates proc title to an appropriate state
10
+ def on_app_#{state}(_event)
11
+ setproctitle('#{state}')
12
+ end
13
+ RUBY
23
14
  end
24
15
 
25
16
  private