julewire-core 1.0.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 +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/docs/advanced-configuration.md +66 -0
- data/docs/attribute-keys.md +74 -0
- data/docs/configuration.md +327 -0
- data/docs/context-and-propagation.md +353 -0
- data/docs/contracts.md +211 -0
- data/docs/development.md +49 -0
- data/docs/extensions-and-api.md +567 -0
- data/docs/health-schema.md +104 -0
- data/docs/instrumentation-cheatsheet.md +29 -0
- data/docs/internals.md +135 -0
- data/docs/outputs-and-lifecycle.md +206 -0
- data/docs/quickstart.md +133 -0
- data/docs/record-sources.md +17 -0
- data/docs/records-and-data-policy.md +230 -0
- data/docs/security-and-wire.md +45 -0
- data/docs/tail.md +91 -0
- data/exe/julewire +6 -0
- data/julewire-core.gemspec +41 -0
- data/lib/julewire/core/cli/doctor.rb +143 -0
- data/lib/julewire/core/cli/line_helpers.rb +77 -0
- data/lib/julewire/core/cli/log_formats/console_text.rb +25 -0
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +46 -0
- data/lib/julewire/core/cli/log_formats/core_json_encoder.rb +21 -0
- data/lib/julewire/core/cli/log_formats/record_decoder.rb +39 -0
- data/lib/julewire/core/cli/log_formats.rb +123 -0
- data/lib/julewire/core/cli/tail.rb +153 -0
- data/lib/julewire/core/cli/transcode.rb +105 -0
- data/lib/julewire/core/cli.rb +73 -0
- data/lib/julewire/core/configuration.rb +99 -0
- data/lib/julewire/core/context_store.rb +384 -0
- data/lib/julewire/core/destinations/chaos_output.rb +91 -0
- data/lib/julewire/core/destinations/collection.rb +177 -0
- data/lib/julewire/core/destinations/definition.rb +125 -0
- data/lib/julewire/core/destinations/destination.rb +268 -0
- data/lib/julewire/core/destinations/registry.rb +81 -0
- data/lib/julewire/core/destinations/sink.rb +35 -0
- data/lib/julewire/core/destinations/synchronized_output.rb +57 -0
- data/lib/julewire/core/destinations/tail_sampling.rb +321 -0
- data/lib/julewire/core/destinations/write_step.rb +119 -0
- data/lib/julewire/core/destinations.rb +33 -0
- data/lib/julewire/core/diagnostics/callback_notifier.rb +63 -0
- data/lib/julewire/core/diagnostics/doctor.rb +114 -0
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +39 -0
- data/lib/julewire/core/diagnostics/health.rb +144 -0
- data/lib/julewire/core/diagnostics/integration_health_store.rb +64 -0
- data/lib/julewire/core/diagnostics/internal_records.rb +61 -0
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +112 -0
- data/lib/julewire/core/diagnostics/meta_observer.rb +161 -0
- data/lib/julewire/core/diagnostics/process_integration_health.rb +26 -0
- data/lib/julewire/core/diagnostics/tail/renderer.rb +36 -0
- data/lib/julewire/core/diagnostics/tail.rb +168 -0
- data/lib/julewire/core/diagnostics.rb +8 -0
- data/lib/julewire/core/error.rb +7 -0
- data/lib/julewire/core/execution/boundary.rb +106 -0
- data/lib/julewire/core/execution/handle.rb +77 -0
- data/lib/julewire/core/execution/lineage.rb +192 -0
- data/lib/julewire/core/execution/measurement_handle.rb +28 -0
- data/lib/julewire/core/execution/no_current_error.rb +9 -0
- data/lib/julewire/core/execution/scope.rb +246 -0
- data/lib/julewire/core/execution/scope_fields.rb +76 -0
- data/lib/julewire/core/execution/scope_identity.rb +71 -0
- data/lib/julewire/core/execution/scope_snapshot.rb +92 -0
- data/lib/julewire/core/execution/summary_state.rb +206 -0
- data/lib/julewire/core/execution/view.rb +56 -0
- data/lib/julewire/core/facade_methods.rb +181 -0
- data/lib/julewire/core/fields/attribute_keys.rb +54 -0
- data/lib/julewire/core/fields/attributes_proxy.rb +11 -0
- data/lib/julewire/core/fields/bags.rb +123 -0
- data/lib/julewire/core/fields/carry_proxy.rb +22 -0
- data/lib/julewire/core/fields/context_proxy.rb +11 -0
- data/lib/julewire/core/fields/field_set.rb +78 -0
- data/lib/julewire/core/fields/field_stack.rb +269 -0
- data/lib/julewire/core/fields/internal/deletion.rb +68 -0
- data/lib/julewire/core/fields/internal.rb +87 -0
- data/lib/julewire/core/fields/lookup.rb +35 -0
- data/lib/julewire/core/fields/section_proxy.rb +88 -0
- data/lib/julewire/core/fields/stack_set.rb +69 -0
- data/lib/julewire/core/fields/static_labels.rb +43 -0
- data/lib/julewire/core/fields/summary_proxy.rb +62 -0
- data/lib/julewire/core/integration/configurable.rb +52 -0
- data/lib/julewire/core/integration/destination_health.rb +43 -0
- data/lib/julewire/core/integration/event_subscriber.rb +62 -0
- data/lib/julewire/core/integration/facade.rb +131 -0
- data/lib/julewire/core/integration/fork_hooks.rb +79 -0
- data/lib/julewire/core/integration/health.rb +41 -0
- data/lib/julewire/core/integration/ivar_state.rb +38 -0
- data/lib/julewire/core/integration/lifecycle.rb +22 -0
- data/lib/julewire/core/integration/scoped.rb +34 -0
- data/lib/julewire/core/integration/settings.rb +92 -0
- data/lib/julewire/core/integration/subscriber_install.rb +39 -0
- data/lib/julewire/core/integration/subscription.rb +29 -0
- data/lib/julewire/core/integration/values.rb +192 -0
- data/lib/julewire/core/lifecycle_error.rb +7 -0
- data/lib/julewire/core/local_storage.rb +91 -0
- data/lib/julewire/core/processing/level_threshold.rb +53 -0
- data/lib/julewire/core/processing/match.rb +74 -0
- data/lib/julewire/core/processing/pipeline.rb +360 -0
- data/lib/julewire/core/processing/processor_chain.rb +69 -0
- data/lib/julewire/core/processing/processor_registry.rb +115 -0
- data/lib/julewire/core/processing/processor_wrapper.rb +44 -0
- data/lib/julewire/core/processing/record_field_transform.rb +124 -0
- data/lib/julewire/core/processing/sampling.rb +109 -0
- data/lib/julewire/core/processing.rb +41 -0
- data/lib/julewire/core/propagation/carrier.rb +93 -0
- data/lib/julewire/core/propagation.rb +50 -0
- data/lib/julewire/core/records/console_formatter.rb +24 -0
- data/lib/julewire/core/records/deconstruct.rb +19 -0
- data/lib/julewire/core/records/display_message.rb +166 -0
- data/lib/julewire/core/records/draft.rb +576 -0
- data/lib/julewire/core/records/formatter.rb +14 -0
- data/lib/julewire/core/records/lazy_emit_input.rb +99 -0
- data/lib/julewire/core/records/metadata.rb +23 -0
- data/lib/julewire/core/records/public_projection.rb +51 -0
- data/lib/julewire/core/records/raw_input.rb +41 -0
- data/lib/julewire/core/records/record.rb +175 -0
- data/lib/julewire/core/records/severity.rb +44 -0
- data/lib/julewire/core/runtime.rb +515 -0
- data/lib/julewire/core/runtime_locator.rb +20 -0
- data/lib/julewire/core/runtime_registry.rb +48 -0
- data/lib/julewire/core/runtime_state.rb +39 -0
- data/lib/julewire/core/scheduling/deadline.rb +24 -0
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +207 -0
- data/lib/julewire/core/scheduling/shared_scheduler.rb +48 -0
- data/lib/julewire/core/sentinel.rb +18 -0
- data/lib/julewire/core/serialization/backtrace_limiter.rb +50 -0
- data/lib/julewire/core/serialization/bounded_transform.rb +55 -0
- data/lib/julewire/core/serialization/bounded_traversal.rb +274 -0
- data/lib/julewire/core/serialization/deep_compact_empty.rb +67 -0
- data/lib/julewire/core/serialization/deep_freeze.rb +63 -0
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +40 -0
- data/lib/julewire/core/serialization/exception_shape.rb +88 -0
- data/lib/julewire/core/serialization/json_encoder.rb +69 -0
- data/lib/julewire/core/serialization/serializer.rb +233 -0
- data/lib/julewire/core/serialization/serializer_pool.rb +21 -0
- data/lib/julewire/core/serialization/text_encoder.rb +147 -0
- data/lib/julewire/core/serialization/value_copy.rb +209 -0
- data/lib/julewire/core/serialization/value_traversal.rb +150 -0
- data/lib/julewire/core/testing/chaos/catalog.rb +72 -0
- data/lib/julewire/core/testing/chaos/core_runtime.rb +120 -0
- data/lib/julewire/core/testing/chaos/destination.rb +55 -0
- data/lib/julewire/core/testing/chaos/emitter.rb +20 -0
- data/lib/julewire/core/testing/chaos/raising_output.rb +42 -0
- data/lib/julewire/core/testing/chaos.rb +80 -0
- data/lib/julewire/core/testing/contracts/component.rb +162 -0
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +59 -0
- data/lib/julewire/core/testing/contracts/integration.rb +166 -0
- data/lib/julewire/core/testing/contracts/integration_fields.rb +36 -0
- data/lib/julewire/core/testing/contracts/record_draft.rb +37 -0
- data/lib/julewire/core/testing/contracts/runtime.rb +178 -0
- data/lib/julewire/core/testing/contracts/wire.rb +60 -0
- data/lib/julewire/core/testing/contracts.rb +24 -0
- data/lib/julewire/core/testing/coverage.rb +58 -0
- data/lib/julewire/core/testing/test_reports.rb +78 -0
- data/lib/julewire/core/testing.rb +122 -0
- data/lib/julewire/core/validation.rb +69 -0
- data/lib/julewire/core/version.rb +7 -0
- data/lib/julewire/core.rb +80 -0
- data/lib/julewire/error.rb +5 -0
- data/lib/julewire-core.rb +3 -0
- metadata +237 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Diagnostics
|
|
6
|
+
class Tail
|
|
7
|
+
DEFAULT_CAPACITY = 200
|
|
8
|
+
DEFAULT_NAME = :tail
|
|
9
|
+
COUNTER_KEYS = %i[captured failures].freeze
|
|
10
|
+
Entry = Data.define(:sequence, :at, :record)
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def attach!(runtime = Julewire, **)
|
|
14
|
+
destination = new(**)
|
|
15
|
+
runtime.configure { it.destinations.add(destination) }
|
|
16
|
+
destination
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :capacity, :name
|
|
21
|
+
|
|
22
|
+
def initialize(
|
|
23
|
+
name: DEFAULT_NAME,
|
|
24
|
+
capacity: DEFAULT_CAPACITY,
|
|
25
|
+
formatter: Records::Formatter.new,
|
|
26
|
+
renderer: Renderer.new,
|
|
27
|
+
serializer: nil
|
|
28
|
+
)
|
|
29
|
+
@name = Core.normalize_name(name)
|
|
30
|
+
@capacity = Validation.validate_integer_limit!(capacity, name: :capacity, positive: true)
|
|
31
|
+
Validation.validate_callable!(formatter, name: :formatter)
|
|
32
|
+
Validation.validate_callable!(renderer, name: :renderer)
|
|
33
|
+
@formatter = formatter
|
|
34
|
+
@renderer = renderer
|
|
35
|
+
@serializer = serializer
|
|
36
|
+
@serializer_pool_key = :"julewire_core_tail_serializers_#{object_id}"
|
|
37
|
+
initialize_state
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def emit(record)
|
|
41
|
+
snapshot = snapshot_record(record)
|
|
42
|
+
@mutex.synchronize do
|
|
43
|
+
@sequence += 1
|
|
44
|
+
entry = Entry.new(@sequence, Time.now.utc, snapshot)
|
|
45
|
+
@entries << entry
|
|
46
|
+
@entries.shift while @entries.length > @capacity
|
|
47
|
+
end
|
|
48
|
+
@health.increment(:captured)
|
|
49
|
+
nil
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
record_failure(e, record)
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def entries(limit: nil)
|
|
56
|
+
limit = normalize_limit(limit)
|
|
57
|
+
snapshot = @mutex.synchronize { @entries.dup }
|
|
58
|
+
limit ? snapshot.last(limit) : snapshot
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def records(limit: nil)
|
|
62
|
+
entries(limit: limit).map(&:record)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render(limit: nil, color: false)
|
|
66
|
+
@renderer.call(entries(limit: limit), color: color)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def write(io = $stdout, limit: nil, color: nil)
|
|
70
|
+
io.write(render(limit: limit, color: color.nil? ? io.tty? : color))
|
|
71
|
+
io
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def clear
|
|
75
|
+
@mutex.synchronize do
|
|
76
|
+
@entries.clear
|
|
77
|
+
end
|
|
78
|
+
@health.clear_degraded!
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def flush(*) = self
|
|
83
|
+
|
|
84
|
+
def close(*) = self
|
|
85
|
+
|
|
86
|
+
def after_fork!
|
|
87
|
+
initialize_state
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def health
|
|
92
|
+
size = @mutex.synchronize { @entries.length }
|
|
93
|
+
@health.snapshot(capacity: @capacity, size: size)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def initialize_state
|
|
99
|
+
@mutex = Mutex.new
|
|
100
|
+
@entries = []
|
|
101
|
+
@health = Integration::DestinationHealth.new(counter_keys: COUNTER_KEYS)
|
|
102
|
+
@sequence = 0
|
|
103
|
+
@serializer_mutex = Mutex.new
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def normalize_limit(value)
|
|
107
|
+
return if value.nil?
|
|
108
|
+
|
|
109
|
+
Validation.validate_integer_limit!(value, name: :limit, positive: true)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def snapshot_record(record)
|
|
113
|
+
payload = @formatter.call(record)
|
|
114
|
+
raise TypeError, "formatter must return a payload object" if payload.nil?
|
|
115
|
+
|
|
116
|
+
Serialization::DeepFreeze.call(with_display_message(serialize_payload(payload), record))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def with_display_message(payload, record)
|
|
120
|
+
return payload unless payload.is_a?(Hash)
|
|
121
|
+
return payload unless blank?(payload["message"]) && blank?(payload[:message])
|
|
122
|
+
|
|
123
|
+
message = Records::DisplayMessage.call(record)
|
|
124
|
+
return payload if blank?(message)
|
|
125
|
+
|
|
126
|
+
payload = payload.dup if payload.frozen?
|
|
127
|
+
payload["message"] = message
|
|
128
|
+
payload
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def serialize_payload(payload)
|
|
132
|
+
return serialize_custom_payload(payload) if @serializer
|
|
133
|
+
|
|
134
|
+
serializer = cached_serializer
|
|
135
|
+
return build_serializer.serialize(payload) if serializer.in_use?
|
|
136
|
+
|
|
137
|
+
serializer.serialize(payload)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def serialize_custom_payload(payload)
|
|
141
|
+
@serializer_mutex.synchronize { @serializer.serialize(payload) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def cached_serializer
|
|
145
|
+
Serialization::SerializerPool.serializer(@serializer_pool_key, :default) { build_serializer }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_serializer
|
|
149
|
+
Serialization::Serializer.new(compact_empty: true)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def blank?(value)
|
|
153
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def record_failure(error, record)
|
|
157
|
+
@health.record_failure(
|
|
158
|
+
error,
|
|
159
|
+
action: :emit,
|
|
160
|
+
destination: @name,
|
|
161
|
+
phase: :tail,
|
|
162
|
+
record_metadata: Records::Metadata.call(record)
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Execution
|
|
6
|
+
class Boundary
|
|
7
|
+
EMPTY_HASH = {}.freeze
|
|
8
|
+
EXECUTION_OPTION_DEFAULTS = {
|
|
9
|
+
id: nil,
|
|
10
|
+
fields: EMPTY_HASH,
|
|
11
|
+
attributes: EMPTY_HASH,
|
|
12
|
+
neutral: EMPTY_HASH,
|
|
13
|
+
labels: EMPTY_HASH,
|
|
14
|
+
owned: false,
|
|
15
|
+
inherit_attributes: true,
|
|
16
|
+
emit_summary: true,
|
|
17
|
+
summary_event: nil,
|
|
18
|
+
summary_severity: nil,
|
|
19
|
+
summary_source: nil
|
|
20
|
+
}.freeze
|
|
21
|
+
EXECUTION_OPTION_KEYS = EXECUTION_OPTION_DEFAULTS.keys.freeze
|
|
22
|
+
private_constant :EXECUTION_OPTION_DEFAULTS, :EXECUTION_OPTION_KEYS, :EMPTY_HASH
|
|
23
|
+
|
|
24
|
+
def initialize(emit_summary_record:, summary_finalizer_failure:, emit_non_standard_exception_summaries:,
|
|
25
|
+
before_call: nil)
|
|
26
|
+
@before_call = before_call
|
|
27
|
+
@emit_summary_record = emit_summary_record
|
|
28
|
+
@summary_finalizer_failure = summary_finalizer_failure
|
|
29
|
+
@emit_non_standard_exception_summaries = emit_non_standard_exception_summaries
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def with_execution(type:, **options, &)
|
|
33
|
+
before_call!(:with_execution)
|
|
34
|
+
raise ArgumentError, "block required" unless block_given?
|
|
35
|
+
|
|
36
|
+
open_context_execution(:with_execution, type: type, options: options, &)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def start_execution(type:, **options)
|
|
40
|
+
before_call!(:start_execution)
|
|
41
|
+
open_context_execution(:start_execution, type: type, options: options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def before_call!(action)
|
|
47
|
+
@before_call&.call(action)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def open_context_execution(method_name, type:, options:, &)
|
|
51
|
+
options = normalized_execution_options(options)
|
|
52
|
+
ContextStore.current.public_send(
|
|
53
|
+
method_name,
|
|
54
|
+
type: type,
|
|
55
|
+
id: options.fetch(:id),
|
|
56
|
+
execution: options.fetch(:execution),
|
|
57
|
+
attributes: options.fetch(:attributes),
|
|
58
|
+
neutral: options.fetch(:neutral),
|
|
59
|
+
labels: options.fetch(:labels),
|
|
60
|
+
owned: options.fetch(:owned),
|
|
61
|
+
inherit_attributes: options.fetch(:inherit_attributes),
|
|
62
|
+
on_finish: summary_finalizer(emit_summary_enabled: options.fetch(:emit_summary)),
|
|
63
|
+
on_finish_failure: @summary_finalizer_failure,
|
|
64
|
+
summary_event: options.fetch(:summary_event),
|
|
65
|
+
summary_severity: options.fetch(:summary_severity),
|
|
66
|
+
summary_source: options.fetch(:summary_source),
|
|
67
|
+
&
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def normalized_execution_options(options)
|
|
72
|
+
unknown = options.keys - EXECUTION_OPTION_KEYS
|
|
73
|
+
raise ArgumentError, "unknown execution options: #{unknown.join(", ")}" unless unknown.empty?
|
|
74
|
+
|
|
75
|
+
EXECUTION_OPTION_DEFAULTS.merge(options).tap do |normalized|
|
|
76
|
+
normalized[:execution] = execution_fields(normalized.delete(:fields))
|
|
77
|
+
normalized[:attributes] ||= EMPTY_HASH
|
|
78
|
+
normalized[:neutral] ||= EMPTY_HASH
|
|
79
|
+
normalized[:labels] ||= EMPTY_HASH
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def execution_fields(fields)
|
|
84
|
+
return {} if fields.nil?
|
|
85
|
+
raise ArgumentError, "execution fields must be a Hash" unless fields.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
fields
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def summary_finalizer(emit_summary_enabled:)
|
|
91
|
+
return unless emit_summary_enabled
|
|
92
|
+
|
|
93
|
+
@summary_finalizer ||= lambda do |scope|
|
|
94
|
+
next if suppress_summary_for_non_standard_exception?(scope)
|
|
95
|
+
|
|
96
|
+
@emit_summary_record.call(scope)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def suppress_summary_for_non_standard_exception?(scope)
|
|
101
|
+
scope.non_standard_exception? && !@emit_non_standard_exception_summaries.call
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Execution
|
|
6
|
+
class Handle
|
|
7
|
+
def initialize(scope:, on_finish:, on_finish_failure:)
|
|
8
|
+
@scope = scope
|
|
9
|
+
@on_finish = on_finish
|
|
10
|
+
@on_finish_failure = on_finish_failure
|
|
11
|
+
@mutex = Mutex.new
|
|
12
|
+
@finished = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :scope
|
|
16
|
+
|
|
17
|
+
def snapshot = View.new(@scope)
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
active_exception = nil
|
|
21
|
+
ContextStore.current.with_scope(@scope) do
|
|
22
|
+
yield self
|
|
23
|
+
end
|
|
24
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
25
|
+
active_exception = e
|
|
26
|
+
raise
|
|
27
|
+
ensure
|
|
28
|
+
finish(reason: :error, error: active_exception) if active_exception
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def with_context(&)
|
|
32
|
+
ContextStore.current.with_scope(@scope, &)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def finish(reason: :closed, fields: {}, attributes: {}, error: nil, severity: nil)
|
|
36
|
+
return false unless mark_finished
|
|
37
|
+
|
|
38
|
+
add_completion_attributes(reason)
|
|
39
|
+
@scope.add_summary(fields) unless fields.empty?
|
|
40
|
+
@scope.add_summary_attributes(attributes) unless attributes.empty?
|
|
41
|
+
@scope.finish_owned(error: error, severity: severity)
|
|
42
|
+
call_finish
|
|
43
|
+
true
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
report_finish_failure(e)
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def mark_finished
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
return false if @finished
|
|
54
|
+
|
|
55
|
+
@finished = true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_completion_attributes(reason)
|
|
60
|
+
@scope.add_summary_attributes({ "julewire.completion": reason.to_s }, owned: true)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call_finish
|
|
64
|
+
@on_finish&.call(@scope)
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
report_finish_failure(e)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def report_finish_failure(error)
|
|
70
|
+
@on_finish_failure&.call(error)
|
|
71
|
+
rescue StandardError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Execution
|
|
6
|
+
class Lineage
|
|
7
|
+
# Bounded to cap summary growth; also the Answer to the Ultimate Question.
|
|
8
|
+
MAX_ANCESTORS = 42
|
|
9
|
+
RELATIONSHIP_KEYS = %i[depth root parent ancestors ancestors_truncated].freeze
|
|
10
|
+
LAZY_RELATIONSHIP_KEYS = %i[ancestors ancestors_truncated].freeze
|
|
11
|
+
attr_reader :depth, :parent_reference, :root_reference
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def clean_execution_hash(execution)
|
|
15
|
+
clean_hash(execution, RELATIONSHIP_KEYS)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clean_owned_execution_hash(execution)
|
|
19
|
+
clean_hash!(execution, RELATIONSHIP_KEYS)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clean_lazy_relationship_hash(execution)
|
|
23
|
+
clean_hash(execution, LAZY_RELATIONSHIP_KEYS)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def from_execution_hash(execution)
|
|
27
|
+
execution = {} unless execution.is_a?(Hash)
|
|
28
|
+
new(
|
|
29
|
+
reference: execution_reference(execution),
|
|
30
|
+
root_reference: relationship_value(execution, :root),
|
|
31
|
+
parent_reference: relationship_value(execution, :parent),
|
|
32
|
+
depth: relationship_value(execution, :depth),
|
|
33
|
+
ancestors: relationship_value(execution, :ancestors),
|
|
34
|
+
ancestors_truncated: relationship_value(execution, :ancestors_truncated)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def relationship_value(execution, key)
|
|
41
|
+
Fields::FieldSet.value_for(execution, key)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reference_value(execution, key)
|
|
45
|
+
Fields::FieldSet.value_for(execution, key, default: MISSING)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clean_hash(execution, keys)
|
|
49
|
+
copy = execution.is_a?(Hash) ? Fields::FieldSet.deep_symbolize_keys(execution) : {}
|
|
50
|
+
clean_hash!(copy, keys)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def clean_hash!(copy, keys)
|
|
54
|
+
keys.each do |key|
|
|
55
|
+
Fields::Internal.delete_key!(copy, key)
|
|
56
|
+
end
|
|
57
|
+
copy
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def execution_reference(execution)
|
|
61
|
+
reference = {}
|
|
62
|
+
type = reference_value(execution, :type)
|
|
63
|
+
id = reference_value(execution, :id)
|
|
64
|
+
reference[:type] = type unless type.equal?(MISSING)
|
|
65
|
+
reference[:id] = id unless id.equal?(MISSING)
|
|
66
|
+
reference.empty? ? nil : reference
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def initialize(
|
|
71
|
+
reference: nil,
|
|
72
|
+
parent_lineage: nil,
|
|
73
|
+
parent_reference: nil,
|
|
74
|
+
root_reference: nil,
|
|
75
|
+
depth: nil,
|
|
76
|
+
ancestors: nil,
|
|
77
|
+
ancestors_truncated: false
|
|
78
|
+
)
|
|
79
|
+
@parent_lineage = parent_lineage
|
|
80
|
+
@depth = depth_value(depth, parent_lineage)
|
|
81
|
+
@root_reference = freeze_reference(root_reference || root_reference_for(reference, parent_lineage))
|
|
82
|
+
@parent_reference = freeze_reference(parent_reference)
|
|
83
|
+
@ancestor_references = freeze_ancestors(ancestors)
|
|
84
|
+
@ancestors_truncated = ancestors_truncated ? true : false
|
|
85
|
+
@ancestor_references_for_child = nil
|
|
86
|
+
@truncated = nil
|
|
87
|
+
@truncated_computed = false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def merge_into_frozen(execution)
|
|
91
|
+
hash = frozen_hash_copy(execution)
|
|
92
|
+
hash[:depth] = depth
|
|
93
|
+
hash[:root] = @root_reference
|
|
94
|
+
hash[:parent] = @parent_reference if @parent_reference
|
|
95
|
+
hash.freeze
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ancestors
|
|
99
|
+
references = @ancestor_references_for_child
|
|
100
|
+
return references if references
|
|
101
|
+
|
|
102
|
+
materialize_ancestor_references_for_child
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def truncated?
|
|
106
|
+
return @truncated if @truncated_computed
|
|
107
|
+
|
|
108
|
+
@truncated = @ancestors_truncated || ancestor_count_exceeds_limit?
|
|
109
|
+
@truncated_computed = true
|
|
110
|
+
@truncated
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def freeze
|
|
114
|
+
return self if frozen?
|
|
115
|
+
|
|
116
|
+
ancestors
|
|
117
|
+
truncated?
|
|
118
|
+
@parent_lineage = nil
|
|
119
|
+
super
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
protected
|
|
123
|
+
|
|
124
|
+
def ancestor_references_for_child
|
|
125
|
+
ancestors
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def root_reference_for_child
|
|
129
|
+
@root_reference
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
attr_reader :parent_lineage
|
|
135
|
+
|
|
136
|
+
def materialize_ancestor_references_for_child
|
|
137
|
+
if @ancestor_references
|
|
138
|
+
@ancestor_references_for_child = @ancestor_references.freeze
|
|
139
|
+
else
|
|
140
|
+
references = build_ancestor_references
|
|
141
|
+
@truncated = references.length > MAX_ANCESTORS
|
|
142
|
+
@truncated_computed = true
|
|
143
|
+
@ancestor_references_for_child = references.last(MAX_ANCESTORS).freeze
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def ancestor_count_exceeds_limit?
|
|
148
|
+
return false if @ancestor_references_for_child
|
|
149
|
+
|
|
150
|
+
build_ancestor_references.length > MAX_ANCESTORS
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def depth_value(depth, parent_lineage)
|
|
154
|
+
return depth if depth.is_a?(Integer) && depth.positive?
|
|
155
|
+
return parent_lineage.depth + 1 if parent_lineage
|
|
156
|
+
|
|
157
|
+
1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def root_reference_for(reference, parent_lineage)
|
|
161
|
+
parent_lineage ? parent_lineage.root_reference_for_child : reference
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def build_ancestor_references
|
|
165
|
+
return [] unless parent_lineage && @parent_reference
|
|
166
|
+
|
|
167
|
+
parent_lineage.ancestor_references_for_child + [@parent_reference]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def freeze_ancestors(ancestors)
|
|
171
|
+
return unless ancestors.is_a?(Array)
|
|
172
|
+
|
|
173
|
+
Serialization::ValueCopy.call(ancestors, freeze_values: true)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def freeze_reference(reference)
|
|
177
|
+
return unless reference
|
|
178
|
+
return reference if reference.frozen?
|
|
179
|
+
|
|
180
|
+
Serialization::ValueCopy.call(reference, freeze_values: true)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def frozen_hash_copy(value)
|
|
184
|
+
value.each_with_object({}) do |(key, field_value), copy|
|
|
185
|
+
copy[Fields::Internal.normalize_key(key)] =
|
|
186
|
+
Serialization::ValueCopy.call(field_value, freeze_values: true)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Execution
|
|
6
|
+
class MeasurementHandle
|
|
7
|
+
def initialize(&finish)
|
|
8
|
+
@finish = finish
|
|
9
|
+
@finished = false
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def finish
|
|
14
|
+
@mutex.synchronize do
|
|
15
|
+
return if @finished
|
|
16
|
+
|
|
17
|
+
@finished = true
|
|
18
|
+
@finish.call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def finished?
|
|
23
|
+
@mutex.synchronize { @finished }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|