julewire-core 1.0.1 → 1.1.1

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