julewire-core 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 +4 -4
- data/CHANGELOG.md +29 -0
- data/docs/advanced-configuration.md +2 -1
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +26 -4
- data/docs/contracts.md +25 -50
- data/docs/extensions-and-api.md +87 -88
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +24 -23
- data/docs/outputs-and-lifecycle.md +15 -8
- data/docs/records-and-data-policy.md +38 -7
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +56 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +16 -66
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +54 -17
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +22 -15
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +14 -17
- data/lib/julewire/core/execution/lineage.rb +26 -62
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +17 -22
- data/lib/julewire/core/execution/scope_fields.rb +4 -13
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +52 -16
- data/lib/julewire/core/fields/field_stack.rb +69 -109
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +35 -26
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +9 -14
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +28 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +73 -19
- data/lib/julewire/core/propagation.rb +18 -4
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +127 -258
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +30 -18
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +87 -72
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +3 -2
- data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +22 -49
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +157 -0
- data/lib/julewire/core/serialization/value_copy.rb +183 -109
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +7 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -6,8 +6,8 @@ module Julewire
|
|
|
6
6
|
# Fiber-local context stack used by the runtime facade. Use Julewire.context
|
|
7
7
|
# and Julewire.with_execution instead of reaching into this class directly.
|
|
8
8
|
class ContextStore # rubocop:disable Metrics/ClassLength
|
|
9
|
-
|
|
10
|
-
private_constant :
|
|
9
|
+
PropagationOverlay = Data.define(:execution, :link_executions)
|
|
10
|
+
private_constant :PropagationOverlay
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
13
|
def current
|
|
@@ -26,10 +26,7 @@ module Julewire
|
|
|
26
26
|
@scopes = []
|
|
27
27
|
@ambient_fields = Fields::StackSet.new
|
|
28
28
|
@execution_overlays = []
|
|
29
|
-
@execution_lineage_overlays = []
|
|
30
29
|
@propagation_execution_hash = nil
|
|
31
|
-
@propagation_scope_snapshot = nil
|
|
32
|
-
@linked_propagation_scope_snapshot = nil
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
def current_scope = @scopes.last
|
|
@@ -72,6 +69,7 @@ module Julewire
|
|
|
72
69
|
current_field_hash(:neutral)
|
|
73
70
|
end
|
|
74
71
|
|
|
72
|
+
# SectionProxy dispatches these dynamically from the public field readers.
|
|
75
73
|
def context_value(key, default:)
|
|
76
74
|
current_field_stack(:context).value_for(key, default: default)
|
|
77
75
|
end
|
|
@@ -84,26 +82,24 @@ module Julewire
|
|
|
84
82
|
current_field_stack(:attributes).value_for(key, default: default)
|
|
85
83
|
end
|
|
86
84
|
|
|
87
|
-
def add_context(fields =
|
|
85
|
+
def add_context(fields = nil, owned: false, **keyword_fields)
|
|
88
86
|
add_field(:context, field_input(fields, keyword_fields), owned: owned)
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
def add_carry(fields =
|
|
89
|
+
def add_carry(fields = nil, owned: false, **keyword_fields)
|
|
92
90
|
add_field(:carry, field_input(fields, keyword_fields), owned: owned)
|
|
93
91
|
end
|
|
94
92
|
|
|
95
|
-
def add_attributes(fields =
|
|
93
|
+
def add_attributes(fields = nil, owned: false, **keyword_fields)
|
|
96
94
|
add_field(:attributes, field_input(fields, keyword_fields), owned: owned)
|
|
97
95
|
end
|
|
98
96
|
|
|
99
|
-
def add_neutral(fields =
|
|
97
|
+
def add_neutral(fields = nil, owned: false, **keyword_fields)
|
|
100
98
|
add_field(:neutral, field_input(fields, keyword_fields), owned: owned)
|
|
101
99
|
end
|
|
102
100
|
|
|
103
101
|
def delete_carry(path)
|
|
104
102
|
path = Fields::Internal.normalize_path(path)
|
|
105
|
-
return if path.empty?
|
|
106
|
-
|
|
107
103
|
if current_scope
|
|
108
104
|
current_scope.delete_carry(path)
|
|
109
105
|
else
|
|
@@ -111,19 +107,19 @@ module Julewire
|
|
|
111
107
|
end
|
|
112
108
|
end
|
|
113
109
|
|
|
114
|
-
def with_context(fields =
|
|
110
|
+
def with_context(fields = nil, owned: false, **keyword_fields, &)
|
|
115
111
|
with_scope_or_ambient_overlay(:context, field_input(fields, keyword_fields), owned: owned, &)
|
|
116
112
|
end
|
|
117
113
|
|
|
118
|
-
def with_carry(fields =
|
|
114
|
+
def with_carry(fields = nil, owned: false, **keyword_fields, &)
|
|
119
115
|
with_scope_or_ambient_overlay(:carry, field_input(fields, keyword_fields), owned: owned, &)
|
|
120
116
|
end
|
|
121
117
|
|
|
122
|
-
def with_attributes(fields =
|
|
118
|
+
def with_attributes(fields = nil, owned: false, **keyword_fields, &)
|
|
123
119
|
with_scope_or_ambient_overlay(:attributes, field_input(fields, keyword_fields), owned: owned, &)
|
|
124
120
|
end
|
|
125
121
|
|
|
126
|
-
def with_neutral(fields =
|
|
122
|
+
def with_neutral(fields = nil, owned: false, **keyword_fields, &)
|
|
127
123
|
with_scope_or_ambient_overlay(:neutral, field_input(fields, keyword_fields), owned: owned, &)
|
|
128
124
|
end
|
|
129
125
|
|
|
@@ -139,26 +135,21 @@ module Julewire
|
|
|
139
135
|
end
|
|
140
136
|
end
|
|
141
137
|
|
|
142
|
-
def with_propagation(context
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
def with_propagation(context:, carry:, execution:, link_executions:, owned:, &)
|
|
139
|
+
execution = if owned
|
|
140
|
+
Serialization::DeepFreeze.validate_symbol_hash(execution)
|
|
141
|
+
else
|
|
142
|
+
Fields::FieldSet.deep_symbolize_keys(execution)
|
|
143
|
+
end
|
|
144
|
+
@execution_overlays.push(PropagationOverlay.new(execution: execution, link_executions: link_executions))
|
|
147
145
|
invalidate_propagation_cache!
|
|
148
146
|
|
|
149
147
|
begin
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
scope.with_context(context, &)
|
|
153
|
-
end
|
|
154
|
-
else
|
|
155
|
-
@ambient_fields.with(:carry, carry) do
|
|
156
|
-
@ambient_fields.with(:context, context, &)
|
|
157
|
-
end
|
|
148
|
+
with_carry(carry, owned: owned) do
|
|
149
|
+
with_context(context, owned: owned, &)
|
|
158
150
|
end
|
|
159
151
|
ensure
|
|
160
152
|
@execution_overlays.pop
|
|
161
|
-
@execution_lineage_overlays.pop
|
|
162
153
|
invalidate_propagation_cache!
|
|
163
154
|
end
|
|
164
155
|
end
|
|
@@ -209,12 +200,12 @@ module Julewire
|
|
|
209
200
|
Execution::Scope.new(
|
|
210
201
|
type: options.fetch(:type),
|
|
211
202
|
id: options[:id],
|
|
212
|
-
execution: merged_execution_hash(options.fetch(:execution,
|
|
203
|
+
execution: merged_execution_hash(options.fetch(:execution, {}), owned: options.fetch(:owned, false)),
|
|
213
204
|
execution_owned: true,
|
|
214
205
|
context: fields.stack(:context),
|
|
215
206
|
attributes: fields.stack(:attributes),
|
|
216
207
|
neutral: fields.stack(:neutral),
|
|
217
|
-
labels: options
|
|
208
|
+
labels: options[:labels],
|
|
218
209
|
carry: fields.stack(:carry),
|
|
219
210
|
parent: parent_scope || linked_propagation_scope_snapshot,
|
|
220
211
|
started_at: options[:started_at],
|
|
@@ -233,7 +224,7 @@ module Julewire
|
|
|
233
224
|
end
|
|
234
225
|
|
|
235
226
|
def add_scope_stack(stack_set, options, section:, key:)
|
|
236
|
-
value = options
|
|
227
|
+
value = options[key]
|
|
237
228
|
if options.fetch(:owned, false)
|
|
238
229
|
stack_set.add(section, value, owned: true)
|
|
239
230
|
else
|
|
@@ -241,37 +232,26 @@ module Julewire
|
|
|
241
232
|
end
|
|
242
233
|
end
|
|
243
234
|
|
|
244
|
-
def add_field(section, fields, owned:
|
|
235
|
+
def add_field(section, fields, owned:)
|
|
245
236
|
scope = current_scope
|
|
246
237
|
if scope
|
|
247
238
|
scope.add_field(section, fields, owned: owned)
|
|
248
|
-
elsif owned
|
|
249
|
-
@ambient_fields.add(section, fields, owned: true)
|
|
250
239
|
else
|
|
251
|
-
@ambient_fields.add(section, fields)
|
|
240
|
+
@ambient_fields.add(section, fields, owned: owned)
|
|
252
241
|
end
|
|
253
242
|
end
|
|
254
243
|
|
|
255
244
|
def field_input(fields, keyword_fields)
|
|
256
245
|
return fields if keyword_fields.empty?
|
|
257
|
-
return keyword_fields if empty_field_input?(fields)
|
|
258
|
-
|
|
259
|
-
fields.is_a?(Hash) ? fields.merge(keyword_fields) : fields
|
|
260
|
-
end
|
|
261
246
|
|
|
262
|
-
|
|
263
|
-
fields.nil? || (fields.respond_to?(:empty?) && fields.empty?)
|
|
247
|
+
Fields::FieldSet.coerce(fields, keyword_fields, invalid: :wrap)
|
|
264
248
|
end
|
|
265
249
|
|
|
266
|
-
def with_scope_or_ambient_overlay(section, fields, owned
|
|
250
|
+
def with_scope_or_ambient_overlay(section, fields, owned:, &)
|
|
267
251
|
scope = current_scope
|
|
268
252
|
return scope.with_field(section, fields, owned: owned, &) if scope
|
|
269
253
|
|
|
270
|
-
|
|
271
|
-
@ambient_fields.with(section, fields, owned: true, &)
|
|
272
|
-
else
|
|
273
|
-
@ambient_fields.with(section, fields, &)
|
|
274
|
-
end
|
|
254
|
+
@ambient_fields.with(section, fields, owned: owned, &)
|
|
275
255
|
end
|
|
276
256
|
|
|
277
257
|
def current_field_stack(section)
|
|
@@ -292,32 +272,31 @@ module Julewire
|
|
|
292
272
|
end
|
|
293
273
|
|
|
294
274
|
def execution_hash
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
Fields::FieldSet.deep_dup(propagation_execution_hash)
|
|
275
|
+
Fields::FieldSet.deep_dup_owned(propagation_execution_hash)
|
|
298
276
|
end
|
|
299
277
|
|
|
300
278
|
def propagation_execution_hash
|
|
301
|
-
@propagation_execution_hash ||= Fields::Internal.
|
|
302
|
-
|
|
303
|
-
|
|
279
|
+
@propagation_execution_hash ||= Fields::Internal.frozen_owned_copy(
|
|
280
|
+
@execution_overlays.reduce({}) do |memo, overlay|
|
|
281
|
+
Fields::Internal.merge_owned!(memo, overlay.execution)
|
|
282
|
+
end
|
|
283
|
+
)
|
|
304
284
|
end
|
|
305
285
|
|
|
306
286
|
def propagation_scope_snapshot
|
|
307
287
|
execution = propagation_execution_hash
|
|
308
288
|
return if execution.empty?
|
|
309
289
|
|
|
310
|
-
@propagation_scope_snapshot ||= Execution::ScopeSnapshot.new(execution: execution)
|
|
290
|
+
@propagation_scope_snapshot ||= Execution::ScopeSnapshot.new(execution: execution, owned: true)
|
|
311
291
|
end
|
|
312
292
|
|
|
313
293
|
def linked_propagation_scope_snapshot
|
|
314
|
-
|
|
315
|
-
return unless lineage
|
|
294
|
+
return unless linked_propagation?
|
|
316
295
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
296
|
+
@linked_propagation_scope_snapshot ||= Execution::ScopeSnapshot.new(
|
|
297
|
+
execution: propagation_execution_hash,
|
|
298
|
+
owned: true
|
|
299
|
+
)
|
|
321
300
|
end
|
|
322
301
|
|
|
323
302
|
def invalidate_propagation_cache!
|
|
@@ -326,56 +305,46 @@ module Julewire
|
|
|
326
305
|
@linked_propagation_scope_snapshot = nil
|
|
327
306
|
end
|
|
328
307
|
|
|
329
|
-
def
|
|
330
|
-
|
|
331
|
-
execution = @execution_overlays.fetch(index)
|
|
332
|
-
next if execution.empty?
|
|
333
|
-
|
|
334
|
-
return @execution_lineage_overlays.fetch(index)
|
|
335
|
-
end
|
|
336
|
-
nil
|
|
308
|
+
def linked_propagation?
|
|
309
|
+
@execution_overlays.reverse_each.find { |overlay| !overlay.execution.empty? }&.link_executions
|
|
337
310
|
end
|
|
338
311
|
|
|
339
|
-
def merged_execution_hash(execution)
|
|
340
|
-
|
|
341
|
-
return inherited unless execution.is_a?(Hash) && !execution.empty?
|
|
312
|
+
def merged_execution_hash(execution, owned:)
|
|
313
|
+
return Fields::FieldSet.merge!(inherited_execution_hash, execution) unless owned
|
|
342
314
|
|
|
343
|
-
|
|
315
|
+
Serialization::DeepFreeze.validate_symbol_hash(execution)
|
|
316
|
+
Fields::Internal.merge_owned!(inherited_execution_hash, execution)
|
|
344
317
|
end
|
|
345
318
|
|
|
346
319
|
def inherited_execution_hash
|
|
347
320
|
scope = current_scope
|
|
348
321
|
return execution_hash unless scope
|
|
349
322
|
|
|
350
|
-
|
|
351
|
-
return inherited if @execution_overlays.empty?
|
|
352
|
-
|
|
353
|
-
overlay = execution_hash
|
|
354
|
-
return inherited if overlay.empty?
|
|
355
|
-
|
|
356
|
-
Fields::FieldSet.merge!(inherited, overlay)
|
|
323
|
+
Fields::Internal.merge_owned!(scope.inheritable_execution_hash, execution_hash)
|
|
357
324
|
end
|
|
358
325
|
|
|
359
326
|
def finish_scope(scope, on_finish, on_finish_failure, active_exception: nil)
|
|
360
327
|
return unless on_finish
|
|
361
328
|
|
|
362
|
-
contain_finish_failure(on_finish_failure, active_exception
|
|
363
|
-
|
|
329
|
+
contain_finish_failure(on_finish_failure, active_exception, phase: :summary_finish) do
|
|
330
|
+
scope.finish_owned unless scope.finished?
|
|
331
|
+
end
|
|
332
|
+
contain_finish_failure(on_finish_failure, active_exception, phase: :summary_emit) { on_finish.call(scope) }
|
|
364
333
|
end
|
|
365
334
|
|
|
366
|
-
def contain_finish_failure(on_finish_failure, active_exception)
|
|
335
|
+
def contain_finish_failure(on_finish_failure, active_exception, phase:)
|
|
367
336
|
yield
|
|
368
337
|
rescue StandardError => e
|
|
369
|
-
report_finish_failure(on_finish_failure, e)
|
|
338
|
+
report_finish_failure(on_finish_failure, e, phase: phase)
|
|
370
339
|
rescue SystemStackError => e
|
|
371
340
|
# Preserve the app's active stack error during unwind.
|
|
372
341
|
raise unless active_exception
|
|
373
342
|
|
|
374
|
-
report_finish_failure(on_finish_failure, e)
|
|
343
|
+
report_finish_failure(on_finish_failure, e, phase: phase)
|
|
375
344
|
end
|
|
376
345
|
|
|
377
|
-
def report_finish_failure(on_finish_failure, error)
|
|
378
|
-
on_finish_failure
|
|
346
|
+
def report_finish_failure(on_finish_failure, error, phase:)
|
|
347
|
+
on_finish_failure.call(error, phase: phase)
|
|
379
348
|
rescue StandardError
|
|
380
349
|
nil
|
|
381
350
|
end
|
|
@@ -25,7 +25,7 @@ module Julewire
|
|
|
25
25
|
when :raise then raise "julewire punk chaos output failure"
|
|
26
26
|
when :reject then false
|
|
27
27
|
when :sleep
|
|
28
|
-
sleep(@sleep_seconds)
|
|
28
|
+
Kernel.sleep(@sleep_seconds)
|
|
29
29
|
@output.write(value)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -77,7 +77,7 @@ module Julewire
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def trigger?
|
|
80
|
-
@
|
|
80
|
+
@random.rand < @rate
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def chaos_mode
|
|
@@ -6,7 +6,7 @@ module Julewire
|
|
|
6
6
|
module Destinations
|
|
7
7
|
class Collection
|
|
8
8
|
def initialize(destinations, on_drop:, on_failure:)
|
|
9
|
-
@destinations = destinations.freeze
|
|
9
|
+
@destinations = destinations.dup.freeze
|
|
10
10
|
@on_drop = on_drop
|
|
11
11
|
@on_failure = on_failure
|
|
12
12
|
end
|
|
@@ -23,9 +23,9 @@ module Julewire
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def validate_destinations(destinations)
|
|
26
|
-
destinations.
|
|
26
|
+
destinations.each do |destination|
|
|
27
27
|
Registry.validate!(destination)
|
|
28
|
-
end
|
|
28
|
+
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -76,7 +76,7 @@ module Julewire
|
|
|
76
76
|
|
|
77
77
|
lifecycle_destinations(skip_resource_identities).each do |destination|
|
|
78
78
|
remaining_timeout = Scheduling::Deadline.remaining(deadline)
|
|
79
|
-
if attempted && deadline && remaining_timeout
|
|
79
|
+
if attempted && deadline && remaining_timeout.zero?
|
|
80
80
|
ok = false
|
|
81
81
|
break
|
|
82
82
|
end
|
|
@@ -96,7 +96,7 @@ module Julewire
|
|
|
96
96
|
def lifecycle_destinations(skip_resource_identities)
|
|
97
97
|
return @destinations unless skip_resource_identities
|
|
98
98
|
|
|
99
|
-
@destinations.reject {
|
|
99
|
+
@destinations.reject { skip_resource_identities.key?(resource_identity(it)) }
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def call_destination_after_fork(destination)
|
|
@@ -108,13 +108,6 @@ module Julewire
|
|
|
108
108
|
destination: destination_name(destination),
|
|
109
109
|
phase: :destination_lifecycle
|
|
110
110
|
)
|
|
111
|
-
nil
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def skip_lifecycle_destination?(destination, identities)
|
|
115
|
-
return false unless identities
|
|
116
|
-
|
|
117
|
-
identities.key?(resource_identity(destination))
|
|
118
111
|
end
|
|
119
112
|
|
|
120
113
|
def resource_identity(destination)
|
|
@@ -125,7 +118,7 @@ module Julewire
|
|
|
125
118
|
|
|
126
119
|
def emit_to_destination(destination, record)
|
|
127
120
|
result = destination.emit(record)
|
|
128
|
-
record_drop(:destination_rejected, destination, record) if result == false
|
|
121
|
+
record_drop(:destination_rejected, metadata: destination_metadata(destination, record)) if result == false
|
|
129
122
|
rescue StandardError => e
|
|
130
123
|
metadata = destination_metadata(destination, record)
|
|
131
124
|
notify_failure(
|
|
@@ -133,8 +126,7 @@ module Julewire
|
|
|
133
126
|
**metadata,
|
|
134
127
|
phase: :destination
|
|
135
128
|
)
|
|
136
|
-
record_drop(:destination_exception,
|
|
137
|
-
nil
|
|
129
|
+
record_drop(:destination_exception, metadata: metadata)
|
|
138
130
|
end
|
|
139
131
|
|
|
140
132
|
def destination_name(destination)
|
|
@@ -161,7 +153,7 @@ module Julewire
|
|
|
161
153
|
@on_failure.call(error, **metadata)
|
|
162
154
|
end
|
|
163
155
|
|
|
164
|
-
def record_drop(reason,
|
|
156
|
+
def record_drop(reason, metadata:)
|
|
165
157
|
@on_drop.call(reason, phase: :destination, **metadata)
|
|
166
158
|
end
|
|
167
159
|
|
|
@@ -43,7 +43,6 @@ module Julewire
|
|
|
43
43
|
on_drop: resolve(:on_drop, defaults),
|
|
44
44
|
on_failure: resolve(:on_failure, defaults),
|
|
45
45
|
output: output,
|
|
46
|
-
error_backtrace_lines: defaults.fetch(:error_backtrace_lines),
|
|
47
46
|
processors: resolve(:processors, defaults)
|
|
48
47
|
)
|
|
49
48
|
end
|
|
@@ -95,17 +94,10 @@ module Julewire
|
|
|
95
94
|
return defaults.fetch(key) if defaults.key?(key)
|
|
96
95
|
|
|
97
96
|
case key
|
|
98
|
-
when :
|
|
99
|
-
false
|
|
100
|
-
when :encoder, :formatter, :on_drop, :on_failure
|
|
97
|
+
when :encoder, :formatter
|
|
101
98
|
raise ArgumentError, "destination default #{key} is required"
|
|
102
99
|
when :max_record_bytes
|
|
103
100
|
DEFAULT_MAX_RECORD_BYTES
|
|
104
|
-
when :processors
|
|
105
|
-
[]
|
|
106
|
-
when :output
|
|
107
|
-
# No inherited output exists; build raises the required-output error.
|
|
108
|
-
nil
|
|
109
101
|
end
|
|
110
102
|
end
|
|
111
103
|
|
|
@@ -4,6 +4,8 @@ module Julewire
|
|
|
4
4
|
module Core
|
|
5
5
|
module Destinations
|
|
6
6
|
class Destination
|
|
7
|
+
include ProcessorHandling
|
|
8
|
+
|
|
7
9
|
COUNTER_KEYS = %i[
|
|
8
10
|
callback_error
|
|
9
11
|
encode_error
|
|
@@ -15,6 +17,7 @@ module Julewire
|
|
|
15
17
|
output_rejected
|
|
16
18
|
processor_dropped
|
|
17
19
|
processor_error
|
|
20
|
+
processor_invalid
|
|
18
21
|
received
|
|
19
22
|
record_too_large
|
|
20
23
|
].freeze
|
|
@@ -30,8 +33,7 @@ module Julewire
|
|
|
30
33
|
on_drop:,
|
|
31
34
|
on_failure:,
|
|
32
35
|
output:,
|
|
33
|
-
|
|
34
|
-
processors: []
|
|
36
|
+
processors: nil
|
|
35
37
|
)
|
|
36
38
|
@name = Destinations.normalize_name(name)
|
|
37
39
|
@formatter = validate_callable(formatter, name: :formatter)
|
|
@@ -43,7 +45,7 @@ module Julewire
|
|
|
43
45
|
raise ArgumentError, "destination #{@name.inspect} output is required" if output.nil?
|
|
44
46
|
|
|
45
47
|
@output = Sink.wrap(output, close_output: close_output)
|
|
46
|
-
@processor_chain = processor_chain(processors
|
|
48
|
+
@processor_chain = processor_chain(processors)
|
|
47
49
|
initialize_tracking
|
|
48
50
|
@write_step = build_write_step
|
|
49
51
|
end
|
|
@@ -57,10 +59,7 @@ module Julewire
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def emit_processed_record(record, degradation_marker:)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
clear_degradation_if_unchanged(degradation_marker)
|
|
63
|
-
nil
|
|
62
|
+
clear_degradation_if_unchanged(degradation_marker) if @write_step.call(record)
|
|
64
63
|
end
|
|
65
64
|
|
|
66
65
|
def flush(timeout: nil)
|
|
@@ -73,7 +72,7 @@ module Julewire
|
|
|
73
72
|
|
|
74
73
|
def after_fork!
|
|
75
74
|
initialize_tracking
|
|
76
|
-
@output.after_fork!
|
|
75
|
+
@output.after_fork!
|
|
77
76
|
self
|
|
78
77
|
rescue StandardError => e
|
|
79
78
|
notify_failure(
|
|
@@ -86,9 +85,7 @@ module Julewire
|
|
|
86
85
|
end
|
|
87
86
|
|
|
88
87
|
def resource_identity
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@output
|
|
88
|
+
@output.resource_identity
|
|
92
89
|
end
|
|
93
90
|
|
|
94
91
|
def health
|
|
@@ -146,7 +143,7 @@ module Julewire
|
|
|
146
143
|
|
|
147
144
|
def record_step_metadata(metadata)
|
|
148
145
|
record = metadata.delete(:record)
|
|
149
|
-
metadata[:record_metadata] = Records::Metadata.call(record)
|
|
146
|
+
metadata[:record_metadata] = Records::Metadata.call(record)
|
|
150
147
|
metadata
|
|
151
148
|
end
|
|
152
149
|
|
|
@@ -166,7 +163,7 @@ module Julewire
|
|
|
166
163
|
end
|
|
167
164
|
|
|
168
165
|
def record_loss(reason, metadata)
|
|
169
|
-
record_metadata = metadata.fetch(:record_metadata
|
|
166
|
+
record_metadata = metadata.fetch(:record_metadata)
|
|
170
167
|
@health.record_loss(
|
|
171
168
|
reason: reason,
|
|
172
169
|
counter: nil,
|
|
@@ -177,10 +174,6 @@ module Julewire
|
|
|
177
174
|
)
|
|
178
175
|
end
|
|
179
176
|
|
|
180
|
-
def clear_degradation
|
|
181
|
-
@health.clear_degradation
|
|
182
|
-
end
|
|
183
|
-
|
|
184
177
|
def clear_degradation_if_unchanged(marker)
|
|
185
178
|
@health.clear_degradation_if_unchanged(marker)
|
|
186
179
|
end
|
|
@@ -195,57 +188,16 @@ module Julewire
|
|
|
195
188
|
callback
|
|
196
189
|
end
|
|
197
190
|
|
|
198
|
-
def processor_chain(processors, error_backtrace_lines)
|
|
199
|
-
processors = processor_entries(processors)
|
|
200
|
-
return if processors.empty?
|
|
201
|
-
|
|
202
|
-
Processing::ProcessorChain.new(
|
|
203
|
-
processors: processors,
|
|
204
|
-
error_backtrace_lines: error_backtrace_lines,
|
|
205
|
-
on_error: method(:record_processor_error)
|
|
206
|
-
)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def processor_entries(value)
|
|
210
|
-
case value
|
|
211
|
-
when Processing::ProcessorRegistry
|
|
212
|
-
value.to_a
|
|
213
|
-
else
|
|
214
|
-
Processing::ProcessorRegistry.new(Array(value)).to_a
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def process_record(record)
|
|
219
|
-
return record unless @processor_chain
|
|
220
|
-
|
|
221
|
-
processed = @processor_chain.call(Records::Draft.from_record(record, freeze_sections: false))
|
|
222
|
-
if processed.equal?(Processing::ProcessorChain::DROP)
|
|
223
|
-
increment_counter(:processor_dropped)
|
|
224
|
-
nil
|
|
225
|
-
elsif processed.is_a?(Processing::ProcessorChain::ErrorResult)
|
|
226
|
-
processed.draft.to_record
|
|
227
|
-
else
|
|
228
|
-
processed.to_record
|
|
229
|
-
end
|
|
230
|
-
rescue StandardError => e
|
|
231
|
-
notify_failure(e, phase: :destination_processor, record_metadata: Records::Metadata.call(record))
|
|
232
|
-
nil
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def record_processor_error(error, record_metadata)
|
|
236
|
-
increment_counter(:processor_error)
|
|
237
|
-
notify_failure(error, phase: :destination_processor, record_metadata: record_metadata)
|
|
238
|
-
end
|
|
239
|
-
|
|
240
191
|
def call_output_lifecycle(method_name, timeout:)
|
|
241
192
|
Validation.validate_timeout!(timeout, name: :timeout)
|
|
242
193
|
call_output_lifecycle_safely(method_name, timeout)
|
|
243
194
|
end
|
|
244
195
|
|
|
245
|
-
def call_output_lifecycle_safely(method_name,
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
196
|
+
def call_output_lifecycle_safely(method_name, timeout)
|
|
197
|
+
# Sink.wrap centralizes timeout-aware lifecycle dispatch for every output.
|
|
198
|
+
degradation_marker = @health.degradation_marker
|
|
199
|
+
result = @output.public_send(method_name, timeout: timeout)
|
|
200
|
+
clear_degradation_if_unchanged(degradation_marker) if method_name == :flush && result
|
|
249
201
|
result
|
|
250
202
|
rescue StandardError => e
|
|
251
203
|
notify_failure(
|
|
@@ -258,9 +210,7 @@ module Julewire
|
|
|
258
210
|
end
|
|
259
211
|
|
|
260
212
|
def output_class_name
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
@output.class.name
|
|
213
|
+
@output.output_class_name
|
|
264
214
|
end
|
|
265
215
|
end
|
|
266
216
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Destinations
|
|
6
|
+
# @api internal
|
|
7
|
+
module ProcessorHandling
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def processor_chain(processors)
|
|
11
|
+
processors = processor_entries(processors)
|
|
12
|
+
return if processors.empty?
|
|
13
|
+
|
|
14
|
+
Processing::ProcessorChain.new(
|
|
15
|
+
processors: processors,
|
|
16
|
+
on_error: method(:record_processor_error),
|
|
17
|
+
on_invalid: method(:record_invalid_processor_result),
|
|
18
|
+
on_invalid_draft: method(:record_invalid_processor_draft)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def processor_entries(value)
|
|
23
|
+
case value
|
|
24
|
+
when Processing::ProcessorRegistry
|
|
25
|
+
value.to_a
|
|
26
|
+
else
|
|
27
|
+
Processing::ProcessorRegistry.new(Array(value)).to_a
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_record(record)
|
|
32
|
+
return record unless @processor_chain
|
|
33
|
+
|
|
34
|
+
processed = @processor_chain.call(Records::Draft.from_record(record))
|
|
35
|
+
if processed.equal?(Processing::ProcessorChain::DROP)
|
|
36
|
+
drop_processed_record
|
|
37
|
+
elsif processed.instance_of?(Processing::ProcessorChain::ErrorResult)
|
|
38
|
+
processed.draft.to_record
|
|
39
|
+
else
|
|
40
|
+
processed.to_record
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def drop_processed_record
|
|
45
|
+
increment_counter(:processor_dropped)
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def record_processor_error(error, record_metadata)
|
|
50
|
+
increment_counter(:processor_error)
|
|
51
|
+
notify_failure(error, phase: :destination_processor, record_metadata: record_metadata)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def record_invalid_processor_result(processor_name, result, record_metadata)
|
|
55
|
+
increment_counter(:processor_invalid)
|
|
56
|
+
error, metadata = Processing::InvalidResultFailure.build(
|
|
57
|
+
message: "destination processor returned unsupported result",
|
|
58
|
+
phase: :destination_processor_result,
|
|
59
|
+
processor_name: processor_name,
|
|
60
|
+
record_metadata: record_metadata,
|
|
61
|
+
result: result
|
|
62
|
+
)
|
|
63
|
+
notify_failure(error, **metadata)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def record_invalid_processor_draft(processor_name, error, record_metadata)
|
|
67
|
+
increment_counter(:processor_invalid)
|
|
68
|
+
notify_failure(
|
|
69
|
+
error,
|
|
70
|
+
phase: :destination_processor_data,
|
|
71
|
+
processor: processor_name,
|
|
72
|
+
record_metadata: record_metadata
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -49,7 +49,7 @@ module Julewire
|
|
|
49
49
|
def build(defaults:)
|
|
50
50
|
output_identities = {}.compare_by_identity
|
|
51
51
|
@definitions.map do |definition|
|
|
52
|
-
if definition.
|
|
52
|
+
if definition.instance_of?(Definition)
|
|
53
53
|
definition.build(defaults: defaults, output_identities: output_identities)
|
|
54
54
|
else
|
|
55
55
|
definition
|