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,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
class Subscription
|
|
8
|
+
attr_reader :subscriber
|
|
9
|
+
|
|
10
|
+
def initialize(subscriber, unsubscribe: nil)
|
|
11
|
+
@subscriber = subscriber
|
|
12
|
+
@unsubscribe = unsubscribe
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def update(configuration)
|
|
16
|
+
@subscriber.configuration = configuration
|
|
17
|
+
@subscriber
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset
|
|
21
|
+
@unsubscribe&.call
|
|
22
|
+
nil
|
|
23
|
+
rescue StandardError
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Julewire
|
|
6
|
+
module Core
|
|
7
|
+
module Integration
|
|
8
|
+
# @api integration_spi
|
|
9
|
+
module Values
|
|
10
|
+
EMPTY_HASH = {}.freeze
|
|
11
|
+
|
|
12
|
+
module Common
|
|
13
|
+
def empty_hash = EMPTY_HASH
|
|
14
|
+
|
|
15
|
+
def blank_value?(value)
|
|
16
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
17
|
+
rescue StandardError
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private_constant :EMPTY_HASH, :Common
|
|
23
|
+
|
|
24
|
+
# @api integration_spi
|
|
25
|
+
module Read
|
|
26
|
+
extend Common
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def blank?(value)
|
|
30
|
+
blank_value?(value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def hash_value(hash, key, default: nil)
|
|
34
|
+
return default unless hash.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
direct_hash_value(hash, key, default)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def value(object, key, default: nil)
|
|
40
|
+
return hash_value(object, key, default: default) if object.is_a?(Hash)
|
|
41
|
+
|
|
42
|
+
return default unless object.respond_to?(key)
|
|
43
|
+
|
|
44
|
+
object.public_send(key)
|
|
45
|
+
rescue StandardError
|
|
46
|
+
default
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def nested_value(object, *keys, default: nil)
|
|
50
|
+
current = object
|
|
51
|
+
keys.each do |key|
|
|
52
|
+
return default if current.nil?
|
|
53
|
+
|
|
54
|
+
current = value(current, key)
|
|
55
|
+
end
|
|
56
|
+
current.nil? ? default : current
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def path_value(object, path, default: nil)
|
|
60
|
+
current = object
|
|
61
|
+
Array(path).each do |key|
|
|
62
|
+
return default if current.nil?
|
|
63
|
+
|
|
64
|
+
current = indexed_value(current, key)
|
|
65
|
+
return default if current.equal?(MISSING)
|
|
66
|
+
end
|
|
67
|
+
current
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def first_value(source, keys:)
|
|
71
|
+
if source.is_a?(Hash)
|
|
72
|
+
found = direct_hash_first_value(source, keys)
|
|
73
|
+
return found unless found.equal?(MISSING)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
keys.each do |key|
|
|
77
|
+
found = indexed_value(source, key)
|
|
78
|
+
return found unless found.equal?(MISSING) || blank_value?(found)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def direct_hash_first_value(source, keys)
|
|
86
|
+
keys.each do |key|
|
|
87
|
+
next unless source.key?(key)
|
|
88
|
+
|
|
89
|
+
found = source[key]
|
|
90
|
+
return found unless blank_value?(found)
|
|
91
|
+
end
|
|
92
|
+
MISSING
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def indexed_value(source, key)
|
|
96
|
+
return hash_value(source, key, default: MISSING) if source.is_a?(Hash)
|
|
97
|
+
return MISSING unless source.respond_to?(:[])
|
|
98
|
+
|
|
99
|
+
source[key]
|
|
100
|
+
rescue StandardError
|
|
101
|
+
MISSING
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def direct_hash_value(hash, key, default)
|
|
105
|
+
return hash[key] if hash.key?(key)
|
|
106
|
+
|
|
107
|
+
case key
|
|
108
|
+
when Symbol then symbol_key_value(hash, key, default)
|
|
109
|
+
when String then string_key_value(hash, key, default)
|
|
110
|
+
else default
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def symbol_key_value(hash, key, default)
|
|
115
|
+
string_key = key.name
|
|
116
|
+
return hash[string_key] if hash.key?(string_key)
|
|
117
|
+
|
|
118
|
+
default
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def string_key_value(hash, key, default)
|
|
122
|
+
symbol_key = key.to_sym
|
|
123
|
+
return hash[symbol_key] if hash.key?(symbol_key)
|
|
124
|
+
|
|
125
|
+
default
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @api integration_spi
|
|
131
|
+
module Shape
|
|
132
|
+
extend Common
|
|
133
|
+
|
|
134
|
+
class << self
|
|
135
|
+
def timestamp(value)
|
|
136
|
+
return unless value
|
|
137
|
+
return value.utc.iso8601(9) if value.respond_to?(:utc) && value.respond_to?(:iso8601)
|
|
138
|
+
return value unless value.respond_to?(:divmod)
|
|
139
|
+
|
|
140
|
+
seconds, nanoseconds = value.divmod(1_000_000_000)
|
|
141
|
+
Time.at(seconds, nanoseconds, :nanosecond).utc.iso8601(9)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def payload_hash(value)
|
|
147
|
+
case value
|
|
148
|
+
when nil
|
|
149
|
+
empty_hash
|
|
150
|
+
when Hash
|
|
151
|
+
return empty_hash if value.empty?
|
|
152
|
+
|
|
153
|
+
Fields::FieldSet.deep_symbolize_keys(value)
|
|
154
|
+
else
|
|
155
|
+
{ Fields::FieldSet::VALUE_KEY => value }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def hash_or_empty(value)
|
|
160
|
+
return empty_hash unless value.is_a?(Hash)
|
|
161
|
+
return empty_hash if value.empty?
|
|
162
|
+
|
|
163
|
+
Fields::FieldSet.deep_symbolize_keys(value)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def append_field(fields, key, value, compact_empty: false)
|
|
167
|
+
return if value.nil?
|
|
168
|
+
return if compact_empty && (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
|
|
169
|
+
|
|
170
|
+
fields[key] = value
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def append_compact_field(fields, key, value)
|
|
175
|
+
append_field(fields, key, value, compact_empty: true)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def source_location_attributes(location)
|
|
179
|
+
return {} unless location.is_a?(Hash)
|
|
180
|
+
|
|
181
|
+
Fields::AttributeKeys.fields(
|
|
182
|
+
Fields::AttributeKeys::CODE_FILE_PATH => Read.first_value(location, keys: %i[filepath path file]),
|
|
183
|
+
Fields::AttributeKeys::CODE_LINE_NUMBER => Read.first_value(location, keys: %i[lineno line]),
|
|
184
|
+
Fields::AttributeKeys::CODE_FUNCTION_NAME => Read.first_value(location, keys: %i[label function])
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/atomic/atomic_reference"
|
|
4
|
+
|
|
5
|
+
module Julewire
|
|
6
|
+
module Core
|
|
7
|
+
# @api internal
|
|
8
|
+
# Process/ractor-local storage for facade lookups. The ractor bridge can set
|
|
9
|
+
# the current runtime from inside a child ractor, but the getter side still
|
|
10
|
+
# runs inside core through Julewire.* facade calls.
|
|
11
|
+
module LocalStorage
|
|
12
|
+
RUNTIME_KEY = :__julewire_core_runtime__
|
|
13
|
+
CONTEXT_STORE_THREAD_KEY = :__julewire_core_context_store__
|
|
14
|
+
CONTEXT_STORE_FIBER_IVAR = :@__julewire_core_context_store__
|
|
15
|
+
private_constant :RUNTIME_KEY, :CONTEXT_STORE_THREAD_KEY, :CONTEXT_STORE_FIBER_IVAR
|
|
16
|
+
|
|
17
|
+
@runtime_ref = Concurrent::AtomicReference.new
|
|
18
|
+
@runtime_mutex = Mutex.new
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def runtime
|
|
22
|
+
return ractor_runtime if ractor_local_storage?
|
|
23
|
+
|
|
24
|
+
runtime_ref.get || runtime_mutex.synchronize do
|
|
25
|
+
runtime_ref.get || Runtime.new.tap { runtime_ref.set(it) }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def runtime=(runtime)
|
|
30
|
+
if ractor_local_storage?
|
|
31
|
+
::Ractor[RUNTIME_KEY] = runtime
|
|
32
|
+
else
|
|
33
|
+
runtime_ref.set(runtime)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def context_store
|
|
38
|
+
context_store_value || store_context(ContextStore.new)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset_context_store!
|
|
42
|
+
store_context(nil)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def after_fork!
|
|
46
|
+
runtime = runtime_ref.get
|
|
47
|
+
@runtime_mutex = Mutex.new
|
|
48
|
+
@runtime_ref = Concurrent::AtomicReference.new(runtime)
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Private testing seam for storage-selection behavior.
|
|
53
|
+
def main_ractor?
|
|
54
|
+
::Ractor.main?
|
|
55
|
+
end
|
|
56
|
+
private :main_ractor?
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
attr_reader :runtime_mutex, :runtime_ref
|
|
61
|
+
|
|
62
|
+
def ractor_runtime
|
|
63
|
+
::Ractor.store_if_absent(RUNTIME_KEY) { Runtime.new }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def context_store_value
|
|
67
|
+
if ractor_local_storage?
|
|
68
|
+
# Child-ractor bridge work is thread-scoped; main-ractor app work is fiber-scoped.
|
|
69
|
+
Thread.current[CONTEXT_STORE_THREAD_KEY]
|
|
70
|
+
else
|
|
71
|
+
# Do not use Fiber#storage here: child fibers inherit it by default,
|
|
72
|
+
# while Julewire context only propagates through Julewire.fiber.
|
|
73
|
+
Fiber.current.instance_variable_get(CONTEXT_STORE_FIBER_IVAR)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def store_context(value)
|
|
78
|
+
if ractor_local_storage?
|
|
79
|
+
Thread.current[CONTEXT_STORE_THREAD_KEY] = value
|
|
80
|
+
else
|
|
81
|
+
Fiber.current.instance_variable_set(CONTEXT_STORE_FIBER_IVAR, value)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def ractor_local_storage?
|
|
86
|
+
!main_ractor?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Processing
|
|
6
|
+
class LevelThreshold
|
|
7
|
+
DEFAULT_EVENT = "log"
|
|
8
|
+
DEFAULT_SEVERITY = :info
|
|
9
|
+
|
|
10
|
+
attr_reader :level
|
|
11
|
+
|
|
12
|
+
def initialize(level:, invalid_severity_reporter: Diagnostics::InvalidSeverityReporter)
|
|
13
|
+
@level = Records::Severity.normalize(level)
|
|
14
|
+
@level_rank = Records::Severity.rank(@level)
|
|
15
|
+
@invalid_severity_reporter = invalid_severity_reporter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def allow?(severity)
|
|
19
|
+
Records::Severity.rank(severity) >= @level_rank
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def raw_input_allowed?(input)
|
|
23
|
+
return allow?(DEFAULT_SEVERITY) unless Records::RawInput.hash_input?(input)
|
|
24
|
+
|
|
25
|
+
severity, invalid, invalid_raw_value = raw_input_severity(input)
|
|
26
|
+
allowed = allow?(severity)
|
|
27
|
+
# Surviving inputs warn later at Records::Draft normalization.
|
|
28
|
+
record_invalid_raw_severity(input, invalid_raw_value) if invalid && !allowed
|
|
29
|
+
allowed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def raw_input_severity(input)
|
|
35
|
+
return [DEFAULT_SEVERITY, false, nil] unless Records::RawInput.explicit_severity?(input)
|
|
36
|
+
|
|
37
|
+
raw_value = Records::RawInput.value(input, :severity)
|
|
38
|
+
[Records::Severity.normalize(raw_value), false, nil]
|
|
39
|
+
rescue ArgumentError
|
|
40
|
+
[DEFAULT_SEVERITY, true, raw_value]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def record_invalid_raw_severity(input, raw_value)
|
|
44
|
+
@invalid_severity_reporter.call(
|
|
45
|
+
raw_value,
|
|
46
|
+
source: Records::RawInput.value(input, :source),
|
|
47
|
+
event: Records::RawInput.value(input, :event) || DEFAULT_EVENT
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Processing
|
|
6
|
+
# @api extension
|
|
7
|
+
class Match
|
|
8
|
+
Rule = Data.define(:conditions, :handler)
|
|
9
|
+
private_constant :Rule
|
|
10
|
+
|
|
11
|
+
def initialize(&)
|
|
12
|
+
@rules = []
|
|
13
|
+
instance_eval(&) if block_given?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def on(conditions = nil, **keyword_conditions, &handler)
|
|
17
|
+
raise ArgumentError, "match handler is required" unless handler
|
|
18
|
+
|
|
19
|
+
conditions = normalize_conditions(conditions, keyword_conditions)
|
|
20
|
+
@rules << Rule.new(conditions.freeze, handler)
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(draft)
|
|
25
|
+
@rules.each do |rule|
|
|
26
|
+
next unless matches_conditions?(draft, rule.conditions)
|
|
27
|
+
|
|
28
|
+
result = rule.handler.call(draft)
|
|
29
|
+
return result if result == :drop || result.is_a?(Records::Draft)
|
|
30
|
+
end
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def normalize_conditions(conditions, keyword_conditions)
|
|
37
|
+
fields = case conditions
|
|
38
|
+
when nil then {}
|
|
39
|
+
when Hash then conditions.dup
|
|
40
|
+
else raise ArgumentError, "match conditions must be a Hash"
|
|
41
|
+
end
|
|
42
|
+
fields.merge!(keyword_conditions)
|
|
43
|
+
raise ArgumentError, "match conditions are required" if fields.empty?
|
|
44
|
+
|
|
45
|
+
fields
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def matches_conditions?(draft, conditions)
|
|
49
|
+
conditions.all? { |key, pattern| matches_value?(pattern, draft[key]) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def matches_value?(pattern, value)
|
|
53
|
+
case pattern
|
|
54
|
+
when Hash then matches_hash?(pattern, value)
|
|
55
|
+
when Proc then pattern.call(value)
|
|
56
|
+
when Regexp then value.is_a?(String) && pattern.match?(value)
|
|
57
|
+
when Range then pattern.cover?(value)
|
|
58
|
+
when Module then value.is_a?(pattern)
|
|
59
|
+
else pattern == value
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def matches_hash?(pattern, value)
|
|
64
|
+
return false unless value.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
pattern.all? do |key, nested_pattern|
|
|
67
|
+
nested_value = Fields::FieldSet.value_for(value, key, default: Core::UNSET)
|
|
68
|
+
!nested_value.equal?(Core::UNSET) && matches_value?(nested_pattern, nested_value)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|