julewire-active_job 1.0.0 → 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: a82446be20c0ec6d8ac39b204411837b4759cb57f94ddb9b2138173d16b5fc3d
4
- data.tar.gz: 949f9196d2e54d90248b66bce79f34d20bd4be6fbae26667febe548527829cd1
3
+ metadata.gz: 994a99590e4edc3673032beb690309bf1dd80b9008bd3ada44c7ee75468ad799
4
+ data.tar.gz: 96330738837417e0a150b60b9212ccfd1055264b45f7e5e6d3e51e7b379194be
5
5
  SHA512:
6
- metadata.gz: f0acd8d095c2b529f8bb865b3757bafa62a97e7997e5009d8a8960bd2b8c96e4ffbb2c3e7eb6345318f2150b0113f28e4236212571c0251eab74a89075dad694
7
- data.tar.gz: fcf079813dfde620139088138f2705db70ad6a0ab62aa8f2655b977a3b9ccbcd04c27790d9582aabc6928f211fc2d182dfb488261d99d3e006fa5164571d70d1
6
+ metadata.gz: ba90a55b73280d51badf2b3ac32a27277cc6175537215cd050324785046eee0bf0a983b0d5c4e05a40cbbe6b2aa86754d213de639caa3b0913fdef07029050fd
7
+ data.tar.gz: 2b8ccba07a60b83bb30d5615fb653a672c72ce36e663ac1bb4dd79bf8a5c68f1b83784ce388d740fac1d7d9e8fcd9b7159fc303f580cb31f33c0297fad218d79
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
9
+ ## 1.0.1 - 2026-06-25
10
+
11
+ - Default propagation carrier byte limits to 64 KiB and record health when
12
+ oversized or malformed job carriers are ignored on restore.
13
+ - Require julewire 1.0.1 internal gems.
14
+
3
15
  ## 1.0.0 - 2026-06-21
4
16
 
5
17
  - Initial release: ActiveJob execution summaries, structured events,
@@ -17,7 +17,7 @@ apps pass a `Configuration` instance to `install!`.
17
17
 
18
18
  | Option | Default | Purpose |
19
19
  | --- | --- | --- |
20
- | `carrier_max_bytes` | `nil` | Omit oversized carriers from serialized jobs. |
20
+ | `carrier_max_bytes` | `65_536` | Omit oversized carriers from serialized jobs. |
21
21
  | `silence_log_subscriber` | `true` | Detach Active Job default text subscriber output. |
22
22
 
23
23
  Example:
data/docs/propagation.md CHANGED
@@ -7,11 +7,15 @@ job execution scope starts.
7
7
  That lets upstream context flow into the job without emitting propagation-only
8
8
  data by default.
9
9
 
10
- Set `carrier_max_bytes` to omit oversized carriers from serialized job payloads.
11
- When omitted, the job still runs normally; it starts without upstream Julewire
12
- context.
10
+ `carrier_max_bytes` defaults to `65_536`. Oversized carriers are omitted from
11
+ serialized job payloads and ignored on restore. The job still runs normally; it
12
+ starts without upstream Julewire context.
13
13
 
14
14
  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"
39
- spec.add_dependency "julewire-rails_support", ">= 1.0"
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
@@ -13,7 +13,8 @@ module Julewire
13
13
  setting :silence_log_subscriber, default: true, predicate: true
14
14
  setting :propagation, default: true, predicate: true
15
15
  setting :carrier_key, default: Julewire::Core::Propagation::Carrier::DEFAULT_KEY
16
- setting :carrier_max_bytes, validate: byte_limit
16
+ setting :carrier_max_bytes, default: Julewire::Core::Propagation::Carrier::DEFAULT_MAX_BYTES,
17
+ validate: byte_limit
17
18
  setting :serialized_carrier_key, default: DEFAULT_SERIALIZED_CARRIER_KEY
18
19
  setting :source, default: "active_job"
19
20
  setting :summary_event, default: "job.completed"
@@ -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,45 +6,62 @@ 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
- Julewire::Core::Propagation::Carrier.restore(carrier, key: configuration.carrier_key) do
13
+ result = Core::Propagation::Carrier.extract_result(
14
+ carrier,
15
+ key: configuration.carrier_key,
16
+ max_bytes: configuration.carrier_max_bytes
17
+ )
18
+ record_carrier_restore_failure(result)
19
+
20
+ Core::Propagation.restore(result.envelope, owned: true) do
14
21
  perform_job(job, configuration, &)
15
22
  end
16
23
  end
17
24
 
18
25
  private
19
26
 
27
+ def record_carrier_restore_failure(result)
28
+ return unless result.failure?
29
+
30
+ IntegrationHealth.record_failure(
31
+ result.error,
32
+ action: :carrier_restore,
33
+ component: :job_execution,
34
+ status: result.status,
35
+ reason: result.reason
36
+ )
37
+ end
38
+
20
39
  def perform_job(job, configuration, &)
21
40
  fields = job_fields(job)
22
- Core::Integration::Facade.with_execution(**execution_options(job, configuration, fields)) do
41
+ Core::Integration::Facade.with_execution(**execution_options(configuration, fields)) do
23
42
  install_context(fields)
24
43
  perform_with_summary(&)
25
44
  end
26
45
  end
27
46
 
28
47
  def carrier_for(job)
29
- job.instance_variable_get(CARRIER_IVAR) || {}
48
+ job.instance_variable_get(CARRIER_IVAR)
30
49
  rescue StandardError
31
- {}
50
+ nil
32
51
  end
33
52
 
34
- def execution_options(job, configuration, fields)
35
- options = {
53
+ def execution_options(configuration, fields)
54
+ {
36
55
  type: :job,
37
- fields: { job_class: fields[:job_class] || job.class.name },
56
+ fields: { job_class: fields.fetch(:job_class) },
38
57
  attributes: attributes_for(fields),
39
58
  neutral: neutral_for(fields),
40
59
  inherit_attributes: false,
41
60
  summary_event: configuration.summary_event,
42
61
  summary_severity: configuration.summary_severity,
43
- summary_source: configuration.source
62
+ summary_source: configuration.source,
63
+ id: fields[:job_id]
44
64
  }
45
- job_id = fields[:job_id]
46
- options[:id] = job_id if job_id
47
- options
48
65
  end
49
66
 
50
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.0"
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.0
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'
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'
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'
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'
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: []