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
@@ -4,14 +4,11 @@ module Julewire
4
4
  module Core
5
5
  module Execution
6
6
  class Scope
7
- EMPTY_HASH = {}.freeze
8
- private_constant :EMPTY_HASH
9
-
10
7
  attr_reader :finished_at
11
8
 
12
- def initialize(type:, id: nil, execution: EMPTY_HASH, execution_owned: false, summary_event: nil, # rubocop:disable Metrics/ParameterLists
13
- summary_severity: nil, summary_source: nil, attributes: EMPTY_HASH, carry: EMPTY_HASH,
14
- context: EMPTY_HASH, labels: EMPTY_HASH, neutral: EMPTY_HASH, parent: nil, started_at: nil)
9
+ def initialize(type:, id: nil, execution: nil, execution_owned: false, summary_event: nil, # rubocop:disable Metrics/ParameterLists
10
+ summary_severity: nil, summary_source: nil, attributes: nil, carry: nil,
11
+ context: nil, labels: nil, neutral: nil, parent: nil, started_at: nil)
15
12
  @identity = ScopeIdentity.new(
16
13
  type: type,
17
14
  id: id,
@@ -37,7 +34,7 @@ module Julewire
37
34
  def depth = @identity.depth
38
35
 
39
36
  def execution_hash
40
- Fields::FieldSet.deep_dup(frozen_execution_hash)
37
+ Fields::FieldSet.deep_dup_owned(frozen_execution_hash)
41
38
  end
42
39
 
43
40
  def frozen_execution_hash
@@ -45,7 +42,7 @@ module Julewire
45
42
  end
46
43
 
47
44
  def inheritable_execution_hash
48
- Fields::FieldSet.deep_dup(@execution)
45
+ Fields::FieldSet.deep_dup_owned(@execution)
49
46
  end
50
47
 
51
48
  def context_hash = @fields.context_hash
@@ -96,7 +93,7 @@ module Julewire
96
93
  end
97
94
  end
98
95
 
99
- def add_field(section, fields, owned: false)
96
+ def add_field(section, fields, owned:)
100
97
  @fields.add(section, fields, owned: owned)
101
98
  end
102
99
 
@@ -105,23 +102,22 @@ module Julewire
105
102
  @fields.delete(:carry, path)
106
103
  end
107
104
 
108
- def with_field(section, fields, owned: false, &)
109
- @fields.with(section, fields, owned: owned, &)
105
+ def with_field(section, fields, owned: false, **keyword_fields, &)
106
+ @fields.with(section, fields, owned: owned, **keyword_fields, &)
110
107
  end
111
108
 
112
109
  def with_context(fields = nil, owned: false, **keyword_fields, &)
113
- @fields.with(:context, fields, owned: owned, **keyword_fields, &)
110
+ with_field(:context, fields, owned: owned, **keyword_fields, &)
114
111
  end
115
112
 
116
113
  def with_carry(fields = nil, owned: false, **keyword_fields, &)
117
- @fields.with(:carry, fields, owned: owned, **keyword_fields, &)
114
+ with_field(:carry, fields, owned: owned, **keyword_fields, &)
118
115
  end
119
116
 
120
117
  def without_carry(path, &)
121
- normalized_path = Fields::Internal.normalize_path(path)
122
- raise ArgumentError, "carry path is required" if normalized_path.empty?
118
+ raise ArgumentError, "carry path is required" if Fields::Internal.normalize_path(path).empty?
123
119
 
124
- @fields.without(:carry, normalized_path, &)
120
+ @fields.without(:carry, path, &)
125
121
  end
126
122
 
127
123
  def add_summary(fields, owned: false)
@@ -149,12 +145,12 @@ module Julewire
149
145
  end
150
146
 
151
147
  def summary_record_input
152
- @summary_state.record_input(**summary_record_fields(timestamp: finished_at || frozen_time(Time.now.utc)))
148
+ @summary_state.record_input(**summary_record_fields(timestamp: frozen_time(Time.now.utc)))
153
149
  end
154
150
 
155
151
  def owned_summary_record_input
156
152
  @summary_state.owned_record_input(
157
- **summary_record_fields(timestamp: finished_at || frozen_time(Time.now.utc))
153
+ **summary_record_fields(timestamp: frozen_time(Time.now.utc))
158
154
  )
159
155
  end
160
156
 
@@ -186,7 +182,7 @@ module Julewire
186
182
 
187
183
  def frozen_time(value) = @identity.frozen_time(value)
188
184
 
189
- def initialize_state(context:, attributes:, labels:, carry: {}, neutral: {})
185
+ def initialize_state(context:, attributes:, labels:, carry:, neutral:)
190
186
  @fields = ScopeFields.new(
191
187
  context: context,
192
188
  carry: carry,
@@ -194,7 +190,6 @@ module Julewire
194
190
  labels: labels,
195
191
  neutral: neutral
196
192
  )
197
- @finished_at = nil
198
193
  end
199
194
 
200
195
  def summary_state(event, severity, source)
@@ -4,9 +4,6 @@ module Julewire
4
4
  module Core
5
5
  module Execution
6
6
  class ScopeFields
7
- EMPTY_HASH = {}.freeze
8
- private_constant :EMPTY_HASH
9
-
10
7
  attr_reader :stacks
11
8
 
12
9
  def initialize(context:, attributes:, labels:, carry:, neutral:)
@@ -36,18 +33,14 @@ module Julewire
36
33
  end
37
34
 
38
35
  def labels_hash
39
- return {} if @labels.empty?
40
-
41
36
  Fields::FieldSet.deep_dup(@labels)
42
37
  end
43
38
 
44
39
  def frozen_labels_hash
45
- return EMPTY_HASH if @labels.empty?
46
-
47
40
  @frozen_labels_hash ||= Fields::Internal.frozen_copy(@labels)
48
41
  end
49
42
 
50
- def add(section, fields, owned: false)
43
+ def add(section, fields, owned:)
51
44
  @stacks.add(section, fields, owned: owned)
52
45
  end
53
46
 
@@ -55,7 +48,7 @@ module Julewire
55
48
  @stacks.delete(section, path)
56
49
  end
57
50
 
58
- def with(section, fields = nil, owned: false, **keyword_fields, &)
51
+ def with(section, fields, owned:, **keyword_fields, &)
59
52
  @stacks.with(section, fields, owned: owned, **keyword_fields, &)
60
53
  end
61
54
 
@@ -66,9 +59,7 @@ module Julewire
66
59
  private
67
60
 
68
61
  def normalized_static_hash(value)
69
- return EMPTY_HASH if value.is_a?(Hash) && value.empty?
70
-
71
- Fields::FieldSet.deep_symbolize_keys(value)
62
+ Fields::FieldSet.deep_symbolize_keys(value || {})
72
63
  end
73
64
  end
74
65
  end
@@ -55,15 +55,7 @@ module Julewire
55
55
  end
56
56
 
57
57
  def freeze_identity_value(value)
58
- case value
59
- when String
60
- copy = value.frozen? ? value : value.dup
61
- copy.freeze
62
- when Symbol, Numeric, true, false, nil
63
- value
64
- else
65
- Serialization::ValueCopy.call(value, freeze_values: true)
66
- end
58
+ Serialization::ValueCopy.call(value, freeze_values: true)
67
59
  end
68
60
  end
69
61
  end
@@ -4,10 +4,8 @@ module Julewire
4
4
  module Core
5
5
  module Execution
6
6
  class ScopeSnapshot
7
- EMPTY_HASH = {}.freeze
8
- private_constant :EMPTY_HASH
9
-
10
- def initialize(execution: {}, carry: {}, attributes: {}, labels: {}, neutral: {}, lineage: nil)
7
+ def initialize(execution: {}, carry: {}, attributes: {}, labels: {}, neutral: {}, lineage: nil, owned: false)
8
+ @owned = owned
11
9
  @execution = normalized_hash(execution)
12
10
  @carry = normalized_hash(carry)
13
11
  @attributes = normalized_hash(attributes)
@@ -17,11 +15,11 @@ module Julewire
17
15
  end
18
16
 
19
17
  def execution_hash
20
- Fields::FieldSet.deep_dup(frozen_execution_hash)
18
+ Fields::FieldSet.deep_dup_owned(frozen_execution_hash)
21
19
  end
22
20
 
23
21
  def frozen_execution_hash
24
- @frozen_execution_hash ||= Fields::Internal.frozen_copy(@execution)
22
+ @frozen_execution_hash ||= Fields::Internal.frozen_owned_copy(@execution)
25
23
  end
26
24
 
27
25
  attr_reader :lineage
@@ -30,61 +28,42 @@ module Julewire
30
28
 
31
29
  def type = @execution[:type]
32
30
 
33
- def started_at = nil
31
+ def started_at; end
34
32
 
35
- def finished_at = nil
33
+ def finished_at; end
36
34
 
37
- def parent = nil
35
+ def parent; end
38
36
 
39
37
  def context_hash = {}
40
38
 
41
- def carry_hash
42
- return {} if @carry.empty?
43
-
44
- Fields::FieldSet.deep_dup(@carry)
45
- end
46
-
47
- def attributes_hash
48
- return {} if @attributes.empty?
39
+ def carry_hash = Fields::FieldSet.deep_dup_owned(@carry)
49
40
 
50
- Fields::FieldSet.deep_dup(@attributes)
51
- end
41
+ def attributes_hash = Fields::FieldSet.deep_dup_owned(@attributes)
52
42
 
53
- def neutral_hash
54
- return {} if @neutral.empty?
43
+ def neutral_hash = Fields::FieldSet.deep_dup_owned(@neutral)
55
44
 
56
- Fields::FieldSet.deep_dup(@neutral)
57
- end
58
-
59
- def labels_hash
60
- return {} if @labels.empty?
61
-
62
- Fields::FieldSet.deep_dup(@labels)
63
- end
45
+ def labels_hash = Fields::FieldSet.deep_dup_owned(@labels)
64
46
 
65
47
  def summary_hash = {}
66
48
 
67
49
  def metrics_hash = {}
68
50
 
69
- def frozen_labels_hash
70
- return EMPTY_HASH if @labels.empty?
71
-
72
- @frozen_labels_hash ||= Fields::Internal.frozen_copy(@labels)
73
- end
51
+ def frozen_labels_hash = (@frozen_labels_hash ||= Fields::Internal.frozen_owned_copy(@labels))
74
52
 
75
53
  def execution_reference_for_child
76
54
  reference = {}
77
- reference[:type] = @execution[:type] if @execution.key?(:type)
78
- reference[:id] = @execution[:id] if @execution.key?(:id)
79
- reference.empty? ? nil : Fields::Internal.frozen_copy(reference)
55
+ reference[:type] = @execution.fetch(:type) if @execution.key?(:type)
56
+ reference[:id] = @execution.fetch(:id) if @execution.key?(:id)
57
+ Fields::Internal.frozen_copy(reference) unless reference.empty?
80
58
  end
81
59
 
82
60
  private
83
61
 
84
62
  def normalized_hash(value)
85
- return EMPTY_HASH if value.is_a?(Hash) && value.empty?
63
+ return Fields::FieldSet.deep_symbolize_keys(value) unless @owned
86
64
 
87
- Fields::FieldSet.deep_symbolize_keys(value)
65
+ Serialization::DeepFreeze.validate_symbol_hash(value)
66
+ Fields::FieldSet.deep_dup_owned(value)
88
67
  end
89
68
  end
90
69
  end
@@ -16,31 +16,29 @@ module Julewire
16
16
  @attributes = {}
17
17
  @metrics = {}
18
18
  @errors = []
19
- @error_severity = nil
20
- @record_input = nil
21
19
  end
22
20
 
23
21
  def payload_hash
24
- Fields::FieldSet.deep_dup(@payload)
22
+ Fields::FieldSet.deep_dup_owned(@payload)
25
23
  end
26
24
 
27
25
  def metrics_hash
28
- Fields::FieldSet.deep_dup(@metrics)
26
+ Fields::FieldSet.deep_dup_owned(@metrics)
29
27
  end
30
28
 
31
- def add(fields, owned: false)
29
+ def add(fields, owned:)
32
30
  merge_fields!(@payload, fields, owned: owned)
33
31
  end
34
32
 
35
- def add_attributes(fields, owned: false)
33
+ def add_attributes(fields, owned:)
36
34
  deep_merge_fields!(@attributes, fields, owned: owned)
37
35
  end
38
36
 
39
- def add_neutral(fields, owned: false)
37
+ def add_neutral(fields, owned:)
40
38
  deep_merge_fields!(@neutral, fields, owned: owned)
41
39
  end
42
40
 
43
- def increment_attribute(path, by: 1)
41
+ def increment_attribute(path, by:)
44
42
  path = Fields::Internal.normalize_path(path)
45
43
  raise ArgumentError, "attribute path is required" if path.empty?
46
44
 
@@ -48,7 +46,7 @@ module Julewire
48
46
  key = path.last
49
47
  value = Fields::FieldSet.value_for(container, key, default: MISSING)
50
48
  existing = !value.equal?(MISSING)
51
- container[Fields::Internal.normalize_key(key)] = incremented_value(value, by, existing: existing)
49
+ container[key] = incremented_value(value, by, existing: existing)
52
50
  end
53
51
 
54
52
  def increment(key, by: 1)
@@ -66,7 +64,7 @@ module Julewire
66
64
  values << Fields::FieldSet.deep_dup(value)
67
65
  end
68
66
 
69
- def record_error(error, severity: nil)
67
+ def record_error(error, severity:)
70
68
  @errors << error
71
69
  @error_severity = Records::Severity.normalize(severity) unless severity.nil?
72
70
  end
@@ -90,7 +88,7 @@ module Julewire
90
88
  end
91
89
 
92
90
  def record_input(**fields)
93
- Fields::FieldSet.deep_dup(owned_record_input(**fields))
91
+ Fields::FieldSet.deep_dup_owned(owned_record_input(**fields))
94
92
  end
95
93
 
96
94
  def owned_record_input(**fields)
@@ -143,13 +141,13 @@ module Julewire
143
141
  def attributes_hash(base_attributes)
144
142
  return base_attributes if @attributes.empty?
145
143
 
146
- Fields::Internal.deep_merge(base_attributes, @attributes)
144
+ Fields::Internal.deep_merge_owned!(Fields::FieldSet.deep_dup_owned(base_attributes), @attributes)
147
145
  end
148
146
 
149
147
  def neutral_hash(base_neutral)
150
148
  return base_neutral if @neutral.empty?
151
149
 
152
- Fields::Internal.deep_merge(base_neutral, @neutral)
150
+ Fields::Internal.deep_merge_owned!(Fields::FieldSet.deep_dup_owned(base_neutral), @neutral)
153
151
  end
154
152
 
155
153
  def array_value(value, existing:)
@@ -173,23 +171,22 @@ module Julewire
173
171
  end
174
172
 
175
173
  def measurement_base(key)
176
- unless key.is_a?(String) || key.is_a?(Symbol)
174
+ unless key.is_a?(String) || key.instance_of?(Symbol)
177
175
  raise ArgumentError, "measurement key must be a String or Symbol"
178
176
  end
179
177
 
180
- base = key.to_s
178
+ base = key
181
179
  raise ArgumentError, "measurement key is required" if base.empty?
182
180
 
183
181
  base
184
182
  end
185
183
 
186
184
  def attribute_container(path)
187
- path[0...-1].reduce(@attributes) do |container, key|
188
- normalized = Fields::Internal.normalize_key(key)
189
- child = container[normalized]
185
+ path[...-1].reduce(@attributes) do |container, key|
186
+ child = container[key]
190
187
  unless child.is_a?(Hash)
191
188
  child = {}
192
- container[normalized] = child
189
+ container[key] = child
193
190
  end
194
191
  child
195
192
  end
@@ -16,15 +16,6 @@ module Julewire
16
16
  @finished_at = scope.finished_at
17
17
  @parent_scope = scope.parent
18
18
  @lineage = scope.lineage
19
- @parent = nil
20
- @execution_hash = nil
21
- @context_hash = nil
22
- @carry_hash = nil
23
- @neutral_hash = nil
24
- @attributes_hash = nil
25
- @labels_hash = nil
26
- @summary_hash = nil
27
- @metrics_hash = nil
28
19
  end
29
20
 
30
21
  def parent
@@ -33,21 +24,21 @@ module Julewire
33
24
  @parent ||= self.class.new(@parent_scope)
34
25
  end
35
26
 
36
- def execution_hash = Fields::FieldSet.deep_dup(@execution_hash ||= @scope.frozen_execution_hash)
27
+ def execution_hash = Fields::FieldSet.deep_dup_owned(@execution_hash ||= @scope.frozen_execution_hash)
37
28
 
38
- def context_hash = Fields::FieldSet.deep_dup(@context_hash ||= @scope.context_hash)
29
+ def context_hash = Fields::FieldSet.deep_dup_owned(@context_hash ||= @scope.context_hash)
39
30
 
40
- def carry_hash = Fields::FieldSet.deep_dup(@carry_hash ||= @scope.carry_hash)
31
+ def carry_hash = Fields::FieldSet.deep_dup_owned(@carry_hash ||= @scope.carry_hash)
41
32
 
42
- def neutral_hash = Fields::FieldSet.deep_dup(@neutral_hash ||= @scope.neutral_hash)
33
+ def neutral_hash = Fields::FieldSet.deep_dup_owned(@neutral_hash ||= @scope.neutral_hash)
43
34
 
44
- def attributes_hash = Fields::FieldSet.deep_dup(@attributes_hash ||= @scope.attributes_hash)
35
+ def attributes_hash = Fields::FieldSet.deep_dup_owned(@attributes_hash ||= @scope.attributes_hash)
45
36
 
46
- def labels_hash = Fields::FieldSet.deep_dup(@labels_hash ||= @scope.frozen_labels_hash)
37
+ def labels_hash = Fields::FieldSet.deep_dup_owned(@labels_hash ||= @scope.frozen_labels_hash)
47
38
 
48
- def summary_hash = Fields::FieldSet.deep_dup(@summary_hash ||= @scope.summary_hash)
39
+ def summary_hash = Fields::FieldSet.deep_dup_owned(@summary_hash ||= @scope.summary_hash)
49
40
 
50
- def metrics_hash = Fields::FieldSet.deep_dup(@metrics_hash ||= @scope.metrics_hash)
41
+ def metrics_hash = Fields::FieldSet.deep_dup_owned(@metrics_hash ||= @scope.metrics_hash)
51
42
 
52
43
  def finished? = !finished_at.nil?
53
44
  end
@@ -2,54 +2,94 @@
2
2
 
3
3
  module Julewire
4
4
  module Core
5
+ module FacadePrivateMethods
6
+ private
7
+
8
+ def punk_banner
9
+ "!!JULEWIRE PUNK!! chaos containment armed\n"
10
+ end
11
+
12
+ def punk_chaos_output(output, chaos)
13
+ options = chaos if chaos.is_a?(Hash)
14
+ Destinations::ChaosOutput.new(output, **options)
15
+ end
16
+
17
+ def emit_with_severity(severity, record, fields, &)
18
+ if !block_given? && !record.is_a?(Hash)
19
+ # Scalar eager logs stay allocation-light; lazy inputs need the wrapper
20
+ # so block-built records can still receive the eager severity.
21
+ emit(eager_severity_input(severity, record, fields))
22
+ else
23
+ emit(Records::LazyEmitInput.with_severity(severity, Core.emit_input(record, fields)), &)
24
+ end
25
+ end
26
+
27
+ def eager_severity_input(severity, record, fields)
28
+ input = if record.equal?(UNSET)
29
+ fields
30
+ elsif fields.empty?
31
+ { message: record.to_s }
32
+ else
33
+ Core.emit_input(record, fields)
34
+ end
35
+ input.delete("severity")
36
+ input[:severity] = severity
37
+ input
38
+ end
39
+
40
+ def with_cleared_configure_guard
41
+ previous_guard = Fiber[Runtime::CONFIGURE_GUARD_KEY]
42
+ Fiber[Runtime::CONFIGURE_GUARD_KEY] = nil
43
+ yield
44
+ ensure
45
+ Fiber[Runtime::CONFIGURE_GUARD_KEY] = previous_guard
46
+ end
47
+ end
48
+
5
49
  module FacadeMethods
50
+ include FacadePrivateMethods
51
+
6
52
  def runtime(name = :default)
7
- Core::RuntimeRegistry.fetch(name, current: Core::RuntimeLocator.current)
53
+ RuntimeRegistry.fetch(name)
8
54
  end
9
55
 
10
56
  def config = runtime.config
11
-
12
57
  def configure(&) = runtime.configure(&)
13
-
14
58
  def context = runtime.context
15
-
16
59
  def attributes = runtime.attributes
17
-
18
60
  def carry = runtime.carry
19
-
20
61
  def current_execution = runtime.current_execution
21
-
22
62
  def current_execution? = runtime.current_execution?
23
63
 
24
- def emit(record = Core::UNSET, **fields, &)
64
+ def emit(record = UNSET, **fields, &)
25
65
  runtime.emit(record, **fields, &)
26
66
  end
27
67
 
28
- def debug(record = Core::UNSET, **fields, &)
68
+ def debug(record = UNSET, **fields, &)
29
69
  emit_with_severity(:debug, record, fields, &)
30
70
  end
31
71
 
32
- def info(record = Core::UNSET, **fields, &)
72
+ def info(record = UNSET, **fields, &)
33
73
  emit_with_severity(:info, record, fields, &)
34
74
  end
35
75
 
36
- def warn(record = Core::UNSET, **fields, &)
76
+ def warn(record = UNSET, **fields, &)
37
77
  emit_with_severity(:warn, record, fields, &)
38
78
  end
39
79
 
40
- def error(record = Core::UNSET, **fields, &)
80
+ def error(record = UNSET, **fields, &)
41
81
  emit_with_severity(:error, record, fields, &)
42
82
  end
43
83
 
44
- def fatal(record = Core::UNSET, **fields, &)
84
+ def fatal(record = UNSET, **fields, &)
45
85
  emit_with_severity(:fatal, record, fields, &)
46
86
  end
47
87
 
48
- def unknown(record = Core::UNSET, **fields, &)
88
+ def unknown(record = UNSET, **fields, &)
49
89
  emit_with_severity(:unknown, record, fields, &)
50
90
  end
51
91
 
52
- def flush(timeout: Core::UNSET)
92
+ def flush(timeout: UNSET)
53
93
  runtime.flush(timeout: timeout)
54
94
  end
55
95
 
@@ -62,26 +102,26 @@ module Julewire
62
102
  def measure_start(key) = summary.measure_start(key)
63
103
 
64
104
  def doctor(name = :default)
65
- Core::Diagnostics::Doctor.call(runtime(name))
105
+ Diagnostics::Doctor.call(runtime(name))
66
106
  end
67
107
 
68
108
  def tail(name = :default, **)
69
- Core::Diagnostics::Tail.attach!(runtime(name), **)
109
+ Diagnostics::Tail.attach!(runtime(name), **)
70
110
  end
71
111
 
72
112
  def observe_self!(name = :default, **)
73
- Core::Diagnostics::MetaObserver.attach!(name, **)
113
+ Diagnostics::MetaObserver.attach!(name, **)
74
114
  end
75
115
 
76
- def dev!(name = :default, output: $stdout, color: Core::UNSET, chaos: false, banner: chaos, tail: true)
77
- color = output.respond_to?(:tty?) ? output.tty? : true if color.equal?(Core::UNSET)
116
+ def dev!(name = :default, output: $stdout, color: UNSET, chaos: false, banner: chaos, tail: true)
117
+ color = output.respond_to?(:tty?) ? output.tty? : true if color.equal?(UNSET)
78
118
  punk!(name, output: output, color: color, chaos: chaos, banner: banner)
79
119
  return unless tail
80
120
 
81
121
  tail_options = tail == true ? {} : tail
82
122
  raise ArgumentError, "tail must be true, false, or an options Hash" unless tail_options.is_a?(Hash)
83
123
 
84
- Core::Diagnostics::Tail.attach!(runtime(name), **tail_options)
124
+ Tail.attach!(runtime(name), **tail_options)
85
125
  end
86
126
 
87
127
  def punk!(name = :default, output: $stdout, color: true, chaos: false, banner: chaos)
@@ -102,21 +142,19 @@ module Julewire
102
142
  def fiber(**, &)
103
143
  raise ArgumentError, "block required" unless block_given?
104
144
 
105
- envelope = Core::Propagation.capture_local
145
+ envelope = Propagation.capture_local
106
146
  Fiber.new(**) do |*args|
107
147
  with_cleared_configure_guard do
108
- Core::Propagation.restore(envelope, owned: true) { yield(*args) }
148
+ Propagation.restore(envelope, owned: true) { yield(*args) }
109
149
  end
110
150
  end
111
151
  end
112
152
 
113
153
  def labels = runtime.labels
114
-
115
154
  def after_fork! = runtime.after_fork!
155
+ def reset! = runtime.reset_facade!
116
156
 
117
- def reset! = runtime.reset!
118
-
119
- def close(timeout: Core::UNSET)
157
+ def close(timeout: UNSET)
120
158
  runtime.close(timeout: timeout)
121
159
  end
122
160
 
@@ -129,10 +167,10 @@ module Julewire
129
167
  def thread(*, &)
130
168
  raise ArgumentError, "block required" unless block_given?
131
169
 
132
- envelope = Core::Propagation.capture_local
170
+ envelope = Propagation.capture_local
133
171
  Thread.new(*) do |*thread_args|
134
172
  with_cleared_configure_guard do
135
- Core::Propagation.restore(envelope, owned: true) { yield(*thread_args) }
173
+ Propagation.restore(envelope, owned: true) { yield(*thread_args) }
136
174
  end
137
175
  end
138
176
  end
@@ -140,42 +178,6 @@ module Julewire
140
178
  def with_execution(type:, **, &)
141
179
  runtime.with_execution(type: type, **, &)
142
180
  end
143
-
144
- private
145
-
146
- def punk_banner
147
- "!!JULEWIRE PUNK!! chaos containment armed\n"
148
- end
149
-
150
- def punk_chaos_output(output, chaos)
151
- options = chaos.is_a?(Hash) ? chaos : {}
152
- Core::Destinations::ChaosOutput.new(output, **options)
153
- end
154
-
155
- def emit_with_severity(severity, record, fields, &)
156
- if record.equal?(Core::UNSET)
157
- fields.delete("severity")
158
- fields[:severity] = severity
159
- runtime.emit(fields, &)
160
- elsif !block_given? && !record.is_a?(Hash)
161
- # Scalar eager logs stay allocation-light; lazy inputs need the wrapper
162
- # so block-built records can still receive the eager severity.
163
- input = fields.empty? ? { message: record.to_s } : Core.emit_input(record, fields)
164
- input.delete("severity")
165
- input[:severity] = severity
166
- runtime.emit(input)
167
- else
168
- runtime.emit(Core::Records::LazyEmitInput.with_severity(severity, Core.emit_input(record, fields)), &)
169
- end
170
- end
171
-
172
- def with_cleared_configure_guard
173
- previous_guard = Fiber[Core::Runtime::CONFIGURE_GUARD_KEY]
174
- Fiber[Core::Runtime::CONFIGURE_GUARD_KEY] = nil
175
- yield
176
- ensure
177
- Fiber[Core::Runtime::CONFIGURE_GUARD_KEY] = previous_guard
178
- end
179
181
  end
180
182
  end
181
183
  end
@@ -45,8 +45,6 @@ module Julewire
45
45
 
46
46
  fields.compact
47
47
  end
48
-
49
- def from(neutral) = neutral.is_a?(Hash) ? neutral : {}
50
48
  end
51
49
  end
52
50
  end