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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/docs/advanced-configuration.md +2 -1
  4. data/docs/configuration.md +14 -7
  5. data/docs/context-and-propagation.md +26 -4
  6. data/docs/contracts.md +25 -50
  7. data/docs/extensions-and-api.md +87 -88
  8. data/docs/health-schema.md +12 -2
  9. data/docs/internals.md +24 -23
  10. data/docs/outputs-and-lifecycle.md +15 -8
  11. data/docs/records-and-data-policy.md +38 -7
  12. data/lib/julewire/core/cli/doctor.rb +20 -16
  13. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  14. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  15. data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
  16. data/lib/julewire/core/cli/log_formats.rb +9 -9
  17. data/lib/julewire/core/cli/tail.rb +19 -14
  18. data/lib/julewire/core/cli/transcode.rb +4 -5
  19. data/lib/julewire/core/cli.rb +1 -1
  20. data/lib/julewire/core/configuration.rb +1 -1
  21. data/lib/julewire/core/context_store.rb +56 -87
  22. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  23. data/lib/julewire/core/destinations/collection.rb +8 -16
  24. data/lib/julewire/core/destinations/definition.rb +1 -9
  25. data/lib/julewire/core/destinations/destination.rb +16 -66
  26. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  27. data/lib/julewire/core/destinations/registry.rb +1 -1
  28. data/lib/julewire/core/destinations/sink.rb +0 -1
  29. data/lib/julewire/core/destinations/synchronized_output.rb +54 -17
  30. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  31. data/lib/julewire/core/destinations/write_step.rb +5 -5
  32. data/lib/julewire/core/destinations.rb +0 -7
  33. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  34. data/lib/julewire/core/diagnostics/doctor.rb +22 -15
  35. data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
  36. data/lib/julewire/core/diagnostics/health.rb +41 -57
  37. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  38. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  39. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  40. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  41. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  42. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  43. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  44. data/lib/julewire/core/execution/boundary.rb +2 -8
  45. data/lib/julewire/core/execution/handle.rb +14 -17
  46. data/lib/julewire/core/execution/lineage.rb +26 -62
  47. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  48. data/lib/julewire/core/execution/scope.rb +17 -22
  49. data/lib/julewire/core/execution/scope_fields.rb +4 -13
  50. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  51. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  52. data/lib/julewire/core/execution/summary_state.rb +16 -19
  53. data/lib/julewire/core/execution/view.rb +8 -17
  54. data/lib/julewire/core/facade_methods.rb +67 -65
  55. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  56. data/lib/julewire/core/fields/bags.rb +23 -27
  57. data/lib/julewire/core/fields/field_set.rb +52 -16
  58. data/lib/julewire/core/fields/field_stack.rb +69 -109
  59. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  60. data/lib/julewire/core/fields/internal.rb +35 -26
  61. data/lib/julewire/core/fields/lookup.rb +9 -13
  62. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  63. data/lib/julewire/core/fields/stack_set.rb +9 -14
  64. data/lib/julewire/core/fields/static_labels.rb +1 -1
  65. data/lib/julewire/core/integration/configurable.rb +4 -3
  66. data/lib/julewire/core/integration/destination_health.rb +28 -7
  67. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  68. data/lib/julewire/core/integration/facade.rb +12 -15
  69. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  70. data/lib/julewire/core/integration/health.rb +0 -3
  71. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  72. data/lib/julewire/core/integration/protocol.rb +20 -0
  73. data/lib/julewire/core/integration/settings.rb +4 -4
  74. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  75. data/lib/julewire/core/integration/subscription.rb +3 -2
  76. data/lib/julewire/core/integration/values.rb +0 -17
  77. data/lib/julewire/core/local_storage.rb +3 -19
  78. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  79. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  80. data/lib/julewire/core/processing/match.rb +6 -4
  81. data/lib/julewire/core/processing/pipeline.rb +46 -40
  82. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  83. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  84. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  85. data/lib/julewire/core/processing/sampling.rb +1 -3
  86. data/lib/julewire/core/processing.rb +2 -2
  87. data/lib/julewire/core/propagation/carrier.rb +73 -19
  88. data/lib/julewire/core/propagation.rb +18 -4
  89. data/lib/julewire/core/records/build_input.rb +75 -0
  90. data/lib/julewire/core/records/console_formatter.rb +6 -6
  91. data/lib/julewire/core/records/deconstruct.rb +1 -1
  92. data/lib/julewire/core/records/display_message.rb +2 -4
  93. data/lib/julewire/core/records/draft.rb +127 -258
  94. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  95. data/lib/julewire/core/records/metadata.rb +4 -3
  96. data/lib/julewire/core/records/public_projection.rb +2 -2
  97. data/lib/julewire/core/records/raw_input.rb +1 -1
  98. data/lib/julewire/core/records/record.rb +30 -18
  99. data/lib/julewire/core/records/severity.rb +7 -15
  100. data/lib/julewire/core/runtime.rb +87 -72
  101. data/lib/julewire/core/runtime_registry.rb +20 -15
  102. data/lib/julewire/core/runtime_state.rb +5 -3
  103. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  104. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  105. data/lib/julewire/core/serialization/bounded_transform.rb +3 -2
  106. data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
  107. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  108. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  109. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  110. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  111. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  112. data/lib/julewire/core/serialization/serializer.rb +22 -49
  113. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  114. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  115. data/lib/julewire/core/serialization/truncation_metadata.rb +157 -0
  116. data/lib/julewire/core/serialization/value_copy.rb +183 -109
  117. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  118. data/lib/julewire/core/testing.rb +6 -56
  119. data/lib/julewire/core/validation.rb +2 -3
  120. data/lib/julewire/core/version.rb +1 -1
  121. data/lib/julewire/core.rb +2 -7
  122. metadata +7 -19
  123. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  124. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  125. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  126. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  127. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  128. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  129. data/lib/julewire/core/testing/chaos.rb +0 -80
  130. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  131. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  132. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  133. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  134. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  135. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  136. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  137. data/lib/julewire/core/testing/contracts.rb +0 -24
  138. data/lib/julewire/core/testing/coverage.rb +0 -58
  139. data/lib/julewire/core/testing/test_reports.rb +0 -78
@@ -12,15 +12,14 @@ module Julewire
12
12
 
13
13
  class << self
14
14
  def build( # rubocop:disable Metrics/ParameterLists -- Record construction has fixed public sections.
15
- input = {},
15
+ input = nil,
16
16
  context:,
17
17
  scope:,
18
- attributes: {},
19
- neutral: {},
20
- carry: {},
21
- static_labels: {},
22
- freeze_sections: true,
23
- error_backtrace_lines: Core::MAX_BACKTRACE_LINES,
18
+ attributes: nil,
19
+ neutral: nil,
20
+ carry: nil,
21
+ static_labels: nil,
22
+ error_backtrace_lines: DEFAULT_ERROR_BACKTRACE_LINES,
24
23
  invalid_severity_reporter: Diagnostics::InvalidSeverityReporter
25
24
  )
26
25
  build_with(
@@ -32,24 +31,21 @@ module Julewire
32
31
  static_labels: static_labels,
33
32
  scope: scope,
34
33
  invalid_severity_reporter: invalid_severity_reporter,
35
- options: BuildOptions.defensive(
36
- freeze_sections: freeze_sections,
37
- error_backtrace_lines: error_backtrace_lines
38
- )
34
+ fields_owned: false,
35
+ input_owned: false,
36
+ error_backtrace_lines: error_backtrace_lines
39
37
  )
40
38
  end
41
39
 
42
40
  def build_pipeline_owned( # rubocop:disable Metrics/ParameterLists -- Record construction has fixed public sections.
43
- input = {},
41
+ input,
44
42
  context:,
45
43
  scope:,
46
- attributes: {},
47
- neutral: {},
48
- carry: {},
49
- static_labels: {},
44
+ attributes: nil,
45
+ neutral: nil,
46
+ carry: nil,
50
47
  input_owned: false,
51
- freeze_sections: true,
52
- error_backtrace_lines: Core::MAX_BACKTRACE_LINES,
48
+ error_backtrace_lines: DEFAULT_ERROR_BACKTRACE_LINES,
53
49
  invalid_severity_reporter: Diagnostics::InvalidSeverityReporter
54
50
  )
55
51
  build_with(
@@ -58,21 +54,20 @@ module Julewire
58
54
  neutral: neutral,
59
55
  attributes: attributes,
60
56
  carry: carry,
61
- static_labels: static_labels,
57
+ static_labels: nil,
62
58
  scope: scope,
63
59
  invalid_severity_reporter: invalid_severity_reporter,
64
- options: BuildOptions.pipeline_owned(
65
- input_owned: input_owned,
66
- freeze_sections: freeze_sections,
67
- error_backtrace_lines: error_backtrace_lines
68
- )
60
+ fields_owned: true,
61
+ input_owned: input_owned,
62
+ error_backtrace_lines: error_backtrace_lines
69
63
  )
70
64
  end
71
65
 
72
66
  private
73
67
 
74
68
  def build_with(input, context:, neutral:, attributes:, carry:, static_labels:, scope:, # rubocop:disable Metrics/ParameterLists
75
- invalid_severity_reporter:, options:)
69
+ invalid_severity_reporter:, fields_owned:, input_owned:,
70
+ error_backtrace_lines:)
76
71
  builder = Builder.new(
77
72
  input,
78
73
  context: context,
@@ -82,94 +77,45 @@ module Julewire
82
77
  static_labels: static_labels,
83
78
  scope: scope,
84
79
  invalid_severity_reporter: invalid_severity_reporter,
85
- options: options
80
+ fields_owned: fields_owned,
81
+ input_owned: input_owned,
82
+ error_backtrace_lines: error_backtrace_lines
86
83
  )
87
- new(builder.to_h, lineage: builder.lineage, freeze_sections: options.freeze_sections)
84
+ new(builder.to_h, lineage: builder.lineage)
88
85
  end
89
86
 
90
87
  public
91
88
 
92
- def from_normalized_hash(data, lineage: nil, freeze_sections: true)
93
- normalized = Fields::FieldSet.deep_symbolize_keys(data)
94
- lineage ||= Execution::Lineage.from_execution_hash(normalized[:execution])
95
- normalized[:execution] = Execution::Lineage.clean_lazy_relationship_hash(normalized[:execution])
96
- normalized = Fields::Internal.frozen_deep_symbolize_keys(normalized) if freeze_sections
97
- new(
98
- normalized,
99
- lineage: lineage,
100
- freeze_sections: freeze_sections
101
- )
89
+ def from_normalized_hash(data, lineage: nil)
90
+ Record.validate_normalized_hash!(data)
91
+ normalized = Fields::FieldSet.deep_dup_owned(data)
92
+ execution = normalized.fetch(:execution)
93
+ lineage ||= Execution::Lineage.from_execution_hash(execution)
94
+ normalized[:execution] = Execution::Lineage.clean_normalized_lazy_relationship_hash(execution)
95
+ new(normalized, lineage: lineage)
102
96
  end
103
97
 
104
- def from_record(record, freeze_sections: true)
98
+ def from_record(record)
105
99
  Record.validate_normalized!(record)
106
- from_normalized_hash(record.to_h, lineage: record.lineage, freeze_sections: freeze_sections)
100
+ from_normalized_hash(record.to_h, lineage: record.lineage)
107
101
  end
108
102
  end
103
+ private_class_method :new
109
104
 
105
+ DEFAULT_ERROR_BACKTRACE_LINES = Core::MAX_BACKTRACE_LINES
110
106
  LINEAGE_IDENTITY_KEYS = %i[type id depth root parent].freeze
111
- private_constant :LINEAGE_IDENTITY_KEYS
112
-
113
- BuildOptions = Data.define(:fields_owned, :input_owned, :freeze_sections, :error_backtrace_lines) do
114
- class << self
115
- def defensive(freeze_sections:, error_backtrace_lines:)
116
- return default_defensive.fetch(freeze_sections) if default_backtrace_lines?(error_backtrace_lines)
117
-
118
- new(false, false, freeze_sections, error_backtrace_lines)
119
- end
120
-
121
- def pipeline_owned(input_owned:, freeze_sections:, error_backtrace_lines:)
122
- if default_backtrace_lines?(error_backtrace_lines)
123
- return default_pipeline.fetch(input_owned).fetch(freeze_sections)
124
- end
125
-
126
- new(true, input_owned, freeze_sections, error_backtrace_lines)
127
- end
128
-
129
- private
130
-
131
- def default_backtrace_lines?(value)
132
- value == Core::MAX_BACKTRACE_LINES
133
- end
134
-
135
- def default_defensive
136
- # Hot-path defaults are reused by every emitted record.
137
- @default_defensive ||= {
138
- false => new(false, false, false, Core::MAX_BACKTRACE_LINES),
139
- true => new(false, false, true, Core::MAX_BACKTRACE_LINES)
140
- }.freeze
141
- end
142
-
143
- def default_pipeline
144
- @default_pipeline ||= {
145
- false => {
146
- false => new(true, false, false, Core::MAX_BACKTRACE_LINES),
147
- true => new(true, false, true, Core::MAX_BACKTRACE_LINES)
148
- }.freeze,
149
- true => {
150
- false => new(true, true, false, Core::MAX_BACKTRACE_LINES),
151
- true => new(true, true, true, Core::MAX_BACKTRACE_LINES)
152
- }.freeze
153
- }.freeze
154
- end
155
- end
156
- end
157
- private_constant :BuildOptions
107
+ private_constant :DEFAULT_ERROR_BACKTRACE_LINES, :LINEAGE_IDENTITY_KEYS
158
108
 
159
- def initialize(data, lineage: nil, freeze_sections: true)
109
+ def initialize(data, lineage:)
160
110
  @data = data
161
- @freeze_sections = freeze_sections
162
- @lineage = lineage || Execution::Lineage.from_execution_hash(@data[:execution])
163
- validate!
111
+ @lineage = lineage
164
112
  end
165
113
 
166
114
  def [](key) = @data[key]
167
115
 
168
116
  def []=(key, value)
169
- @lineage = nil if key == :execution
170
- ensure_mutable_data!
171
117
  @data[key] = value
172
- @to_record = nil
118
+ @lineage = nil if key == :execution
173
119
  end
174
120
 
175
121
  def fetch(...) = @data.fetch(...)
@@ -182,17 +128,19 @@ module Julewire
182
128
 
183
129
  def each_key(&) = @data.each_key(&)
184
130
 
185
- def to_h = Fields::FieldSet.deep_dup(@data)
131
+ def to_h = Fields::FieldSet.deep_dup_owned(@data)
186
132
 
187
133
  def transform_field!(key)
188
- key = Fields::Internal.normalize_key(key)
189
- replace_transformed_field!(key, yield(@data[key]))
134
+ ensure_unfinalized!
135
+ validate_transform_key!(key, Record::REQUIRED_KEYS, kind: :field)
136
+ replace_transformed_field!(key, yield(self[key]))
190
137
  self
191
138
  end
192
139
 
193
140
  def transform_section!(key)
194
- key = Fields::Internal.normalize_key(key)
195
- section = @data[key]
141
+ ensure_unfinalized!
142
+ validate_transform_key!(key, Record::HASH_SECTIONS, kind: :section)
143
+ section = self[key]
196
144
  replacement = yield(section)
197
145
  raise TypeError, "record #{key} must be a Hash" unless replacement.is_a?(Hash)
198
146
 
@@ -201,12 +149,12 @@ module Julewire
201
149
  end
202
150
 
203
151
  def transform_record!
152
+ ensure_unfinalized!
204
153
  previous_lineage = @lineage
205
- previous_identity = execution_lineage_identity(@data[:execution])
154
+ previous_identity = execution_lineage_identity(fetch(:execution))
206
155
  replacement = yield(@data)
207
156
  @lineage = replacement_lineage_for(previous_lineage, previous_identity, replacement)
208
- @data = replacement
209
- @to_record = nil
157
+ @data = replacement.dup
210
158
  self
211
159
  end
212
160
 
@@ -219,123 +167,86 @@ module Julewire
219
167
  self
220
168
  end
221
169
 
222
- def lineage = (@lineage ||= Execution::Lineage.from_execution_hash(@data[:execution]))
170
+ def lineage = (@lineage ||= Execution::Lineage.from_execution_hash(fetch(:execution)))
223
171
 
224
172
  def to_record
225
- @to_record ||= Record.from_owned_hash(@data, lineage: @lineage, trust_frozen: @freeze_sections)
173
+ return @to_record if @to_record
174
+
175
+ record = Record.from_owned_hash(@data, lineage: @lineage)
176
+ @lineage = record.lineage
177
+ @to_record = record
178
+ freeze
179
+ record
226
180
  end
227
181
 
228
182
  private
229
183
 
184
+ def validate_transform_key!(key, allowed, kind:)
185
+ raise TypeError, "record transform #{kind} must be a Symbol" unless key.instance_of?(Symbol)
186
+ return if allowed.include?(key)
187
+
188
+ raise ArgumentError, "unknown record #{kind}: #{key}"
189
+ end
190
+
230
191
  def replace_transformed_field!(key, value)
231
- preserve_lineage = transformed_field_lineage(key, value)
232
- ensure_mutable_data!
233
- @data[key] = value
234
- @lineage = preserve_lineage if key == :execution
235
- @to_record = nil
192
+ if key == :execution
193
+ replace_transformed_execution!(value)
194
+ else
195
+ self[key] = value
196
+ end
236
197
  end
237
198
 
238
- def ensure_mutable_data!
239
- @data = Fields::FieldSet.deep_dup(@data) if @data.frozen?
199
+ def replace_transformed_execution!(value)
200
+ preserve_lineage = transformed_execution_lineage(value)
201
+ self[:execution] = value
202
+ @lineage = preserve_lineage
240
203
  end
241
204
 
242
- def transformed_field_lineage(key, value)
243
- return @lineage unless key == :execution
205
+ def ensure_unfinalized!
206
+ raise FrozenError, "can't transform a finalized record draft" if frozen?
207
+ end
244
208
 
209
+ def transformed_execution_lineage(value)
245
210
  replacement_lineage_for(
246
211
  @lineage,
247
- execution_lineage_identity(@data[:execution]),
212
+ execution_lineage_identity(fetch(:execution)),
248
213
  @data.merge(execution: value)
249
214
  )
250
215
  end
251
216
 
252
217
  def replacement_lineage_for(lineage, previous, data)
253
- return unless lineage
254
-
255
- current = data.is_a?(Hash) ? execution_lineage_identity(data[:execution]) : nil
218
+ current = execution_lineage_identity(data[:execution]) if data.is_a?(Hash)
256
219
  lineage if previous == current
257
220
  end
258
221
 
259
222
  def execution_lineage_identity(execution)
260
223
  return unless execution.is_a?(Hash)
261
224
 
262
- normalized = Fields::FieldSet.deep_symbolize_keys(execution)
263
225
  LINEAGE_IDENTITY_KEYS.each_with_object({}) do |key, identity|
264
- identity[key] = normalized[key] if normalized.key?(key)
265
- end
266
- end
267
-
268
- class BuildInput
269
- # Normalizes raw emit input into draft top-level fields plus payload.
270
- RECORD_INPUT_KEYS = Record::REQUIRED_KEYS.freeze
271
- RECORD_INPUT_KEY_SET = RECORD_INPUT_KEYS.to_h { [it, true] }.freeze
272
- private_constant :RECORD_INPUT_KEYS
273
- private_constant :RECORD_INPUT_KEY_SET
274
-
275
- class << self
276
- def call(input, owned:)
277
- return {} if input.nil?
278
- return input if owned && input.is_a?(Hash)
279
- return shallow_symbolize(input) if RawInput.hash_input?(input)
280
-
281
- { message: input.to_s }
282
- end
283
-
284
- private
285
-
286
- def shallow_symbolize(input)
287
- normalized = {}
288
- payload_fields = {}
289
- input.each do |key, raw_value|
290
- normalized_key = Fields::Internal.normalize_key(key)
291
- value = raw_value.equal?(input) ? CIRCULAR_REFERENCE : raw_value
292
- if RECORD_INPUT_KEY_SET.key?(normalized_key)
293
- normalized[normalized_key] = value
294
- else
295
- payload_fields[normalized_key] = value
296
- end
297
- end
298
- merge_unknown_payload!(normalized, payload_fields)
299
- normalized
300
- end
301
-
302
- def merge_unknown_payload!(normalized, payload_fields)
303
- return if payload_fields.empty?
304
-
305
- normalized[:payload] = if normalized.key?(:payload)
306
- merge_payload_input(normalized[:payload], payload_fields)
307
- else
308
- payload_fields
309
- end
310
- end
311
-
312
- def merge_payload_input(explicit_payload, unknown_payload)
313
- if explicit_payload.is_a?(Hash)
314
- Fields::FieldSet.merge(unknown_payload, explicit_payload)
315
- else
316
- Fields::FieldSet.merge(unknown_payload, Fields::FieldSet::VALUE_KEY => explicit_payload)
317
- end
318
- end
226
+ identity[key] = execution.fetch(key) if execution.key?(key)
319
227
  end
320
228
  end
321
- private_constant :BuildInput
322
229
 
323
230
  class Builder
324
231
  EMPTY_HASH = {}.freeze
325
232
  private_constant :EMPTY_HASH
326
233
 
327
- def initialize(input = {}, context:, neutral:, attributes:, carry:, static_labels:, scope:, # rubocop:disable Metrics/ParameterLists
328
- invalid_severity_reporter:, options:)
329
- @input_owned = options.input_owned
330
- @input = BuildInput.call(input, owned: @input_owned)
234
+ def initialize(input, context:, neutral:, attributes:, carry:, static_labels:, scope:, # rubocop:disable Metrics/ParameterLists
235
+ invalid_severity_reporter:, fields_owned:, input_owned:,
236
+ error_backtrace_lines:)
237
+ @input_owned = input_owned
238
+ @input = if @input_owned
239
+ BuildInput.validate_owned(input)
240
+ else
241
+ BuildInput.normalize_public(input)
242
+ end
331
243
  @context = context || {}
332
244
  @neutral = neutral || {}
333
245
  @attributes = attributes || {}
334
246
  @carry = carry || {}
335
247
  @static_labels = static_labels || {}
336
- @fields_owned = options.fields_owned
337
- @freeze_sections = options.freeze_sections
338
- @error_backtrace_lines = options.error_backtrace_lines
248
+ @fields_owned = fields_owned
249
+ @error_backtrace_lines = error_backtrace_lines
339
250
  @invalid_severity_reporter = invalid_severity_reporter
340
251
  @scope = scope
341
252
  end
@@ -356,7 +267,7 @@ module Julewire
356
267
  def base_record(source, event)
357
268
  {
358
269
  timestamp: timestamp_value,
359
- severity: severity_for(source, event),
270
+ severity: severity_value,
360
271
  kind: kind_for(value(:kind)),
361
272
  event: event,
362
273
  message: normalized_value(:message),
@@ -384,23 +295,22 @@ module Julewire
384
295
  Serialization::ValueCopy.call(raw_value.nil? ? Time.now.utc : raw_value, freeze_values: true)
385
296
  end
386
297
 
387
- def severity_for(source, event)
388
- return normalize_record_severity(value(:severity), source: source, event: event) if present?(:severity)
298
+ def severity_value
299
+ return normalize_record_severity(value(:severity)) if present?(:severity)
389
300
 
390
301
  :info
391
302
  end
392
303
 
393
- def normalize_record_severity(raw_value, source:, event:)
394
- Records::Severity.normalize(raw_value)
304
+ def normalize_record_severity(raw_value)
305
+ Severity.normalize(raw_value)
395
306
  rescue ArgumentError
396
307
  # Below-threshold raw inputs warn before draft construction.
397
- @invalid_severity_reporter.call(raw_value, source: source, event: event)
308
+ @invalid_severity_reporter.call(raw_value)
398
309
  :info
399
310
  end
400
311
 
401
312
  def kind_for(kind)
402
313
  return :point if kind.nil?
403
- return kind if Record::KINDS.value?(kind)
404
314
 
405
315
  Record::KINDS.fetch(kind.to_s) do
406
316
  raise ArgumentError, "unsupported record kind: #{kind.inspect}"
@@ -414,13 +324,11 @@ module Julewire
414
324
  end
415
325
 
416
326
  def base_execution_hash
417
- return scope_frozen_execution_hash if owned_frozen_scope_execution?
418
-
419
- normalized_hash(scope_execution_hash)
327
+ normalized_hash(scope_execution_hash, owned: true)
420
328
  end
421
329
 
422
330
  def input_execution_hash
423
- present?(:execution) ? hash_value(:execution) : scope_execution_hash
331
+ hash_value(:execution)
424
332
  end
425
333
 
426
334
  def context_hash
@@ -440,73 +348,34 @@ module Julewire
440
348
  end
441
349
 
442
350
  def labels_hash
443
- section_hash(labels_base, :labels)
351
+ section_hash(@static_labels.merge(scope_labels_hash || EMPTY_HASH), :labels)
444
352
  end
445
353
 
446
354
  def merge_section(base, key)
447
- return merge_owned_section(base, key) if @input_owned
448
-
449
- value = hash_value(key)
450
- value = Execution::Lineage.clean_execution_hash(value) if key == :execution
451
- return normalized_hash(value) if base.empty?
452
-
453
- base = Fields::FieldSet.deep_dup(base)
454
- merged = if key == :attributes
455
- Fields::Internal.deep_merge!(base, value)
456
- else
457
- Fields::FieldSet.merge!(base, value)
458
- end
459
- normalized_hash(merged)
460
- end
461
-
462
- def merge_owned_section(base, key)
463
355
  value = hash_value(key)
464
356
  value = clean_owned_execution_hash(value) if key == :execution
465
- return normalized_hash(value) if base.empty?
466
357
 
467
- base = Fields::FieldSet.deep_dup(base)
468
- merged = if key == :attributes
469
- Fields::Internal.deep_merge_owned!(base, value)
470
- else
471
- Fields::Internal.merge_owned!(base, value)
472
- end
473
- normalized_hash(merged)
358
+ target = normalized_hash(base, owned: @fields_owned)
359
+ if key == :attributes
360
+ Fields::Internal.deep_merge_owned!(target, value)
361
+ else
362
+ Fields::Internal.merge_owned!(target, value)
363
+ end
474
364
  end
475
365
 
476
366
  def clean_owned_execution_hash(value)
477
- return Execution::Lineage.clean_execution_hash(value) if @freeze_sections
478
-
479
367
  Execution::Lineage.clean_owned_execution_hash(value)
480
368
  end
481
369
 
482
- def section_hash(base, key)
483
- return base if owned_frozen_section?(base) && !present?(key)
484
- return normalized_hash(base) unless present?(key)
485
-
486
- merge_section(base, key)
487
- end
488
-
489
- def owned_frozen_section?(base)
490
- @fields_owned && @freeze_sections && base.is_a?(Hash) && base.frozen?
491
- end
492
-
493
- def owned_frozen_scope_execution?
494
- @scope && @fields_owned && @freeze_sections
495
- end
496
-
497
- def labels_base
498
- return scope_labels_hash if @static_labels.empty?
499
-
500
- Fields::FieldSet.merge!(Fields::FieldSet.deep_dup(@static_labels), scope_labels_hash)
501
- end
370
+ def section_hash(base, key) = merge_section(base, key)
502
371
 
503
372
  def hash_value(key)
504
- return empty_hash unless present?(key)
373
+ return {} unless present?(key)
505
374
 
506
375
  raw_value = value(key)
507
- return normalized_hash(raw_value) if raw_value.is_a?(Hash)
376
+ return normalized_input_hash(raw_value) if raw_value.is_a?(Hash)
508
377
 
509
- normalized_hash(Fields::FieldSet::VALUE_KEY => raw_value)
378
+ normalized_input_hash(Fields::FieldSet::VALUE_KEY => raw_value)
510
379
  end
511
380
 
512
381
  def value(key)
@@ -519,11 +388,8 @@ module Julewire
519
388
 
520
389
  def immutable_scalar_value(value)
521
390
  return value unless value.is_a?(String)
522
- return value if value.frozen?
523
- return value.dup unless @freeze_sections
524
- return value.freeze if @input_owned
525
391
 
526
- value.dup.freeze
392
+ value.dup
527
393
  end
528
394
 
529
395
  def present?(key)
@@ -535,39 +401,42 @@ module Julewire
535
401
  when nil
536
402
  nil
537
403
  when Exception
538
- normalized_hash(Serialization::ExceptionShape.call(error, max_backtrace_lines: @error_backtrace_lines))
404
+ Serialization::ExceptionShape.call(error, max_backtrace_lines: @error_backtrace_lines)
539
405
  when Hash
540
- normalized_hash(normalize_error_hash(error))
406
+ normalize_error_hash(error)
541
407
  else
542
- normalized_hash(message: error.to_s)
408
+ { message: immutable_scalar_value(error.to_s) }
543
409
  end
544
410
  end
545
411
 
546
412
  def normalize_error_hash(error)
547
413
  Serialization::BacktraceLimiter.call(
548
- Fields::FieldSet.deep_symbolize_keys(error),
414
+ mutable_input_hash(error),
549
415
  max_backtrace_lines: @error_backtrace_lines
550
416
  )
551
417
  end
552
418
 
553
- def normalized_hash(value)
554
- return empty_hash if value.is_a?(Hash) && value.empty?
419
+ def normalized_input_hash(value)
420
+ normalized_hash(value, owned: @input_owned)
421
+ end
555
422
 
556
- return value if @input_owned && !@freeze_sections && value.is_a?(Hash)
557
- return Fields::Internal.frozen_deep_symbolize_keys(value) if @freeze_sections
423
+ def normalized_hash(value, owned:)
424
+ return Fields::FieldSet.deep_dup_owned(value) if owned
558
425
 
559
426
  Fields::FieldSet.deep_symbolize_keys(value)
560
427
  end
561
428
 
562
- def empty_hash
563
- @freeze_sections ? EMPTY_HASH : {}
429
+ def mutable_input_hash(value)
430
+ if @input_owned
431
+ Fields::FieldSet.deep_dup_owned(value)
432
+ else
433
+ Fields::FieldSet.deep_symbolize_keys(value)
434
+ end
564
435
  end
565
436
 
566
437
  def scope_execution_hash = @scope ? @scope.frozen_execution_hash : EMPTY_HASH
567
438
 
568
- def scope_frozen_execution_hash = @scope.frozen_execution_hash
569
-
570
- def scope_labels_hash = @scope ? @scope.frozen_labels_hash : EMPTY_HASH
439
+ def scope_labels_hash = @scope&.frozen_labels_hash
571
440
  end
572
441
  private_constant :Builder
573
442
  end
@@ -24,7 +24,7 @@ module Julewire
24
24
  end
25
25
 
26
26
  def each
27
- return enum_for(:each) unless block_given?
27
+ return enum_for unless block_given?
28
28
 
29
29
  input_hash.each do |key, value|
30
30
  yield key, value unless severity_key?(key)
@@ -32,12 +32,6 @@ module Julewire
32
32
  yield :severity, @severity
33
33
  end
34
34
 
35
- def to_h
36
- each_with_object({}) do |(key, value), hash|
37
- hash[key] = value
38
- end
39
- end
40
-
41
35
  private
42
36
 
43
37
  def input_hash
@@ -66,9 +60,7 @@ module Julewire
66
60
  SeverityInput.new(severity, input)
67
61
  end
68
62
 
69
- def input?(value)
70
- value.is_a?(SeverityInput)
71
- end
63
+ def input?(value) = value.instance_of?(SeverityInput)
72
64
 
73
65
  private
74
66
 
@@ -88,8 +80,6 @@ module Julewire
88
80
  end
89
81
 
90
82
  def without_severity(input)
91
- return input unless input.is_a?(Hash)
92
-
93
83
  RawInput.without_severity_keys(input)
94
84
  end
95
85
  end
@@ -6,15 +6,16 @@ module Julewire
6
6
  module Metadata
7
7
  class << self
8
8
  def call(record)
9
- return {} unless record.respond_to?(:key?) && record.respond_to?(:[])
10
-
9
+ labels = record[:labels]
11
10
  {
12
11
  event: record[:event],
13
- labels: record[:labels].is_a?(Hash) ? Fields::FieldSet.deep_dup(record[:labels]) : {},
12
+ labels: labels.is_a?(Hash) ? Fields::FieldSet.deep_dup(labels) : {},
14
13
  logger: record[:logger],
15
14
  severity: record[:severity],
16
15
  source: record[:source]
17
16
  }.compact
17
+ rescue StandardError
18
+ {}
18
19
  end
19
20
  end
20
21
  end