julewire-karafka 1.0.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2348fa4e2f74ae14416481646fb82ca838306ba5f637b3ee21d38d8e25d7f1c3
4
- data.tar.gz: 937a2babb22b4ed2bcc277c12d008568646a0dcef77dac25cbfb9c829b516c57
3
+ metadata.gz: c859473affe912e211098b9247586fb17695c366d9d554d95f4690013fe2d4dc
4
+ data.tar.gz: cce0973311bb175fa5c3b998706f7af2a370ff18719019962241b62242fd3903
5
5
  SHA512:
6
- metadata.gz: c1ea911dce21f4ae508f70f8a8d47a9beebe35c44d235ca3d0e5a27753996634ca15f123b4c431f04c4f3fadfe1c2d1d63adfe79bdb56cf11c3a03cb45dfb516
7
- data.tar.gz: db66dcbf0c4faaccd8b10a1240bdb045f307ddbc842bf375d5e2718119edef8cf5411164e58afe9c3a3e9330c0cacc8c8541d6e90ef027ad1abd8feaa1a6b314
6
+ metadata.gz: '09a000b1edf0d1052cb36ca3a6bbbb0cf7db1597111e9b9386ebcd61884fee68d3da8594f753b84f91eaf770dbe6aea89eafa29cec718059a6090b3f426875fc'
7
+ data.tar.gz: 658376067030f6873f06ea26816598163b899b78c53389b97e44547f4b03b63a74d4ea37ba0208a73c500abd55825791c7e49f1dc8dbb35d9eb3f628b8b223e8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 1.1.0 - 2026-07-20
4
+
5
+ - Report unsupported WaterDrop message shapes through Karafka integration
6
+ health instead of silently skipping propagation.
7
+ - Restore propagated context through owned carrier envelopes.
8
+ - Require julewire-core 1.1.0.
9
+
3
10
  ## 1.0.1 - 2026-06-25
4
11
 
5
12
  - Default propagation carrier byte limits to 64 KiB and record health when
data/README.md CHANGED
@@ -18,6 +18,9 @@ class KarafkaApp < Karafka::App
18
18
  end
19
19
  ```
20
20
 
21
+ Consumer installation is explicit: pass `monitor:` or `app:` so Julewire binds to
22
+ the intended Karafka monitor.
23
+
21
24
  Message processing:
22
25
 
23
26
  ```ruby
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Default Path
4
4
 
5
+ Install consumer hooks with an explicit monitor or app:
6
+
7
+ ```ruby
8
+ Julewire::Karafka.install!(monitor: Karafka.monitor)
9
+ ```
10
+
11
+ `install!` does not discover a global Karafka app. Passing the monitor keeps the
12
+ integration bound to the app instance being configured.
13
+
5
14
  | Option | Default | Purpose |
6
15
  | --- | --- | --- |
7
16
  | `enabled` | `true` | Install the integration. |
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.executables = []
35
35
  spec.require_paths = ["lib"]
36
36
 
37
- spec.add_dependency "julewire-core", ">= 1.0.1"
37
+ spec.add_dependency "julewire-core", ">= 1.1.0"
38
38
  spec.add_dependency "karafka", ">= 2.5"
39
39
  spec.add_dependency "waterdrop", ">= 2.10"
40
40
  spec.add_dependency "zeitwerk", ">= 2.8.1"
@@ -17,10 +17,11 @@ module Julewire
17
17
  payload = event_payload(event)
18
18
  return {} unless payload.is_a?(Hash)
19
19
 
20
- payload.each_with_object(consumer_payload(name, payload)) do |(key, value), result|
20
+ fields = payload.each_with_object(consumer_payload(name, payload)) do |(key, value), result|
21
21
  safe = safe_value(key, value)
22
22
  result[key] = safe unless safe.nil?
23
23
  end
24
+ Core::Fields::FieldSet.deep_symbolize_keys(fields)
24
25
  rescue StandardError => e
25
26
  { payload_error: { exception_class: e.class.name } }
26
27
  end
@@ -37,7 +37,7 @@ module Julewire
37
37
 
38
38
  def payload_severity(payload)
39
39
  value = payload_value(payload, :severity) || payload_value(payload, :level)
40
- Julewire::Core::Records::Severity.normalize(value) if value
40
+ Core::Records::Severity.normalize(value)
41
41
  rescue StandardError
42
42
  nil
43
43
  end
@@ -85,22 +85,14 @@ module Julewire
85
85
  end
86
86
 
87
87
  def raw_event_value(event, key)
88
- raw = EventPayload.event_payload(event)
89
- return raw[key] if raw.respond_to?(:key?) && raw.key?(key)
90
- return raw[key.to_s] if raw.respond_to?(:key?) && raw.key?(key.to_s)
91
- return event[key] if event.respond_to?(:[])
92
-
93
- nil
94
- rescue StandardError
95
- nil
88
+ Core::Integration::Values::Read.value(EventPayload.event_payload(event), key)
96
89
  end
97
90
 
98
91
  def collection_count(value)
99
- return value[:count] if value.is_a?(Hash) && value.key?(:count)
100
- return value["count"] if value.is_a?(Hash) && value.key?("count")
101
- return value.size if value.respond_to?(:size)
92
+ count = Core::Integration::Values::Read.hash_value(value, :count, default: Core::UNSET)
93
+ return count unless count.equal?(Core::UNSET)
102
94
 
103
- nil
95
+ value.size if value.respond_to?(:size)
104
96
  end
105
97
  end
106
98
  end
@@ -11,18 +11,17 @@ module Julewire
11
11
  private_constant :EVENTS, :INSTALL_STATE
12
12
 
13
13
  class << self
14
- def subscribe!(monitor, configuration: Configuration.new)
14
+ def subscribe!(monitor, configuration:)
15
15
  return unless configuration.enabled?
16
16
  return unless monitor.respond_to?(:subscribe)
17
17
 
18
- state = INSTALL_STATE.fetch_or_store(monitor) { { events: [].freeze }.freeze }
19
- subscribed_events = Array(state[:events]).dup
18
+ state = INSTALL_STATE.fetch_or_store(monitor) { { events: [] } }
19
+ subscribed_events = state.fetch(:events)
20
20
  EVENTS.each do |event_name|
21
21
  next if subscribed_events.include?(event_name)
22
22
 
23
23
  subscribed_events << event_name if subscribe_event(monitor, event_name)
24
24
  end
25
- INSTALL_STATE.store(monitor, { events: subscribed_events.freeze }.freeze)
26
25
  monitor
27
26
  end
28
27
 
@@ -35,10 +34,7 @@ module Julewire
35
34
  private
36
35
 
37
36
  def subscribe_event(monitor, event_name)
38
- IntegrationHealth.with_failure_health(action: :subscribe, component: :fork_hooks, event: event_name) do
39
- monitor.subscribe(event_name) { handle(event_name, it) }
40
- true
41
- end || false
37
+ MonitorSubscription.subscribe(monitor, event_name, component: :fork_hooks) { handle(event_name, it) }
42
38
  end
43
39
  end
44
40
  end
@@ -4,9 +4,7 @@ module Julewire
4
4
  module Karafka
5
5
  module Installer
6
6
  class << self
7
- def install!(app: nil, monitor: nil, configuration: Configuration.new)
8
- return false unless configuration.enabled?
9
-
7
+ def install!(app:, configuration:, monitor: nil)
10
8
  monitor ||= monitor_for(app)
11
9
  raise Error, "Karafka monitor is not available" unless monitor
12
10
 
@@ -20,10 +18,7 @@ module Julewire
20
18
  private
21
19
 
22
20
  def monitor_for(app)
23
- app ||= defined?(::Karafka::App) ? ::Karafka::App : nil
24
- app.config.monitor if app.respond_to?(:config)
25
- rescue StandardError
26
- nil
21
+ Core::Integration::Values::Read.nested_value(app, :config, :monitor)
27
22
  end
28
23
  end
29
24
  end
@@ -5,21 +5,24 @@ module Julewire
5
5
  module MessageContext
6
6
  class << self
7
7
  def call(message, configuration:, fields: nil, &)
8
- raise ArgumentError, "block required" unless block_given?
9
-
10
8
  fields ||= PayloadReader.message_payload(message)
9
+ call_fields(fields, configuration: configuration, &)
10
+ end
11
+
12
+ def call_fields(fields, configuration:, &)
11
13
  carrier = carrier_for(fields, configuration)
12
14
 
13
- result = Julewire::Core::Propagation::Carrier.extract_result(
15
+ result = Core::Propagation::Carrier.extract_result(
14
16
  carrier,
15
17
  key: configuration.carrier_key,
16
18
  max_bytes: configuration.carrier_max_bytes
17
19
  )
18
20
  record_carrier_restore_failure(result)
21
+ fields = Core::Fields::FieldSet.deep_symbolize_keys(fields)
19
22
 
20
- Julewire::Core::Propagation.restore(result.envelope, owned: true) do
21
- Julewire::Core::Integration::Facade.with_neutral(message_neutral(fields)) do
22
- Julewire::Core::Integration::Facade.with_attributes(message_attributes(fields), &)
23
+ Core::Propagation.restore(result.envelope, owned: true) do
24
+ Core::Integration::Facade.with_neutral(message_neutral(fields)) do
25
+ Core::Integration::Facade.with_attributes(message_attributes(fields), &)
23
26
  end
24
27
  end
25
28
  end
@@ -39,17 +42,20 @@ module Julewire
39
42
  end
40
43
 
41
44
  def carrier_for(fields, configuration)
42
- return {} unless configuration.propagation?
45
+ return unless configuration.propagation?
43
46
 
44
- headers = fields[:headers].is_a?(Hash) ? fields[:headers] : {}
47
+ headers = fields[:headers]
48
+ headers = {} unless headers.is_a?(Hash)
45
49
  filter = configuration.carrier_filter
46
50
  return headers unless filter
47
51
 
48
- filtered = filter.call(headers, message: fields)
49
- filtered.is_a?(Hash) ? filtered : {}
50
- rescue StandardError => e
51
- IntegrationHealth.record_failure(e, action: :carrier_filter, component: :message_context)
52
- {}
52
+ begin
53
+ filtered = filter.call(headers, message: fields)
54
+ filtered.is_a?(Hash) ? filtered : {}
55
+ rescue StandardError => e
56
+ IntegrationHealth.record_failure(e, action: :carrier_filter, component: :message_context)
57
+ {}
58
+ end
53
59
  end
54
60
 
55
61
  def message_attributes(fields) = { karafka: fields }
@@ -7,9 +7,7 @@ module Julewire
7
7
  DEFAULT_SUMMARY_EVENT = "message.completed"
8
8
 
9
9
  class << self
10
- def call(message, configuration: Configuration.new, **options, &)
11
- raise ArgumentError, "block required" unless block_given?
12
-
10
+ def call(message, configuration:, **options, &)
13
11
  fields = PayloadReader.message_payload(message)
14
12
  execution_fields = execution_fields(options)
15
13
  type = execution_fields.delete(:type) || DEFAULT_TYPE
@@ -18,8 +16,8 @@ module Julewire
18
16
  summary_event = execution_fields.delete(:summary_event) || DEFAULT_SUMMARY_EVENT
19
17
  summary_severity = execution_fields.delete(:summary_severity)
20
18
  summary_source = execution_fields.delete(:summary_source) || configuration.source
21
- MessageContext.call(message, configuration: configuration, fields: fields) do
22
- Julewire::Core::Integration::Facade.with_execution(
19
+ MessageContext.call_fields(fields, configuration: configuration) do
20
+ Core::Integration::Facade.with_execution(
23
21
  type: type,
24
22
  id: id,
25
23
  emit_summary: emit_summary,
@@ -36,8 +34,7 @@ module Julewire
36
34
 
37
35
  def execution_fields(options)
38
36
  values = Core::Integration::Values::Shape
39
- fields = values.hash_or_empty(options)
40
- fields.empty? ? {} : fields
37
+ values.hash_or_empty(options).dup
41
38
  end
42
39
 
43
40
  def execution_id(fields)
@@ -43,7 +43,7 @@ module Julewire
43
43
  end
44
44
  end
45
45
 
46
- def initialize(configuration = Configuration.new, profile:)
46
+ def initialize(configuration, profile:)
47
47
  @configuration = configuration
48
48
  @profile = profile
49
49
  end
@@ -64,13 +64,12 @@ module Julewire
64
64
  source: @configuration.source,
65
65
  error: EventPayload.error(event),
66
66
  neutral: messaging_attributes(name, payload),
67
- attributes: event_attributes(name, payload)
67
+ attributes: event_attributes(payload)
68
68
  )
69
69
  end
70
- nil
71
70
  end
72
71
 
73
- def event_attributes(_name, payload)
72
+ def event_attributes(payload)
74
73
  Core.deep_compact_empty(@profile.event_prefix.to_sym => payload)
75
74
  end
76
75
 
@@ -10,7 +10,16 @@ module Julewire
10
10
  private_constant :PROFILE_CONSTANTS
11
11
 
12
12
  class << self
13
- def install!(monitor, profile:, configuration: Configuration.new)
13
+ def subscribe(monitor, event_name, component:, &)
14
+ return false unless monitor.respond_to?(:subscribe)
15
+
16
+ IntegrationHealth.with_failure_health(action: :subscribe, component:, event: event_name) do
17
+ monitor.subscribe(event_name, &)
18
+ true
19
+ end
20
+ end
21
+
22
+ def install!(monitor, profile:, configuration:)
14
23
  profile = monitor_listener_profile(profile)
15
24
  state = subscription_state(monitor, profile)
16
25
  listener = listener_for(state, configuration, profile)
@@ -23,21 +32,22 @@ module Julewire
23
32
  next if subscriptions.key?(event_name)
24
33
 
25
34
  callback = ->(event) { listener.emit(event_name, event) }
26
- subscriptions[event_name] = callback if subscribe_event(monitor, event_name, profile, &callback)
35
+ if subscribe(monitor, event_name, component: profile.component, &callback)
36
+ subscriptions[event_name] = callback
37
+ end
27
38
  end
28
39
  store_subscription_state(monitor, listener: listener, subscriptions: subscriptions, profile: profile)
29
- listener
30
40
  end
31
41
 
32
42
  private
33
43
 
34
44
  def monitor_listener_profile(profile)
35
- constant_name = PROFILE_CONSTANTS.fetch(profile) { return profile }
36
- MonitorListener.const_get(constant_name, false)
45
+ constant_name = PROFILE_CONSTANTS.fetch(profile)
46
+ MonitorListener.const_get(constant_name)
37
47
  end
38
48
 
39
49
  def listener_for(state, configuration, profile)
40
- listener = state && state[:listener]
50
+ listener = state&.fetch(:listener)
41
51
  if listener
42
52
  listener.configuration = configuration
43
53
  listener
@@ -49,7 +59,7 @@ module Julewire
49
59
  def subscriptions_for(state)
50
60
  return {} unless state
51
61
 
52
- state[:subscriptions].is_a?(Hash) ? state[:subscriptions].dup : {}
62
+ state.fetch(:subscriptions)
53
63
  end
54
64
 
55
65
  def subscription_state(monitor, profile)
@@ -59,7 +69,7 @@ module Julewire
59
69
  def store_subscription_state(monitor, listener:, subscriptions:, profile:)
60
70
  subscription_state_store(profile).store(
61
71
  monitor,
62
- { listener: listener, subscriptions: subscriptions.freeze }.freeze
72
+ { listener: listener, subscriptions: subscriptions }
63
73
  )
64
74
  end
65
75
 
@@ -96,31 +106,19 @@ module Julewire
96
106
  end
97
107
 
98
108
  def direct_available_events(monitor)
99
- return [] unless monitor.respond_to?(:available_events)
100
-
101
- Array(monitor.available_events)
102
- rescue StandardError
103
- []
109
+ Array(Core::Integration::Values::Read.value(monitor, :available_events))
104
110
  end
105
111
 
106
112
  def notification_bus_available_events(monitor)
107
- return [] unless monitor.respond_to?(:notifications_bus)
113
+ bus = Core::Integration::Values::Read.value(monitor, :notifications_bus)
108
114
 
109
- bus = monitor.notifications_bus
110
- return [] unless bus.respond_to?(:available_events)
111
-
112
- Array(bus.available_events)
113
- rescue StandardError
114
- []
115
+ Array(Core::Integration::Values::Read.value(bus, :available_events))
115
116
  end
116
117
 
117
118
  def listener_event_names(monitor)
118
- return [] unless monitor.respond_to?(:listeners)
119
+ listeners = Core::Integration::Values::Read.value(monitor, :listeners)
119
120
 
120
- listeners = monitor.listeners
121
121
  listeners.is_a?(Hash) ? listeners.keys : []
122
- rescue StandardError
123
- []
124
122
  end
125
123
 
126
124
  def all_events?(configured)
@@ -131,7 +129,7 @@ module Julewire
131
129
  return unless monitor.respond_to?(:unsubscribe)
132
130
 
133
131
  desired = desired_events.to_h { [it, true] }
134
- subscriptions.each_key.to_a.each do |event_name|
132
+ subscriptions.each_key do |event_name|
135
133
  next if desired.key?(event_name)
136
134
 
137
135
  callback = subscriptions.delete(event_name)
@@ -145,19 +143,9 @@ module Julewire
145
143
  component: profile.component,
146
144
  event: event_name
147
145
  ) do
148
- monitor.unsubscribe(callback || event_name)
149
- true
146
+ monitor.unsubscribe(callback)
150
147
  end
151
148
  end
152
-
153
- def subscribe_event(monitor, event_name, profile, &)
154
- return false unless monitor.respond_to?(:subscribe)
155
-
156
- IntegrationHealth.with_failure_health(action: :subscribe, component: profile.component, event: event_name) do
157
- monitor.subscribe(event_name, &)
158
- true
159
- end || false
160
- end
161
149
  end
162
150
  end
163
151
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Julewire
4
4
  module Karafka
5
- VERSION = "1.0.1"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -6,10 +6,8 @@ module Julewire
6
6
  MIDDLEWARE_INSTALL = Core::Integration::IvarState.new(:@julewire_karafka_waterdrop_middleware)
7
7
 
8
8
  class << self
9
- def install!(producer, configuration: Configuration.new)
10
- return false unless configuration.enabled?
11
-
12
- install_or_update_middleware(producer, configuration) if middleware_needed?(producer, configuration)
9
+ def install!(producer, configuration:)
10
+ install_middleware(producer, configuration) if middleware_needed?(producer, configuration)
13
11
  install_listener(producer, configuration) if configuration.producer_events?
14
12
  producer
15
13
  end
@@ -20,35 +18,32 @@ module Julewire
20
18
  configuration.propagation? || installed_middleware(producer)
21
19
  end
22
20
 
23
- def install_or_update_middleware(producer, configuration)
24
- install_middleware(producer, configuration)
25
- end
26
-
27
21
  def install_middleware(producer, configuration)
28
22
  existing = MIDDLEWARE_INSTALL.fetch(producer)
29
23
  if existing
30
24
  existing.configuration = configuration
31
- return existing
25
+ return
32
26
  end
33
27
 
34
- middleware = producer.middleware if producer.respond_to?(:middleware)
28
+ return unless producer.respond_to?(:middleware)
29
+
30
+ middleware = producer.middleware
35
31
  return unless middleware.respond_to?(:prepend)
36
32
 
37
33
  installed = WaterdropMiddleware.new(configuration: configuration)
38
34
  middleware.prepend(installed)
39
35
  MIDDLEWARE_INSTALL.store(producer, installed)
40
- installed
41
36
  rescue StandardError => e
42
37
  IntegrationHealth.record_failure(e, action: :install, component: :waterdrop_installer)
43
- nil
44
38
  end
45
39
 
46
40
  def install_listener(producer, configuration)
47
- monitor = producer.monitor if producer.respond_to?(:monitor)
48
- MonitorSubscription.install!(monitor, configuration: configuration, profile: :producer) if monitor
41
+ return unless producer.respond_to?(:monitor)
42
+
43
+ monitor = producer.monitor
44
+ MonitorSubscription.install!(monitor, configuration: configuration, profile: :producer)
49
45
  rescue StandardError => e
50
46
  IntegrationHealth.record_failure(e, action: :install, component: :waterdrop_installer)
51
- nil
52
47
  end
53
48
 
54
49
  def installed_middleware(producer)
@@ -19,7 +19,8 @@ module Julewire
19
19
  def inject_carrier(message)
20
20
  IntegrationHealth.with_failure_health(action: :carrier_inject, component: :waterdrop_middleware) do
21
21
  headers = headers_for(message)
22
- Julewire::Core::Propagation::Carrier.inject(
22
+
23
+ Core::Propagation::Carrier.inject(
23
24
  headers,
24
25
  key: @configuration.carrier_key,
25
26
  max_bytes: @configuration.carrier_max_bytes
@@ -32,8 +33,6 @@ module Julewire
32
33
  message.headers ||= {}
33
34
  elsif message.is_a?(Hash)
34
35
  message[:headers] ||= {}
35
- else
36
- {}
37
36
  end
38
37
  end
39
38
  end
@@ -19,11 +19,12 @@ module Julewire
19
19
  return false unless configuration.enabled?
20
20
 
21
21
  consumer_result = Installer.install!(app: app, monitor: monitor, configuration: configuration) if consumer
22
- producer_result = WaterdropInstaller.install!(producer, configuration: configuration) if producer
22
+ return consumer_result unless producer
23
23
 
24
- return InstallResult.new(consumer_result, producer_result) if consumer && producer
24
+ producer_result = WaterdropInstaller.install!(producer, configuration: configuration)
25
+ return InstallResult.new(consumer_result, producer_result) if consumer
25
26
 
26
- producer ? producer_result : consumer_result
27
+ producer_result
27
28
  end
28
29
 
29
30
  def inject!(message, configuration: config)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: julewire-karafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Grebennik
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 1.0.1
18
+ version: 1.1.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 1.0.1
25
+ version: 1.1.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: karafka
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  requirements: []
125
- rubygems_version: 4.0.14
125
+ rubygems_version: 4.0.16
126
126
  specification_version: 4
127
127
  summary: Karafka and WaterDrop integration for Julewire structured logging.
128
128
  test_files: []