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
@@ -1,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Julewire
4
- module Core
5
- module Testing
6
- module Contracts
7
- module Runtime
8
- def assert_julewire_runtime_integration_contract(**options)
9
- run_julewire_runtime_contract(options)
10
- end
11
-
12
- def assert_julewire_execution_boundary_contract(**options)
13
- run_julewire_execution_boundary_contract(options)
14
- end
15
-
16
- def assert_julewire_failure_containment_contract(configure:, destination_name: :default, emit: nil)
17
- Julewire.configure(&configure)
18
-
19
- assert_nil((emit || default_failure_contract_emit).call)
20
-
21
- health = Julewire.health
22
- destination_health = health.dig(:pipeline, :destinations, destination_name.to_sym)
23
-
24
- assert_equal :degraded, health.fetch(:status)
25
- assert_kind_of Hash, destination_health
26
-
27
- [health, destination_health]
28
- end
29
-
30
- def assert_julewire_record_source_contract(
31
- records:,
32
- event:,
33
- source:,
34
- **options
35
- )
36
- record = find_contract_record(records, options.fetch(:event_path, %w[event]), event)
37
-
38
- assert_equal source.to_s, fetch_contract_path(record, options.fetch(:source_path, %w[source])).to_s
39
- assert_contract_optional_source_field(record, options, :logger)
40
- assert_contract_optional_source_field(record, options, :kind)
41
-
42
- record
43
- end
44
-
45
- private
46
-
47
- def assert_contract_optional_source_field(record, options, field)
48
- expected = options[field]
49
- return unless expected
50
-
51
- path = options.fetch(:"#{field}_path", [field.to_s])
52
- assert_equal expected.to_s, fetch_contract_path(record, path).to_s
53
- end
54
-
55
- def run_julewire_runtime_contract(options)
56
- Julewire.configure(&options.fetch(:configure))
57
- emit_runtime_contract_records
58
-
59
- records = options.fetch(:records).call
60
- point = find_contract_record(records, options.fetch(:event_path), point_event(options))
61
- summary = find_contract_record(records, options.fetch(:event_path), summary_event(options))
62
- health = Julewire.health
63
-
64
- assert_runtime_contract_records(point, summary, health, options)
65
- [point, summary, health]
66
- end
67
-
68
- def run_julewire_execution_boundary_contract(options)
69
- Julewire.configure(&options.fetch(:configure))
70
- options.fetch(:exercise).call(**execution_boundary_probe(options))
71
- Julewire.flush
72
-
73
- records = options.fetch(:records).call
74
- point = find_contract_record(records, options.fetch(:event_path), point_event(options))
75
- summary = find_contract_record(records, options.fetch(:event_path), summary_event(options))
76
- health = Julewire.health
77
-
78
- assert_runtime_contract_records(point, summary, health, options)
79
- [point, summary, health]
80
- end
81
-
82
- def emit_runtime_contract_records
83
- Julewire.with_execution(
84
- type: :contract,
85
- id: "contract-1",
86
- summary_event: "contract.completed",
87
- summary_source: "contract"
88
- ) do
89
- Julewire.context.add(request_id: "request-1")
90
- Julewire.carry.add(http: { request_headers: { traceparent: contract_traceparent } })
91
- Julewire.summary.add(total: 2)
92
- Julewire.emit(event: point_event({}), source: "contract", message: "point", payload: { value: 1 })
93
- end
94
- Julewire.flush
95
- end
96
-
97
- def assert_runtime_contract_records(point, summary, health, options)
98
- assert_equal "request-1", fetch_contract_path(point, options.fetch(:context_path) + [:request_id])
99
- assert_equal 2, fetch_contract_path(summary, options.fetch(:summary_payload_path) + [:total])
100
- assert_runtime_contract_carry(point, options[:carry_path]) if options[:carry_path]
101
-
102
- assert health.dig(:pipeline, :configured)
103
- assert_kind_of Hash, health.dig(:pipeline, :destinations, contract_destination_name(options))
104
- end
105
-
106
- def assert_runtime_contract_carry(point, carry_path)
107
- path = carry_path + %i[http request_headers traceparent]
108
-
109
- assert_equal contract_traceparent, fetch_contract_path(point, path)
110
- end
111
-
112
- def contract_destination_name(options)
113
- options.fetch(:destination_name, :default).to_sym
114
- end
115
-
116
- def default_failure_contract_emit
117
- lambda do
118
- Julewire.emit(
119
- event: "contract.failure",
120
- source: "contract",
121
- message: "failure probe",
122
- payload: { token: "secret" }
123
- )
124
- end
125
- end
126
-
127
- def execution_boundary_probe(options)
128
- {
129
- add_summary: -> { Julewire.summary.add(total: 2) },
130
- carry: { http: { request_headers: { traceparent: contract_traceparent } } },
131
- context: { request_id: "request-1" },
132
- emit_point: lambda do
133
- Julewire.emit(event: point_event(options), source: "contract", message: "point", payload: { value: 1 })
134
- end,
135
- summary_event: summary_event(options),
136
- traceparent: contract_traceparent
137
- }
138
- end
139
-
140
- def point_event(options)
141
- options.fetch(:point_event, "contract.point")
142
- end
143
-
144
- def summary_event(options)
145
- options.fetch(:summary_event, "contract.completed")
146
- end
147
-
148
- def find_contract_record(records, event_path, event)
149
- record = records.find { fetch_contract_path(it, event_path) == event }
150
- return record if record
151
-
152
- flunk("expected contract record #{event.inspect}, got #{records.inspect}")
153
- end
154
-
155
- def fetch_contract_path(value, path)
156
- path.reduce(value) { |current, key| fetch_contract_key(current, key) }
157
- end
158
-
159
- def fetch_contract_key(value, key)
160
- return value.fetch(key) if value.respond_to?(:key?) && value.key?(key)
161
- return value.fetch(key.to_s) if value.respond_to?(:key?) && value.key?(key.to_s)
162
- return fetch_symbol_contract_key(value, key) if key.respond_to?(:to_sym)
163
-
164
- flunk("expected key #{key.inspect} in #{value.inspect}")
165
- end
166
-
167
- def fetch_symbol_contract_key(value, key)
168
- return value.fetch(key.to_sym) if value.respond_to?(:key?) && value.key?(key.to_sym)
169
-
170
- flunk("expected key #{key.inspect} in #{value.inspect}")
171
- end
172
-
173
- def contract_traceparent = "00-06796866738c859f2f19b7cfb3214824-000000000000004a-01"
174
- end
175
- end
176
- end
177
- end
178
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Julewire
4
- module Core
5
- module Testing
6
- module Contracts
7
- module Wire
8
- def assert_julewire_propagation_contract(key: Julewire::Core::Propagation::Carrier::DEFAULT_KEY)
9
- carrier = {}
10
- extracted = nil
11
- restored = nil
12
-
13
- Julewire.with_execution(type: :contract, id: "contract-1", emit_summary: false) do
14
- Julewire.context.add(request_id: "request-1")
15
- Julewire.carry.add(http: { request_headers: { traceparent: contract_traceparent } })
16
-
17
- assert_same carrier, Julewire::Core::Propagation::Carrier.inject(carrier, key: key)
18
-
19
- extracted = Julewire::Core::Propagation::Carrier.extract(carrier, key: key)
20
- assert_equal "request-1", extracted.dig(:context, :request_id)
21
- assert_equal contract_traceparent, extracted.dig(:carry, :http, :request_headers, :traceparent)
22
- assert_equal "contract-1", extracted.dig(:execution, :id)
23
-
24
- Julewire::Core::Propagation::Carrier.restore(carrier, key: key) do
25
- restored = Julewire::Core::Propagation.capture_local
26
- end
27
- end
28
-
29
- assert_equal "request-1", restored.dig(:context, :request_id)
30
- assert_equal contract_traceparent, restored.dig(:carry, :http, :request_headers, :traceparent)
31
- assert_equal "contract-1", restored.dig(:execution, :id)
32
- assert_oversize_carrier_clears_stale_value!(key)
33
-
34
- extracted
35
- end
36
-
37
- private
38
-
39
- def assert_oversize_carrier_clears_stale_value!(key)
40
- string_key = key.to_s
41
- symbol_key = key.is_a?(String) ? key.to_sym : key
42
- carrier = { string_key => "stale" }
43
- carrier[symbol_key] = "stale" if symbol_key != string_key
44
-
45
- result = Julewire::Core::Propagation::Carrier.inject(
46
- carrier,
47
- envelope: { context: { large: "x" * 64 } },
48
- key: key,
49
- max_bytes: 1
50
- )
51
-
52
- assert_nil result
53
- refute carrier.key?(string_key)
54
- refute carrier.key?(symbol_key) if symbol_key != string_key
55
- end
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "contracts/component"
4
- require_relative "contracts/deadline_scheduler"
5
- require_relative "contracts/integration"
6
- require_relative "contracts/record_draft"
7
- require_relative "contracts/runtime"
8
- require_relative "contracts/wire"
9
-
10
- module Julewire
11
- module Core
12
- module Testing
13
- # @api extension
14
- module Contracts
15
- include Component
16
- include DeadlineScheduler
17
- include Integration
18
- include RecordDraft
19
- include Runtime
20
- include Wire
21
- end
22
- end
23
- end
24
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Julewire
4
- module Core
5
- module Testing
6
- # @api extension
7
- module Coverage
8
- DEFAULT_MINIMUM_LINE = 96
9
- DEFAULT_MINIMUM_BRANCH = 87
10
-
11
- class << self
12
- def start!(minimum_line: DEFAULT_MINIMUM_LINE, minimum_branch: DEFAULT_MINIMUM_BRANCH, filters: [])
13
- return unless ENV["COVERAGE"]
14
-
15
- require "simplecov"
16
- require "simplecov-lcov"
17
-
18
- configure_lcov_formatter
19
- configure_formatters
20
- start_simplecov(minimum_line: minimum_line, minimum_branch: minimum_branch, filters: filters)
21
- nil
22
- end
23
-
24
- def configure_lcov_formatter
25
- SimpleCov::Formatter::LcovFormatter.config do |config|
26
- config.report_with_single_file = true
27
- config.output_directory = "coverage/lcov"
28
- config.lcov_file_name = "lcov.info"
29
- config.single_report_path = "coverage/lcov/lcov.info"
30
- end
31
- end
32
-
33
- def configure_formatters
34
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
35
- [
36
- SimpleCov::Formatter::HTMLFormatter,
37
- SimpleCov::Formatter::LcovFormatter
38
- ]
39
- )
40
- end
41
-
42
- def start_simplecov(minimum_line:, minimum_branch:, filters:)
43
- SimpleCov.start do
44
- enable_coverage :branch
45
- minimum_coverage line: minimum_line, branch: minimum_branch if minimum_line || minimum_branch
46
- track_files "lib/**/*.rb"
47
- add_filter "/test/"
48
- add_filter "/lib/julewire/core/testing/coverage.rb"
49
- add_filter %r{/lib/julewire/[^/]+/version\.rb\z}
50
- add_filter %r{/lib/julewire-[^/]+\.rb\z}
51
- filters.each { add_filter(it) }
52
- end
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Julewire
4
- module Core
5
- module Testing
6
- module TestReports
7
- module CodecovJUnitReportMethods
8
- def report
9
- return super unless @single_file
10
-
11
- puts "Writing XML reports to #{reports_path}"
12
- File.write(filename_for("minitest"), testsuites_xml)
13
- end
14
-
15
- private
16
-
17
- def testsuites_xml
18
- suites = tests.group_by { test_class(it) }
19
- result = analyze_suite(tests)
20
- xml = Builder::XmlMarkup.new(indent: 2)
21
- xml.instruct!
22
- xml.testsuites(testsuite_result_attributes(result)) do
23
- suites.each do |suite, suite_tests|
24
- parse_xml_for(xml, suite, suite_tests)
25
- end
26
- end
27
-
28
- xml.target!
29
- end
30
-
31
- def testsuite_result_attributes(result)
32
- {
33
- name: "minitest",
34
- skipped: result[:skip_count],
35
- failures: result[:fail_count],
36
- errors: result[:error_count],
37
- tests: result[:test_count],
38
- assertions: result[:assertion_count],
39
- time: result[:time]
40
- }
41
- end
42
- end
43
- private_constant :CodecovJUnitReportMethods
44
-
45
- module_function
46
-
47
- def start!(enabled: ENV.fetch("JULEWIRE_JUNIT", nil),
48
- reports_dir: ENV.fetch("JULEWIRE_JUNIT_DIR", "test/reports"))
49
- return unless enabled
50
-
51
- require "fileutils"
52
- require "minitest/reporters"
53
-
54
- FileUtils.mkdir_p(reports_dir)
55
- junit = junit_reporter_class.new(
56
- reports_dir,
57
- true,
58
- base_path: Dir.pwd,
59
- include_timestamp: true,
60
- single_file: true
61
- )
62
-
63
- Minitest::Reporters.use!(
64
- [Minitest::Reporters::DefaultReporter.new, junit],
65
- ENV,
66
- Minitest.backtrace_filter
67
- )
68
- end
69
-
70
- def junit_reporter_class
71
- @junit_reporter_class ||= Class.new(Minitest::Reporters::JUnitReporter) do
72
- include CodecovJUnitReportMethods
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end