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
|
@@ -4,6 +4,9 @@ module Julewire
|
|
|
4
4
|
module Core
|
|
5
5
|
module Serialization
|
|
6
6
|
class BacktraceLimiter
|
|
7
|
+
MAX_CAUSE_DEPTH = Core::NORMALIZATION_MAX_DEPTH
|
|
8
|
+
private_constant :MAX_CAUSE_DEPTH
|
|
9
|
+
|
|
7
10
|
class << self
|
|
8
11
|
def call(value, max_backtrace_lines:)
|
|
9
12
|
new(max_backtrace_lines: max_backtrace_lines).call(value)
|
|
@@ -18,18 +21,18 @@ module Julewire
|
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def call(value)
|
|
21
|
-
|
|
22
|
-
limit_backtraces(value)
|
|
24
|
+
limit_backtraces(value, Set.new.compare_by_identity)
|
|
23
25
|
value
|
|
24
|
-
ensure
|
|
25
|
-
@seen = nil
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
def limit_backtraces(value)
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
def limit_backtraces(value, seen)
|
|
31
|
+
MAX_CAUSE_DEPTH.times do
|
|
32
|
+
break unless value.is_a?(Hash)
|
|
33
|
+
break if seen.include?(value)
|
|
34
|
+
|
|
35
|
+
seen.add(value)
|
|
33
36
|
limit_backtrace_field!(value)
|
|
34
37
|
value = value[:cause]
|
|
35
38
|
end
|
|
@@ -40,8 +43,9 @@ module Julewire
|
|
|
40
43
|
|
|
41
44
|
if @max_backtrace_lines.zero?
|
|
42
45
|
error.delete(:backtrace)
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
else
|
|
47
|
+
backtrace = error.fetch(:backtrace)
|
|
48
|
+
error[:backtrace] = backtrace.first(@max_backtrace_lines) if backtrace.is_a?(Array)
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
end
|
|
@@ -35,12 +35,11 @@ module Julewire
|
|
|
35
35
|
@prepare_values = !@transform.nil?
|
|
36
36
|
@track_paths = @prepare_values && !track_paths.equal?(false)
|
|
37
37
|
end
|
|
38
|
+
private_class_method :new
|
|
38
39
|
|
|
39
40
|
def call(value)
|
|
40
41
|
@root = value
|
|
41
42
|
walk(value)
|
|
42
|
-
ensure
|
|
43
|
-
@root = nil
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
private
|
|
@@ -49,6 +48,8 @@ module Julewire
|
|
|
49
48
|
transformed = @transform.call(value, key: key, path: path, original: @root, depth: depth)
|
|
50
49
|
transformed.equal?(CONTINUE) ? value : transformed
|
|
51
50
|
end
|
|
51
|
+
|
|
52
|
+
def truncation_metadata(fields) = super(fields, key_style: :symbol)
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
end
|
|
@@ -20,18 +20,17 @@ module Julewire
|
|
|
20
20
|
max_array_items: DEFAULT_MAX_ARRAY_ITEMS,
|
|
21
21
|
max_depth: DEFAULT_MAX_DEPTH,
|
|
22
22
|
max_hash_keys: DEFAULT_MAX_HASH_KEYS,
|
|
23
|
-
max_string_bytes: DEFAULT_MAX_STRING_BYTES
|
|
23
|
+
max_string_bytes: DEFAULT_MAX_STRING_BYTES,
|
|
24
|
+
key_style: :string
|
|
24
25
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
}
|
|
26
|
+
TruncationMetadata.build(
|
|
27
|
+
fields,
|
|
28
|
+
key_style: key_style,
|
|
29
|
+
max_array_items: max_array_items,
|
|
30
|
+
max_depth: max_depth,
|
|
31
|
+
max_hash_keys: max_hash_keys,
|
|
32
|
+
max_string_bytes: max_string_bytes
|
|
33
|
+
)
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
|
|
@@ -43,57 +42,52 @@ module Julewire
|
|
|
43
42
|
@max_hash_keys = Validation.validate_integer_limit!(max_hash_keys, name: :max_hash_keys)
|
|
44
43
|
@max_string_bytes = Validation.validate_integer_limit!(max_string_bytes, name: :max_string_bytes)
|
|
45
44
|
@truncation_key = truncation_key
|
|
46
|
-
@last_truncated = false
|
|
47
|
-
@compact_empty = false
|
|
48
|
-
@prepare_values = false
|
|
49
|
-
@track_paths = false
|
|
50
45
|
end
|
|
51
46
|
|
|
52
47
|
private
|
|
53
48
|
|
|
54
49
|
def walk(value)
|
|
55
|
-
@last_truncated = false
|
|
56
50
|
traverse(value) { |root, depth| walk_value(root, depth, nil, nil) }
|
|
57
51
|
ensure
|
|
58
52
|
@last_truncated = false
|
|
59
53
|
end
|
|
60
54
|
|
|
61
55
|
def walk_value(value, depth, key, path)
|
|
62
|
-
@last_truncated = false
|
|
63
56
|
value = prepare_value(value, depth, key, path) if @prepare_values
|
|
64
|
-
return max_depth_value if depth
|
|
57
|
+
return max_depth_value if depth == @max_depth
|
|
65
58
|
return walk_container(value, depth, path) if value.is_a?(Array) || hash_like?(value)
|
|
66
59
|
|
|
67
|
-
scalar_value(value, depth
|
|
60
|
+
scalar_value(value, depth)
|
|
68
61
|
rescue StandardError => e
|
|
69
|
-
@last_truncated = false
|
|
70
62
|
error_value(e)
|
|
71
63
|
end
|
|
72
64
|
|
|
73
|
-
def prepare_value(value, _depth, _key, _path) = value
|
|
74
|
-
|
|
75
65
|
def hash_like?(value) = value.is_a?(Hash)
|
|
76
66
|
|
|
77
|
-
def scalar_value(value, _depth
|
|
78
|
-
value.is_a?(String) ? string_value(value) :
|
|
67
|
+
def scalar_value(value, _depth)
|
|
68
|
+
value.is_a?(String) ? string_value(value) : value
|
|
79
69
|
end
|
|
80
70
|
|
|
81
71
|
# Transform-stage errors must bubble so processors can fail closed.
|
|
82
|
-
def error_value(
|
|
83
|
-
raise
|
|
72
|
+
def error_value(_error)
|
|
73
|
+
raise
|
|
84
74
|
end
|
|
85
75
|
|
|
86
76
|
def walk_container(value, depth, path)
|
|
87
|
-
|
|
77
|
+
seen = traversal_seen
|
|
78
|
+
return circular_value if seen.include?(value)
|
|
88
79
|
|
|
89
|
-
|
|
80
|
+
seen.add(value)
|
|
81
|
+
begin
|
|
90
82
|
value.is_a?(Array) ? walk_array(value, depth, path) : walk_hash(value, depth, path)
|
|
83
|
+
ensure
|
|
84
|
+
seen.delete(value)
|
|
91
85
|
end
|
|
92
86
|
end
|
|
93
87
|
|
|
94
88
|
def circular_value
|
|
95
89
|
@last_truncated = true
|
|
96
|
-
|
|
90
|
+
CIRCULAR_REFERENCE
|
|
97
91
|
end
|
|
98
92
|
|
|
99
93
|
def max_depth_value
|
|
@@ -101,7 +95,7 @@ module Julewire
|
|
|
101
95
|
end
|
|
102
96
|
|
|
103
97
|
def walk_hash(value, depth, path)
|
|
104
|
-
return walk_compact_hash(value, depth
|
|
98
|
+
return walk_compact_hash(value, depth) if @compact_empty
|
|
105
99
|
|
|
106
100
|
walk_full_hash(value, depth, path)
|
|
107
101
|
end
|
|
@@ -110,121 +104,122 @@ module Julewire
|
|
|
110
104
|
fields = nil
|
|
111
105
|
result = {}
|
|
112
106
|
track_paths = @track_paths
|
|
107
|
+
visited = 0
|
|
113
108
|
value.each do |raw_key, item|
|
|
114
|
-
if
|
|
109
|
+
if visited == @max_hash_keys
|
|
115
110
|
fields = append_truncation_field(fields, "hash_keys")
|
|
116
111
|
break
|
|
117
112
|
end
|
|
118
113
|
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
visited += 1
|
|
115
|
+
child_path = path_for(path, raw_key) if track_paths
|
|
116
|
+
child = walk_value(item, depth + 1, raw_key, child_path)
|
|
121
117
|
key = key_value(raw_key)
|
|
122
|
-
key_truncated = consume_truncated
|
|
123
118
|
result[key] = child
|
|
124
|
-
fields = record_hash_truncation(fields,
|
|
119
|
+
fields = record_hash_truncation(fields, key, consume_truncated)
|
|
125
120
|
end
|
|
126
121
|
finish_hash(result, fields)
|
|
127
122
|
end
|
|
128
123
|
|
|
129
|
-
def walk_compact_hash(value, depth
|
|
124
|
+
def walk_compact_hash(value, depth)
|
|
130
125
|
fields = nil
|
|
131
126
|
result = {}
|
|
132
|
-
|
|
127
|
+
visited = 0
|
|
133
128
|
value.each do |raw_key, item|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
child_truncated = consume_truncated
|
|
138
|
-
next if omitted_value?(child)
|
|
139
|
-
|
|
140
|
-
if result.length >= @max_hash_keys
|
|
129
|
+
# The hash cap bounds visited input entries, not final output keys, so
|
|
130
|
+
# serialized-key collisions and compacted entries cannot hide work.
|
|
131
|
+
if visited == @max_hash_keys
|
|
141
132
|
fields = append_truncation_field(fields, "hash_keys")
|
|
142
133
|
break
|
|
143
134
|
end
|
|
144
135
|
|
|
136
|
+
visited += 1
|
|
137
|
+
next if raw_omitted_value?(item)
|
|
138
|
+
|
|
139
|
+
child = walk_value(item, depth + 1, nil, nil)
|
|
140
|
+
next if omitted_value?(child)
|
|
141
|
+
|
|
145
142
|
key = key_value(raw_key)
|
|
146
|
-
key_truncated = consume_truncated
|
|
147
143
|
result[key] = child
|
|
148
|
-
fields = record_hash_truncation(fields,
|
|
144
|
+
fields = record_hash_truncation(fields, key, consume_truncated)
|
|
149
145
|
end
|
|
150
146
|
finish_hash(result, fields)
|
|
151
147
|
end
|
|
152
148
|
|
|
153
149
|
def walk_array(value, depth, path)
|
|
154
|
-
return walk_compact_array(value, depth
|
|
150
|
+
return walk_compact_array(value, depth) if @compact_empty
|
|
155
151
|
|
|
156
152
|
walk_full_array(value, depth, path)
|
|
157
153
|
end
|
|
158
154
|
|
|
159
155
|
def walk_full_array(value, depth, path)
|
|
160
|
-
|
|
156
|
+
truncated = false
|
|
161
157
|
result = []
|
|
158
|
+
visited = 0
|
|
162
159
|
value.each do |item|
|
|
163
|
-
if
|
|
164
|
-
|
|
160
|
+
if visited == @max_array_items
|
|
161
|
+
truncated = true
|
|
165
162
|
break
|
|
166
163
|
end
|
|
167
164
|
|
|
165
|
+
visited += 1
|
|
168
166
|
child = walk_value(item, depth + 1, nil, path)
|
|
169
167
|
child_truncated = consume_truncated
|
|
170
168
|
result << child
|
|
171
|
-
|
|
169
|
+
truncated = true if child_truncated
|
|
172
170
|
end
|
|
173
|
-
finish_array(result,
|
|
171
|
+
finish_array(result, truncated && "array_items")
|
|
174
172
|
end
|
|
175
173
|
|
|
176
|
-
def walk_compact_array(value, depth
|
|
177
|
-
|
|
174
|
+
def walk_compact_array(value, depth)
|
|
175
|
+
truncated = false
|
|
178
176
|
result = []
|
|
177
|
+
visited = 0
|
|
179
178
|
value.each do |item|
|
|
179
|
+
if visited == @max_array_items
|
|
180
|
+
truncated = true
|
|
181
|
+
break
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
visited += 1
|
|
180
185
|
next if raw_omitted_value?(item)
|
|
181
186
|
|
|
182
|
-
child = walk_value(item, depth + 1, nil,
|
|
187
|
+
child = walk_value(item, depth + 1, nil, nil)
|
|
183
188
|
child_truncated = consume_truncated
|
|
184
189
|
next if omitted_value?(child)
|
|
185
190
|
|
|
186
|
-
if result.length >= @max_array_items
|
|
187
|
-
fields = append_truncation_field(fields, "array_items")
|
|
188
|
-
break
|
|
189
|
-
end
|
|
190
|
-
|
|
191
191
|
result << child
|
|
192
|
-
|
|
192
|
+
truncated = true if child_truncated
|
|
193
193
|
end
|
|
194
|
-
finish_array(result,
|
|
194
|
+
finish_array(result, truncated && "array_items")
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
-
def
|
|
198
|
-
|
|
199
|
-
def raw_omitted_value?(_value) = false
|
|
200
|
-
|
|
201
|
-
def key_value(key)
|
|
202
|
-
clear_truncated(key.is_a?(String) ? copy_string(key) : key)
|
|
203
|
-
end
|
|
197
|
+
def key_value(key) = key
|
|
204
198
|
|
|
205
|
-
def record_hash_truncation(fields,
|
|
206
|
-
return fields unless
|
|
199
|
+
def record_hash_truncation(fields, key, truncated)
|
|
200
|
+
return fields unless truncated
|
|
207
201
|
|
|
208
|
-
append_truncation_field(fields,
|
|
202
|
+
append_truncation_field(fields, key.to_s)
|
|
209
203
|
end
|
|
210
204
|
|
|
211
205
|
def finish_hash(result, fields)
|
|
212
|
-
return
|
|
206
|
+
return result unless fields
|
|
213
207
|
|
|
214
208
|
result[@truncation_key] = truncation_metadata(fields) if @truncation_key
|
|
215
209
|
mark_truncated(result)
|
|
216
210
|
end
|
|
217
211
|
|
|
218
212
|
def finish_array(result, fields)
|
|
219
|
-
return
|
|
213
|
+
return result unless fields
|
|
220
214
|
|
|
221
215
|
result << { @truncation_key => truncation_metadata(fields) } if @truncation_key
|
|
222
216
|
mark_truncated(result)
|
|
223
217
|
end
|
|
224
218
|
|
|
225
|
-
def truncation_metadata(fields)
|
|
219
|
+
def truncation_metadata(fields, key_style: :string)
|
|
226
220
|
self.class.truncation_metadata(
|
|
227
221
|
fields,
|
|
222
|
+
key_style: key_style,
|
|
228
223
|
max_array_items: @max_array_items,
|
|
229
224
|
max_depth: @max_depth,
|
|
230
225
|
max_hash_keys: @max_hash_keys,
|
|
@@ -233,7 +228,7 @@ module Julewire
|
|
|
233
228
|
end
|
|
234
229
|
|
|
235
230
|
def string_value(value)
|
|
236
|
-
return
|
|
231
|
+
return copy_string(value) if value.bytesize <= @max_string_bytes
|
|
237
232
|
|
|
238
233
|
mark_truncated("#{value.byteslice(0, @max_string_bytes).scrub("?")}#{TRUNCATED_SUFFIX}")
|
|
239
234
|
end
|
|
@@ -243,11 +238,6 @@ module Julewire
|
|
|
243
238
|
value
|
|
244
239
|
end
|
|
245
240
|
|
|
246
|
-
def clear_truncated(value)
|
|
247
|
-
@last_truncated = false
|
|
248
|
-
value
|
|
249
|
-
end
|
|
250
|
-
|
|
251
241
|
def consume_truncated
|
|
252
242
|
truncated = @last_truncated
|
|
253
243
|
@last_truncated = false
|
|
@@ -259,8 +249,7 @@ module Julewire
|
|
|
259
249
|
end
|
|
260
250
|
|
|
261
251
|
def append_truncation_field(fields, field)
|
|
262
|
-
(fields
|
|
263
|
-
fields
|
|
252
|
+
TruncationMetadata.append_field(fields, field)
|
|
264
253
|
end
|
|
265
254
|
|
|
266
255
|
def path_for(parent_path, key)
|
|
@@ -27,23 +27,21 @@ module Julewire
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def compact_value!(value)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
if value.is_a?(Hash)
|
|
31
|
+
compact_hash!(value)
|
|
32
|
+
elsif value.is_a?(Array)
|
|
33
|
+
compact_array!(value)
|
|
34
|
+
else
|
|
35
|
+
value
|
|
36
|
+
end
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def compact_hash!(value)
|
|
37
40
|
with_traversal_container(value, value) do
|
|
38
41
|
value.each do |key, item|
|
|
39
42
|
compacted = compact_value!(item)
|
|
40
|
-
if self.class.omitted?(compacted)
|
|
41
|
-
value.delete(key)
|
|
42
|
-
elsif !compacted.equal?(item)
|
|
43
|
-
value[key] = compacted
|
|
44
|
-
end
|
|
43
|
+
value.delete(key) if self.class.omitted?(compacted)
|
|
45
44
|
end
|
|
46
|
-
value
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
|
|
@@ -57,7 +55,7 @@ module Julewire
|
|
|
57
55
|
value[index] = compacted
|
|
58
56
|
index += 1
|
|
59
57
|
end
|
|
60
|
-
value.slice!(index, value.length
|
|
58
|
+
value.slice!(index, value.length)
|
|
61
59
|
value
|
|
62
60
|
end
|
|
63
61
|
end
|
|
@@ -7,9 +7,46 @@ module Julewire
|
|
|
7
7
|
include ValueTraversal
|
|
8
8
|
|
|
9
9
|
class << self
|
|
10
|
-
def call(value, max_depth:
|
|
10
|
+
def call(value, max_depth: NORMALIZATION_MAX_DEPTH, trust_frozen: false, validate_symbol_keys: false)
|
|
11
|
+
validate_symbol_keys(value) if validate_symbol_keys
|
|
11
12
|
new(max_depth, trust_frozen: trust_frozen).call(value)
|
|
12
13
|
end
|
|
14
|
+
|
|
15
|
+
def validate_symbol_keys(value)
|
|
16
|
+
# WeakMap is identity-keyed and accepts new keys while enumerating,
|
|
17
|
+
# so reachable containers form one finite work inventory without a
|
|
18
|
+
# separate cycle-skip branch.
|
|
19
|
+
containers = ObjectSpace::WeakMap.new
|
|
20
|
+
containers[value] = nil if container?(value)
|
|
21
|
+
containers.each_key do |current|
|
|
22
|
+
if current.is_a?(Hash)
|
|
23
|
+
current.each do |key, item|
|
|
24
|
+
validate_symbol_key!(key)
|
|
25
|
+
containers[item] = nil if container?(item)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
current.each { containers[it] = nil if container?(it) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_symbol_hash(value)
|
|
35
|
+
raise TypeError, "owned data must be a Hash" unless value.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
validate_symbol_keys(value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def container?(value)
|
|
43
|
+
value.is_a?(Hash) || value.is_a?(Array)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_symbol_key!(key)
|
|
47
|
+
raise TypeError, Fields::Internal::RECORD_STRING_KEY_ERROR if key.is_a?(String)
|
|
48
|
+
raise TypeError, Fields::Internal::RECORD_SYMBOL_KEY_ERROR unless key.instance_of?(Symbol)
|
|
49
|
+
end
|
|
13
50
|
end
|
|
14
51
|
|
|
15
52
|
def initialize(max_depth, trust_frozen:)
|
|
@@ -24,7 +61,7 @@ module Julewire
|
|
|
24
61
|
private
|
|
25
62
|
|
|
26
63
|
def freeze_value(value, depth)
|
|
27
|
-
|
|
64
|
+
value.freeze if value.is_a?(String)
|
|
28
65
|
return value if @trust_frozen && value.frozen?
|
|
29
66
|
return freeze_container(value, depth) if value.is_a?(Hash) || value.is_a?(Array)
|
|
30
67
|
|
|
@@ -32,7 +69,7 @@ module Julewire
|
|
|
32
69
|
end
|
|
33
70
|
|
|
34
71
|
def freeze_container(value, depth)
|
|
35
|
-
return Serializer::MAX_DEPTH_VALUE
|
|
72
|
+
return Serializer::MAX_DEPTH_VALUE if depth_limited?(depth)
|
|
36
73
|
|
|
37
74
|
with_traversal_container(value, value) do
|
|
38
75
|
value.is_a?(Hash) ? freeze_hash(value, depth) : freeze_array(value, depth)
|
|
@@ -40,11 +77,13 @@ module Julewire
|
|
|
40
77
|
end
|
|
41
78
|
|
|
42
79
|
def depth_limited?(depth)
|
|
43
|
-
|
|
80
|
+
depth == @max_depth
|
|
44
81
|
end
|
|
45
82
|
|
|
46
83
|
def freeze_hash(value, depth)
|
|
47
|
-
value.each
|
|
84
|
+
value.each do |key, item|
|
|
85
|
+
freeze_child(value, key, item, depth)
|
|
86
|
+
end
|
|
48
87
|
value.freeze
|
|
49
88
|
end
|
|
50
89
|
|
|
@@ -55,7 +94,7 @@ module Julewire
|
|
|
55
94
|
|
|
56
95
|
def freeze_child(value, key, item, depth)
|
|
57
96
|
frozen = freeze_value(item, depth + 1)
|
|
58
|
-
value[key] = frozen unless value.frozen?
|
|
97
|
+
value[key] = frozen unless value.frozen?
|
|
59
98
|
end
|
|
60
99
|
end
|
|
61
100
|
end
|
|
@@ -9,7 +9,6 @@ module Julewire
|
|
|
9
9
|
raise TypeError, "value must be a String" unless value.is_a?(String)
|
|
10
10
|
|
|
11
11
|
return value if valid_utf8?(value) || valid_ascii_only?(value)
|
|
12
|
-
return value.scrub("?") if utf8?(value)
|
|
13
12
|
|
|
14
13
|
encode_utf8(value)
|
|
15
14
|
rescue EncodingError
|
|
@@ -23,7 +22,7 @@ module Julewire
|
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def valid_ascii_only?(value)
|
|
26
|
-
value.ascii_only?
|
|
25
|
+
value.ascii_only?
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def utf8?(value)
|
|
@@ -31,7 +30,7 @@ module Julewire
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def encode_utf8(value)
|
|
34
|
-
value.encode(Encoding::UTF_8, invalid: :replace,
|
|
33
|
+
value.encode(Encoding::UTF_8, invalid: :replace, replace: "?")
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
end
|
|
@@ -9,7 +9,7 @@ module Julewire
|
|
|
9
9
|
DEFAULT_MAX_CAUSE_DEPTH = 5
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
|
-
def call(error, max_backtrace_lines:
|
|
12
|
+
def call(error, max_backtrace_lines: MAX_BACKTRACE_LINES, max_cause_depth: DEFAULT_MAX_CAUSE_DEPTH)
|
|
13
13
|
new(
|
|
14
14
|
max_backtrace_lines: max_backtrace_lines,
|
|
15
15
|
max_cause_depth: max_cause_depth
|
|
@@ -32,7 +32,7 @@ module Julewire
|
|
|
32
32
|
def shape_exception(error, depth)
|
|
33
33
|
return error unless error.is_a?(Exception)
|
|
34
34
|
|
|
35
|
-
with_traversal_container(error,
|
|
35
|
+
with_traversal_container(error, CIRCULAR_REFERENCE) do
|
|
36
36
|
exception_hash(error, depth)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -50,7 +50,7 @@ module Julewire
|
|
|
50
50
|
cause = exception_cause(error)
|
|
51
51
|
next unless cause
|
|
52
52
|
|
|
53
|
-
if depth
|
|
53
|
+
if depth == @max_cause_depth
|
|
54
54
|
result[:cause_truncated] = true
|
|
55
55
|
else
|
|
56
56
|
result[:cause] = shape_exception(cause, depth + 1)
|
|
@@ -59,20 +59,21 @@ module Julewire
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def class_name(error)
|
|
62
|
-
error.class.
|
|
62
|
+
error.class.to_s
|
|
63
63
|
rescue StandardError
|
|
64
64
|
"Exception"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def error_message(error)
|
|
68
68
|
message = error.message
|
|
69
|
-
message.
|
|
69
|
+
message.instance_of?(String) ? message.dup : message.to_s
|
|
70
70
|
rescue StandardError
|
|
71
71
|
"[Unavailable]"
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def backtrace(error)
|
|
75
|
-
@backtrace_limiter.call(backtrace:
|
|
75
|
+
limited = @backtrace_limiter.call(backtrace: Fields::FieldSet.deep_dup(error.backtrace))
|
|
76
|
+
limited.fetch(:backtrace)
|
|
76
77
|
rescue StandardError
|
|
77
78
|
nil
|
|
78
79
|
end
|
|
@@ -13,7 +13,7 @@ module Julewire
|
|
|
13
13
|
max_array_items: Serializer::DEFAULT_MAX_ARRAY_ITEMS,
|
|
14
14
|
max_hash_keys: Serializer::DEFAULT_MAX_HASH_KEYS,
|
|
15
15
|
compact_empty: true,
|
|
16
|
-
max_backtrace_lines:
|
|
16
|
+
max_backtrace_lines: MAX_BACKTRACE_LINES,
|
|
17
17
|
append_newline: true
|
|
18
18
|
)
|
|
19
19
|
@max_depth = max_depth
|
|
@@ -23,6 +23,7 @@ module Julewire
|
|
|
23
23
|
@compact_empty = compact_empty
|
|
24
24
|
@max_backtrace_lines = max_backtrace_lines
|
|
25
25
|
@line_suffix = append_newline ? "\n" : ""
|
|
26
|
+
# `append_newline` is suffix-only; it does not change serializer shape.
|
|
26
27
|
@serializer_key = [
|
|
27
28
|
@max_depth,
|
|
28
29
|
@max_string_bytes,
|
|
@@ -30,11 +31,11 @@ module Julewire
|
|
|
30
31
|
@max_hash_keys,
|
|
31
32
|
@compact_empty,
|
|
32
33
|
@max_backtrace_lines
|
|
33
|
-
]
|
|
34
|
+
]
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def call(payload)
|
|
37
|
-
JSON.generate(serialized_payload(payload)
|
|
38
|
+
JSON.generate(serialized_payload(payload)).tap do |json|
|
|
38
39
|
json << @line_suffix
|
|
39
40
|
end
|
|
40
41
|
end
|
|
@@ -42,14 +43,11 @@ module Julewire
|
|
|
42
43
|
private
|
|
43
44
|
|
|
44
45
|
def serialized_payload(payload)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def cached_serializer
|
|
52
|
-
SerializerPool.serializer(:julewire_core_json_encoder_serializers, @serializer_key) { build_serializer }
|
|
46
|
+
SerializerPool.serialize(
|
|
47
|
+
:julewire_core_json_encoder_serializers,
|
|
48
|
+
@serializer_key,
|
|
49
|
+
payload
|
|
50
|
+
) { build_serializer }
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
def build_serializer
|
|
@@ -59,8 +57,7 @@ module Julewire
|
|
|
59
57
|
max_array_items: @max_array_items,
|
|
60
58
|
max_hash_keys: @max_hash_keys,
|
|
61
59
|
compact_empty: @compact_empty,
|
|
62
|
-
max_backtrace_lines: @max_backtrace_lines
|
|
63
|
-
copy_strings: false
|
|
60
|
+
max_backtrace_lines: @max_backtrace_lines
|
|
64
61
|
)
|
|
65
62
|
end
|
|
66
63
|
end
|