julewire-active_job 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: 21ef818e5da1febb4f3f612cd025a0d1c1d1552ffeb6bd118739688007d6fb76
4
- data.tar.gz: 253e29b031dd05980d2a5b314ef80fdcd65b597b3c329dc874e425ab950253e2
3
+ metadata.gz: 994a99590e4edc3673032beb690309bf1dd80b9008bd3ada44c7ee75468ad799
4
+ data.tar.gz: 96330738837417e0a150b60b9212ccfd1055264b45f7e5e6d3e51e7b379194be
5
5
  SHA512:
6
- metadata.gz: 7709ff28c7e85d54d934446bb702f591d8039c7aaa6b792bdbadaa53ae2a36f906f5306b34ab566f517611d2245e84ace45710cc0693d559856718614e393e59
7
- data.tar.gz: 0726b22af8206e3238307792143aea2072986f3a9704090bd8cadb4e910d86a331c06970dff8bffa3bc14ffbd7d5ceb46300d47f564d52c2833f99e8a2ebbf05
6
+ metadata.gz: ba90a55b73280d51badf2b3ac32a27277cc6175537215cd050324785046eee0bf0a983b0d5c4e05a40cbbe6b2aa86754d213de639caa3b0913fdef07029050fd
7
+ data.tar.gz: 2b8ccba07a60b83bb30d5615fb653a672c72ce36e663ac1bb4dd79bf8a5c68f1b83784ce388d740fac1d7d9e8fcd9b7159fc303f580cb31f33c0297fad218d79
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 1.1.0 - 2026-07-20
4
+
5
+ - Restore propagated context through owned carrier envelopes and keep job
6
+ status summaries on Active Job's normal `StandardError` path.
7
+ - Require Julewire 1.1.0 internal gems.
8
+
3
9
  ## 1.0.1 - 2026-06-25
4
10
 
5
11
  - Default propagation carrier byte limits to 64 KiB and record health when
data/docs/propagation.md CHANGED
@@ -15,3 +15,7 @@ Generic job metadata such as class, id, queue, priority, execution count,
15
15
  timestamps, and status is emitted in the record's `neutral` section as `job.*`
16
16
  formatter-coordination fields. Full Active Job metadata, including framework-
17
17
  specific status and exception fields, is emitted under `attributes.active_job`.
18
+
19
+ Status classification follows Active Job's normal `StandardError` path. Fatal
20
+ Ruby exceptions such as `SystemExit` or `NoMemoryError` are not converted into
21
+ job status metadata; they keep Ruby's process-level semantics.
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.require_paths = ["lib"]
36
36
 
37
37
  spec.add_dependency "activejob", ">= 8.1"
38
- spec.add_dependency "julewire-core", ">= 1.0.1"
39
- spec.add_dependency "julewire-rails_support", ">= 1.0.1"
38
+ spec.add_dependency "julewire-core", ">= 1.1.0"
39
+ spec.add_dependency "julewire-rails_support", ">= 1.1.0"
40
40
  spec.add_dependency "zeitwerk", ">= 2.8.1"
41
41
  end
@@ -9,17 +9,13 @@ module Julewire
9
9
  def install!(base: nil, event_reporter: nil, configuration: Configuration.new)
10
10
  return unless configuration.enabled?
11
11
 
12
- Julewire::ActiveJob.config = configuration
12
+ ActiveJob.config = configuration
13
13
  base ||= active_job_base
14
14
  raise Error, "ActiveJob::Base is not available" unless base
15
15
 
16
16
  install_serialization(base, configuration)
17
17
  install_execution_callback(base, configuration)
18
- if configuration.structured_events?
19
- Subscribers::Event.install!(configuration, event_reporter: event_reporter)
20
- else
21
- Subscribers::Event.reset!
22
- end
18
+ Subscribers::Event.install!(configuration, event_reporter: event_reporter)
23
19
  LogSubscriberSilencer.silence! if configuration.silence_log_subscriber?
24
20
  base
25
21
  end
@@ -33,24 +29,15 @@ module Julewire
33
29
 
34
30
  def install_serialization(base, configuration)
35
31
  install_serialization_configuration(base, configuration)
36
- return if base < JobSerialization
37
32
 
38
33
  base.prepend(JobSerialization)
39
34
  end
40
35
 
41
36
  def install_serialization_configuration(base, configuration)
42
- if base.respond_to?(:class_attribute)
43
- unless base.respond_to?(JobSerialization::CONFIGURATION_METHOD)
44
- base.class_attribute(
45
- JobSerialization::CONFIGURATION_METHOD,
46
- instance_accessor: false,
47
- instance_predicate: false
48
- )
49
- end
50
- base.public_send("#{JobSerialization::CONFIGURATION_METHOD}=", configuration)
51
- else
52
- base.instance_variable_set(JobSerialization::CONFIGURATION_IVAR, configuration)
37
+ if base.singleton_methods(false).include?(JobSerialization::CONFIGURATION_METHOD)
38
+ base.singleton_class.remove_method(JobSerialization::CONFIGURATION_METHOD)
53
39
  end
40
+ base.define_singleton_method(JobSerialization::CONFIGURATION_METHOD) { configuration }
54
41
  end
55
42
 
56
43
  def install_execution_callback(base, configuration)
@@ -59,7 +46,7 @@ module Julewire
59
46
  installed = EXECUTION_INSTALL.fetch(base)
60
47
  if installed
61
48
  installed.configuration = configuration
62
- return installed
49
+ return
63
50
  end
64
51
 
65
52
  callback = ExecutionCallback.new(configuration)
@@ -79,7 +66,7 @@ module Julewire
79
66
  attr_writer :configuration
80
67
 
81
68
  def call(job, &)
82
- Julewire::ActiveJob::JobExecution.call(job, configuration: @configuration, &)
69
+ JobExecution.call(job, configuration: @configuration, &)
83
70
  end
84
71
  end
85
72
  private_constant :ExecutionCallback
@@ -6,18 +6,18 @@ module Julewire
6
6
  module ActiveJob
7
7
  module JobExecution
8
8
  class << self
9
- def call(job, configuration: Configuration.new, &)
9
+ def call(job, configuration:, &)
10
10
  carrier = carrier_for(job)
11
11
  return perform_job(job, configuration, &) unless configuration.propagation?
12
12
 
13
- result = Julewire::Core::Propagation::Carrier.extract_result(
13
+ result = Core::Propagation::Carrier.extract_result(
14
14
  carrier,
15
15
  key: configuration.carrier_key,
16
16
  max_bytes: configuration.carrier_max_bytes
17
17
  )
18
18
  record_carrier_restore_failure(result)
19
19
 
20
- Julewire::Core::Propagation.restore(result.envelope, owned: true) do
20
+ Core::Propagation.restore(result.envelope, owned: true) do
21
21
  perform_job(job, configuration, &)
22
22
  end
23
23
  end
@@ -38,32 +38,30 @@ module Julewire
38
38
 
39
39
  def perform_job(job, configuration, &)
40
40
  fields = job_fields(job)
41
- Core::Integration::Facade.with_execution(**execution_options(job, configuration, fields)) do
41
+ Core::Integration::Facade.with_execution(**execution_options(configuration, fields)) do
42
42
  install_context(fields)
43
43
  perform_with_summary(&)
44
44
  end
45
45
  end
46
46
 
47
47
  def carrier_for(job)
48
- job.instance_variable_get(CARRIER_IVAR) || {}
48
+ job.instance_variable_get(CARRIER_IVAR)
49
49
  rescue StandardError
50
- {}
50
+ nil
51
51
  end
52
52
 
53
- def execution_options(job, configuration, fields)
54
- options = {
53
+ def execution_options(configuration, fields)
54
+ {
55
55
  type: :job,
56
- fields: { job_class: fields[:job_class] || job.class.name },
56
+ fields: { job_class: fields.fetch(:job_class) },
57
57
  attributes: attributes_for(fields),
58
58
  neutral: neutral_for(fields),
59
59
  inherit_attributes: false,
60
60
  summary_event: configuration.summary_event,
61
61
  summary_severity: configuration.summary_severity,
62
- summary_source: configuration.source
62
+ summary_source: configuration.source,
63
+ id: fields[:job_id]
63
64
  }
64
- job_id = fields[:job_id]
65
- options[:id] = job_id if job_id
66
- options
67
65
  end
68
66
 
69
67
  def install_context(fields)
@@ -3,7 +3,6 @@
3
3
  module Julewire
4
4
  module ActiveJob
5
5
  module JobSerialization
6
- CONFIGURATION_IVAR = :@julewire_active_job_configuration
7
6
  CONFIGURATION_METHOD = :julewire_active_job_configuration
8
7
 
9
8
  def serialize
@@ -23,16 +22,12 @@ module Julewire
23
22
  configuration = julewire_active_job_configuration
24
23
  return unless configuration.propagation?
25
24
 
26
- carrier = Julewire::Core::Propagation::Carrier.inject(
27
- {},
28
- key: configuration.carrier_key,
29
- max_bytes: configuration.carrier_max_bytes
30
- )
25
+ carrier = Core::Propagation::Carrier.inject(key: configuration.carrier_key,
26
+ max_bytes: configuration.carrier_max_bytes)
31
27
  return unless carrier
32
28
 
33
- value = carrier[configuration.carrier_key.to_s]
34
- job_data[configuration.serialized_carrier_key] = value if value
35
- IntegrationHealth.record_success(action: :carrier_inject, component: :job_serialization)
29
+ job_data[configuration.serialized_carrier_key] = carrier.fetch(configuration.carrier_key.to_s)
30
+ IntegrationHealth.record_success
36
31
  rescue StandardError => e
37
32
  IntegrationHealth.record_failure(e, action: :carrier_inject, component: :job_serialization)
38
33
  end
@@ -46,26 +41,16 @@ module Julewire
46
41
 
47
42
  value = job_data[configuration.serialized_carrier_key]
48
43
  instance_variable_set(CARRIER_IVAR, value ? { configuration.carrier_key => value } : {})
49
- IntegrationHealth.record_success(action: :carrier_extract, component: :job_serialization)
44
+ IntegrationHealth.record_success
50
45
  rescue StandardError => e
51
46
  IntegrationHealth.record_failure(e, action: :carrier_extract, component: :job_serialization)
52
47
  instance_variable_set(CARRIER_IVAR, {})
53
48
  end
54
49
 
55
50
  def julewire_active_job_configuration
56
- if self.class.respond_to?(CONFIGURATION_METHOD)
57
- configuration = self.class.public_send(CONFIGURATION_METHOD)
58
- return configuration if configuration
59
- end
60
-
61
- self.class.ancestors.each do |ancestor|
62
- next unless ancestor.instance_variable_defined?(CONFIGURATION_IVAR)
63
-
64
- return ancestor.instance_variable_get(CONFIGURATION_IVAR)
65
- end
66
- Julewire::ActiveJob.config
51
+ self.class.public_send(CONFIGURATION_METHOD)
67
52
  rescue StandardError
68
- Julewire::ActiveJob.config
53
+ ActiveJob.config
69
54
  end
70
55
  end
71
56
  end
@@ -6,17 +6,8 @@ module Julewire
6
6
  class << self
7
7
  def silence!
8
8
  Core::Integration::Lifecycle.require_optional("active_job/log_subscriber")
9
- subscriber_class = active_job_log_subscriber
10
- return unless subscriber_class
11
-
12
- subscriber_class.detach_from(:active_job) if subscriber_class.respond_to?(:detach_from)
13
- Julewire::RailsSupport::EventReporter.unsubscribe_log_subscriber(subscriber_class)
14
- end
15
-
16
- private
17
-
18
- def active_job_log_subscriber
19
- ::ActiveJob::LogSubscriber if defined?(::ActiveJob::LogSubscriber)
9
+ subscriber_class = ::ActiveJob::LogSubscriber if defined?(::ActiveJob::LogSubscriber)
10
+ Julewire::RailsSupport::LogSubscribers.detach(subscriber_class, :active_job)
20
11
  end
21
12
  end
22
13
  end
@@ -14,7 +14,7 @@ module Julewire
14
14
  return unless settings.enabled?
15
15
 
16
16
  ActiveSupport.on_load(:active_job) do
17
- Julewire::ActiveJob.install!(base: self, configuration: settings)
17
+ ActiveJob.install!(base: self, configuration: settings)
18
18
  end
19
19
  end
20
20
  end
@@ -21,11 +21,11 @@ module Julewire
21
21
  return reset! unless configuration.structured_events?
22
22
 
23
23
  Core::Integration::Lifecycle.require_optional(STRUCTURED_EVENT_FILE)
24
- reporter = event_reporter || Julewire::RailsSupport::EventReporter.default
25
- return unless Julewire::RailsSupport::EventReporter.subscribable?(reporter)
24
+ reporter = event_reporter || RailsSupport::EventReporter.default
25
+ return unless RailsSupport::EventReporter.subscribable?(reporter)
26
26
 
27
27
  install_subscriber(configuration, enabled: true) do |subscriber|
28
- Julewire::RailsSupport::EventReporter.subscribe(reporter, subscriber) { subscriber.accept?(it) }
28
+ RailsSupport::EventReporter.subscribe(reporter, subscriber) { subscriber.accept?(it) }
29
29
  end
30
30
  end
31
31
  end
@@ -45,7 +45,7 @@ module Julewire
45
45
  def emit_event(event)
46
46
  name = event[:name].to_s
47
47
  record = record_for(event, name)
48
- enrich_continuation_summary(name, record.dig(:attributes, :active_job) || {})
48
+ enrich_continuation_summary(name, record.fetch(:attributes).fetch(:active_job))
49
49
  Core::Integration::Facade.emit(record)
50
50
  end
51
51
 
@@ -60,13 +60,13 @@ module Julewire
60
60
  def base_record(event, name, payload)
61
61
  values = Core::Integration::Values::Shape
62
62
  record = {
63
- severity: severity_for(name, payload),
64
63
  event: name,
65
64
  logger: "ActiveJob.event",
66
65
  context: values.hash_or_empty(event[:context]),
67
66
  attributes: attributes_for(event, payload),
68
67
  neutral: neutral_for(event, payload)
69
68
  }
69
+ values.append_field(record, :severity, severity_for(name, payload))
70
70
  values.append_field(record, :timestamp, values.timestamp(event[:timestamp]))
71
71
  values.append_field(record, :source, @configuration.source)
72
72
  record
@@ -81,22 +81,17 @@ module Julewire
81
81
 
82
82
  def neutral_for(event, payload)
83
83
  values = Core::Integration::Values::Shape
84
- Core::Fields::FieldSet.merge!(
84
+ Core::Fields::FieldSet.merge(
85
85
  JobAttributes.call(payload),
86
86
  values.source_location_attributes(event[:source_location])
87
87
  )
88
88
  end
89
89
 
90
90
  def severity_for(name, payload)
91
- return :error if ERROR_EVENTS.include?(name)
92
- return :error if exception_payload?(payload)
93
-
94
- :info
91
+ :error if ERROR_EVENTS.include?(name) || exception_payload?(payload)
95
92
  end
96
93
 
97
94
  def exception_payload?(payload)
98
- return false unless payload.is_a?(Hash)
99
-
100
95
  payload.key?(:exception_class) ||
101
96
  payload.key?(:exception_message) ||
102
97
  payload.key?(:exception_backtrace)
@@ -111,8 +106,6 @@ module Julewire
111
106
  end
112
107
 
113
108
  def enrich_continuation_summary(name, payload)
114
- return unless Core::Integration::Facade.summary_active?
115
-
116
109
  case name
117
110
  when "active_job.step_started"
118
111
  increment_summary(:continuation_steps_started)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Julewire
4
4
  module ActiveJob
5
- VERSION = "1.0.1"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -23,10 +23,6 @@ module Julewire
23
23
  def perform(job, &)
24
24
  JobExecution.call(job, configuration: config, &)
25
25
  end
26
-
27
- def load_railtie_if_rails!
28
- Railtie if defined?(::Rails::Railtie)
29
- end
30
26
  end
31
27
  end
32
28
 
@@ -34,5 +30,5 @@ module Julewire
34
30
  # Rails-only autoload, skipped when non-Rails processes eager load the gem.
35
31
  loader.do_not_eager_load("#{__dir__}/active_job/railtie.rb")
36
32
  loader.setup
37
- Julewire::ActiveJob.load_railtie_if_rails!
33
+ Julewire::ActiveJob::Railtie if defined?(::Rails::Railtie)
38
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: julewire-active_job
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
@@ -29,28 +29,28 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.0.1
32
+ version: 1.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 1.0.1
39
+ version: 1.1.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: julewire-rails_support
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 1.0.1
46
+ version: 1.1.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 1.0.1
53
+ version: 1.1.0
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: zeitwerk
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 4.0.14
119
+ rubygems_version: 4.0.16
120
120
  specification_version: 4
121
121
  summary: Active Job integration for Julewire structured logging.
122
122
  test_files: []