julewire-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/docs/advanced-configuration.md +66 -0
- data/docs/attribute-keys.md +74 -0
- data/docs/configuration.md +327 -0
- data/docs/context-and-propagation.md +353 -0
- data/docs/contracts.md +211 -0
- data/docs/development.md +49 -0
- data/docs/extensions-and-api.md +567 -0
- data/docs/health-schema.md +104 -0
- data/docs/instrumentation-cheatsheet.md +29 -0
- data/docs/internals.md +135 -0
- data/docs/outputs-and-lifecycle.md +206 -0
- data/docs/quickstart.md +133 -0
- data/docs/record-sources.md +17 -0
- data/docs/records-and-data-policy.md +230 -0
- data/docs/security-and-wire.md +45 -0
- data/docs/tail.md +91 -0
- data/exe/julewire +6 -0
- data/julewire-core.gemspec +41 -0
- data/lib/julewire/core/cli/doctor.rb +143 -0
- data/lib/julewire/core/cli/line_helpers.rb +77 -0
- data/lib/julewire/core/cli/log_formats/console_text.rb +25 -0
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +46 -0
- data/lib/julewire/core/cli/log_formats/core_json_encoder.rb +21 -0
- data/lib/julewire/core/cli/log_formats/record_decoder.rb +39 -0
- data/lib/julewire/core/cli/log_formats.rb +123 -0
- data/lib/julewire/core/cli/tail.rb +153 -0
- data/lib/julewire/core/cli/transcode.rb +105 -0
- data/lib/julewire/core/cli.rb +73 -0
- data/lib/julewire/core/configuration.rb +99 -0
- data/lib/julewire/core/context_store.rb +384 -0
- data/lib/julewire/core/destinations/chaos_output.rb +91 -0
- data/lib/julewire/core/destinations/collection.rb +177 -0
- data/lib/julewire/core/destinations/definition.rb +125 -0
- data/lib/julewire/core/destinations/destination.rb +268 -0
- data/lib/julewire/core/destinations/registry.rb +81 -0
- data/lib/julewire/core/destinations/sink.rb +35 -0
- data/lib/julewire/core/destinations/synchronized_output.rb +57 -0
- data/lib/julewire/core/destinations/tail_sampling.rb +321 -0
- data/lib/julewire/core/destinations/write_step.rb +119 -0
- data/lib/julewire/core/destinations.rb +33 -0
- data/lib/julewire/core/diagnostics/callback_notifier.rb +63 -0
- data/lib/julewire/core/diagnostics/doctor.rb +114 -0
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +39 -0
- data/lib/julewire/core/diagnostics/health.rb +144 -0
- data/lib/julewire/core/diagnostics/integration_health_store.rb +64 -0
- data/lib/julewire/core/diagnostics/internal_records.rb +61 -0
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +112 -0
- data/lib/julewire/core/diagnostics/meta_observer.rb +161 -0
- data/lib/julewire/core/diagnostics/process_integration_health.rb +26 -0
- data/lib/julewire/core/diagnostics/tail/renderer.rb +36 -0
- data/lib/julewire/core/diagnostics/tail.rb +168 -0
- data/lib/julewire/core/diagnostics.rb +8 -0
- data/lib/julewire/core/error.rb +7 -0
- data/lib/julewire/core/execution/boundary.rb +106 -0
- data/lib/julewire/core/execution/handle.rb +77 -0
- data/lib/julewire/core/execution/lineage.rb +192 -0
- data/lib/julewire/core/execution/measurement_handle.rb +28 -0
- data/lib/julewire/core/execution/no_current_error.rb +9 -0
- data/lib/julewire/core/execution/scope.rb +246 -0
- data/lib/julewire/core/execution/scope_fields.rb +76 -0
- data/lib/julewire/core/execution/scope_identity.rb +71 -0
- data/lib/julewire/core/execution/scope_snapshot.rb +92 -0
- data/lib/julewire/core/execution/summary_state.rb +206 -0
- data/lib/julewire/core/execution/view.rb +56 -0
- data/lib/julewire/core/facade_methods.rb +181 -0
- data/lib/julewire/core/fields/attribute_keys.rb +54 -0
- data/lib/julewire/core/fields/attributes_proxy.rb +11 -0
- data/lib/julewire/core/fields/bags.rb +123 -0
- data/lib/julewire/core/fields/carry_proxy.rb +22 -0
- data/lib/julewire/core/fields/context_proxy.rb +11 -0
- data/lib/julewire/core/fields/field_set.rb +78 -0
- data/lib/julewire/core/fields/field_stack.rb +269 -0
- data/lib/julewire/core/fields/internal/deletion.rb +68 -0
- data/lib/julewire/core/fields/internal.rb +87 -0
- data/lib/julewire/core/fields/lookup.rb +35 -0
- data/lib/julewire/core/fields/section_proxy.rb +88 -0
- data/lib/julewire/core/fields/stack_set.rb +69 -0
- data/lib/julewire/core/fields/static_labels.rb +43 -0
- data/lib/julewire/core/fields/summary_proxy.rb +62 -0
- data/lib/julewire/core/integration/configurable.rb +52 -0
- data/lib/julewire/core/integration/destination_health.rb +43 -0
- data/lib/julewire/core/integration/event_subscriber.rb +62 -0
- data/lib/julewire/core/integration/facade.rb +131 -0
- data/lib/julewire/core/integration/fork_hooks.rb +79 -0
- data/lib/julewire/core/integration/health.rb +41 -0
- data/lib/julewire/core/integration/ivar_state.rb +38 -0
- data/lib/julewire/core/integration/lifecycle.rb +22 -0
- data/lib/julewire/core/integration/scoped.rb +34 -0
- data/lib/julewire/core/integration/settings.rb +92 -0
- data/lib/julewire/core/integration/subscriber_install.rb +39 -0
- data/lib/julewire/core/integration/subscription.rb +29 -0
- data/lib/julewire/core/integration/values.rb +192 -0
- data/lib/julewire/core/lifecycle_error.rb +7 -0
- data/lib/julewire/core/local_storage.rb +91 -0
- data/lib/julewire/core/processing/level_threshold.rb +53 -0
- data/lib/julewire/core/processing/match.rb +74 -0
- data/lib/julewire/core/processing/pipeline.rb +360 -0
- data/lib/julewire/core/processing/processor_chain.rb +69 -0
- data/lib/julewire/core/processing/processor_registry.rb +115 -0
- data/lib/julewire/core/processing/processor_wrapper.rb +44 -0
- data/lib/julewire/core/processing/record_field_transform.rb +124 -0
- data/lib/julewire/core/processing/sampling.rb +109 -0
- data/lib/julewire/core/processing.rb +41 -0
- data/lib/julewire/core/propagation/carrier.rb +93 -0
- data/lib/julewire/core/propagation.rb +50 -0
- data/lib/julewire/core/records/console_formatter.rb +24 -0
- data/lib/julewire/core/records/deconstruct.rb +19 -0
- data/lib/julewire/core/records/display_message.rb +166 -0
- data/lib/julewire/core/records/draft.rb +576 -0
- data/lib/julewire/core/records/formatter.rb +14 -0
- data/lib/julewire/core/records/lazy_emit_input.rb +99 -0
- data/lib/julewire/core/records/metadata.rb +23 -0
- data/lib/julewire/core/records/public_projection.rb +51 -0
- data/lib/julewire/core/records/raw_input.rb +41 -0
- data/lib/julewire/core/records/record.rb +175 -0
- data/lib/julewire/core/records/severity.rb +44 -0
- data/lib/julewire/core/runtime.rb +515 -0
- data/lib/julewire/core/runtime_locator.rb +20 -0
- data/lib/julewire/core/runtime_registry.rb +48 -0
- data/lib/julewire/core/runtime_state.rb +39 -0
- data/lib/julewire/core/scheduling/deadline.rb +24 -0
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +207 -0
- data/lib/julewire/core/scheduling/shared_scheduler.rb +48 -0
- data/lib/julewire/core/sentinel.rb +18 -0
- data/lib/julewire/core/serialization/backtrace_limiter.rb +50 -0
- data/lib/julewire/core/serialization/bounded_transform.rb +55 -0
- data/lib/julewire/core/serialization/bounded_traversal.rb +274 -0
- data/lib/julewire/core/serialization/deep_compact_empty.rb +67 -0
- data/lib/julewire/core/serialization/deep_freeze.rb +63 -0
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +40 -0
- data/lib/julewire/core/serialization/exception_shape.rb +88 -0
- data/lib/julewire/core/serialization/json_encoder.rb +69 -0
- data/lib/julewire/core/serialization/serializer.rb +233 -0
- data/lib/julewire/core/serialization/serializer_pool.rb +21 -0
- data/lib/julewire/core/serialization/text_encoder.rb +147 -0
- data/lib/julewire/core/serialization/value_copy.rb +209 -0
- data/lib/julewire/core/serialization/value_traversal.rb +150 -0
- data/lib/julewire/core/testing/chaos/catalog.rb +72 -0
- data/lib/julewire/core/testing/chaos/core_runtime.rb +120 -0
- data/lib/julewire/core/testing/chaos/destination.rb +55 -0
- data/lib/julewire/core/testing/chaos/emitter.rb +20 -0
- data/lib/julewire/core/testing/chaos/raising_output.rb +42 -0
- data/lib/julewire/core/testing/chaos.rb +80 -0
- data/lib/julewire/core/testing/contracts/component.rb +162 -0
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +59 -0
- data/lib/julewire/core/testing/contracts/integration.rb +166 -0
- data/lib/julewire/core/testing/contracts/integration_fields.rb +36 -0
- data/lib/julewire/core/testing/contracts/record_draft.rb +37 -0
- data/lib/julewire/core/testing/contracts/runtime.rb +178 -0
- data/lib/julewire/core/testing/contracts/wire.rb +60 -0
- data/lib/julewire/core/testing/contracts.rb +24 -0
- data/lib/julewire/core/testing/coverage.rb +58 -0
- data/lib/julewire/core/testing/test_reports.rb +78 -0
- data/lib/julewire/core/testing.rb +122 -0
- data/lib/julewire/core/validation.rb +69 -0
- data/lib/julewire/core/version.rb +7 -0
- data/lib/julewire/core.rb +80 -0
- data/lib/julewire/error.rb +5 -0
- data/lib/julewire-core.rb +3 -0
- metadata +237 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Fields
|
|
6
|
+
class SummaryProxy
|
|
7
|
+
def initialize(store)
|
|
8
|
+
@store = store
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add(fields = nil, **keyword_fields)
|
|
12
|
+
current_scope.add_summary(summary_fields(fields, keyword_fields), owned: true)
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_attributes(fields = nil, **keyword_fields)
|
|
17
|
+
current_scope.add_summary_attributes(summary_fields(fields, keyword_fields), owned: true)
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def increment_attribute(*path, by: 1)
|
|
22
|
+
current_scope.increment_summary_attribute(path, by: by)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def increment(key, by: 1)
|
|
27
|
+
current_scope.increment_summary(key, by: by)
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def measure(key, &)
|
|
32
|
+
raise ArgumentError, "block required" unless block_given?
|
|
33
|
+
|
|
34
|
+
current_scope.measure_summary(key, &)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def measure_start(key)
|
|
38
|
+
current_scope.measure_summary_start(key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def append(key, value)
|
|
42
|
+
current_scope.append_summary(key, value)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def active?
|
|
47
|
+
@store.current_scope?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def current_scope
|
|
53
|
+
@store.current_scope || raise(Execution::NoCurrentError, "summary data requires a current execution scope")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def summary_fields(fields, keyword_fields)
|
|
57
|
+
FieldSet.coerce(fields, keyword_fields, invalid: :wrap)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module Configurable
|
|
8
|
+
def configurable_with(&configuration_class)
|
|
9
|
+
raise ArgumentError, "configuration class block required" unless configuration_class
|
|
10
|
+
|
|
11
|
+
@julewire_configuration_class = configuration_class
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def config
|
|
15
|
+
@config ||= build_config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config=(configuration)
|
|
19
|
+
validate_config!(configuration)
|
|
20
|
+
@config = configuration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configure
|
|
24
|
+
raise ArgumentError, "#{name}.configure requires a block" unless block_given?
|
|
25
|
+
|
|
26
|
+
yield config
|
|
27
|
+
config
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset!
|
|
31
|
+
@config = build_config
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_config
|
|
37
|
+
configuration_class.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_config!(configuration)
|
|
41
|
+
return if configuration.is_a?(configuration_class)
|
|
42
|
+
|
|
43
|
+
raise TypeError, "expected #{configuration_class.name}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def configuration_class
|
|
47
|
+
@julewire_configuration_class.call
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
class DestinationHealth
|
|
8
|
+
def initialize(counter_keys:, failure_counter: :failures)
|
|
9
|
+
@failure_counter = failure_counter
|
|
10
|
+
@state = Diagnostics::Health.new(
|
|
11
|
+
counter_keys: counter_keys,
|
|
12
|
+
failure_counter: failure_counter,
|
|
13
|
+
track_failures: failure_counter == :failures
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def increment(key, by: 1)
|
|
18
|
+
@state.increment(key, by: by)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def record_failure(error, counter: @failure_counter, **metadata)
|
|
22
|
+
@state.record_failure(error, counter: counter, degrade: false, **metadata)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def record_loss(reason:, counter: reason, **metadata)
|
|
26
|
+
@state.record_loss(reason: reason, counter: counter, degrade: false, **metadata)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clear_degraded! = @state.clear_failures!
|
|
30
|
+
|
|
31
|
+
def degraded? = @state.degraded?(status_from: :failure_or_loss)
|
|
32
|
+
|
|
33
|
+
def last_loss = @state.last_loss
|
|
34
|
+
|
|
35
|
+
def last_failure = @state.last_failure
|
|
36
|
+
|
|
37
|
+
def snapshot(status: nil, **fields)
|
|
38
|
+
@state.snapshot(status: status, status_from: :failure_or_loss, include_loss: true, **fields)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module EventSubscriber
|
|
8
|
+
class << self
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def event_subscriber(integration_health:, configuration_class:, component: :event_subscriber)
|
|
16
|
+
event_subscriber_options[:component] = component
|
|
17
|
+
event_subscriber_options[:configuration_class] = configuration_class
|
|
18
|
+
event_subscriber_options[:integration_health] = integration_health
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default_configuration
|
|
22
|
+
event_subscriber_options.fetch(:configuration_class).new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def event_subscriber_component
|
|
26
|
+
event_subscriber_options.fetch(:component)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def event_subscriber_health
|
|
30
|
+
event_subscriber_options.fetch(:integration_health)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def event_subscriber_options
|
|
36
|
+
@event_subscriber_options ||= {}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(configuration = self.class.default_configuration)
|
|
41
|
+
self.configuration = configuration
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def configuration=(configuration)
|
|
45
|
+
@configuration = configuration
|
|
46
|
+
after_configuration_change
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def emit(event)
|
|
50
|
+
self.class.event_subscriber_health.with_failure_health(
|
|
51
|
+
action: :emit,
|
|
52
|
+
component: self.class.event_subscriber_component
|
|
53
|
+
) { emit_event(event) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def after_configuration_change = nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module Facade
|
|
8
|
+
class << self
|
|
9
|
+
def emit(record = Core::UNSET, enforce_level: true, **fields)
|
|
10
|
+
record = Core.emit_input(record, fields)
|
|
11
|
+
runtime = RuntimeLocator.current
|
|
12
|
+
if runtime.respond_to?(:emit_integration)
|
|
13
|
+
runtime.emit_integration(record, enforce_level: enforce_level)
|
|
14
|
+
elsif enforce_level
|
|
15
|
+
runtime.emit(record)
|
|
16
|
+
else
|
|
17
|
+
runtime.emit_without_level(record)
|
|
18
|
+
end
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def with_execution(type:, **, &)
|
|
23
|
+
raise ArgumentError, "block required" unless block_given?
|
|
24
|
+
|
|
25
|
+
integration_write_section!(:execution)
|
|
26
|
+
RuntimeLocator.current.with_execution(type: type, owned: true, **, &)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def with_attributes(fields, &)
|
|
30
|
+
with_fields(:attributes, fields, &)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def with_neutral(fields, &)
|
|
34
|
+
with_fields(:neutral, fields, &)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def with_carry(fields, &)
|
|
38
|
+
with_fields(:carry, fields, &)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_context(fields, &)
|
|
42
|
+
with_fields(:context, fields, &)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_context(fields)
|
|
46
|
+
add_fields(:context, fields)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add_attributes(fields)
|
|
50
|
+
add_fields(:attributes, fields)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_neutral(fields)
|
|
54
|
+
add_fields(:neutral, fields)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_carry(fields)
|
|
58
|
+
add_fields(:carry, fields)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def add_summary_attributes(fields)
|
|
62
|
+
add_summary_fields(fields, :add_summary_attributes)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def add_summary_neutral(fields)
|
|
66
|
+
add_summary_fields(fields, :add_summary_neutral)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def summary_active?
|
|
70
|
+
current_scope?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def increment_summary_attribute(*path, by: 1)
|
|
74
|
+
scope = ContextStore.current.current_scope
|
|
75
|
+
return unless scope
|
|
76
|
+
|
|
77
|
+
scope.increment_summary_attribute(path, by: by)
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def current_scope?
|
|
84
|
+
ContextStore.current.current_scope?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def with_fields(section, fields, &)
|
|
88
|
+
raise ArgumentError, "block required" unless block_given?
|
|
89
|
+
|
|
90
|
+
integration_write_section!(section)
|
|
91
|
+
case section
|
|
92
|
+
when :attributes then ContextStore.current.with_attributes(fields, owned: true, &)
|
|
93
|
+
when :carry then ContextStore.current.with_carry(fields, owned: true, &)
|
|
94
|
+
when :context then ContextStore.current.with_context(fields, owned: true, &)
|
|
95
|
+
when :neutral then ContextStore.current.with_neutral(fields, owned: true, &)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def add_fields(section, fields)
|
|
100
|
+
integration_write_section!(section)
|
|
101
|
+
case section
|
|
102
|
+
when :attributes then ContextStore.current.add_attributes(fields, owned: true)
|
|
103
|
+
when :carry then ContextStore.current.add_carry(fields, owned: true)
|
|
104
|
+
when :context then ContextStore.current.add_context(fields, owned: true)
|
|
105
|
+
when :neutral then ContextStore.current.add_neutral(fields, owned: true)
|
|
106
|
+
end
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def integration_write_section!(section)
|
|
111
|
+
# Keep the failure path close to the table it protects; new field
|
|
112
|
+
# bags should not become integration-writable by accident.
|
|
113
|
+
return if Fields::Bags.integration_write_sections.include?(section)
|
|
114
|
+
|
|
115
|
+
raise ArgumentError, "integration cannot write #{section}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def add_summary_fields(fields, writer)
|
|
119
|
+
integration_write_section!(:summary)
|
|
120
|
+
scope = ContextStore.current.current_scope
|
|
121
|
+
return unless scope && fields.is_a?(Hash)
|
|
122
|
+
|
|
123
|
+
fields = Serialization::DeepCompactEmpty.compact_owned!(fields)
|
|
124
|
+
scope.public_send(writer, fields, owned: true) unless fields.empty?
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
module ForkHooks
|
|
7
|
+
Entry = Data.define(:integration, :component, :callback)
|
|
8
|
+
private_constant :Entry
|
|
9
|
+
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
@entries = {}
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def register(integration, component:, &callback)
|
|
15
|
+
raise ArgumentError, "block required" unless callback
|
|
16
|
+
|
|
17
|
+
name = integration_name(integration)
|
|
18
|
+
component = component.to_sym
|
|
19
|
+
register_entry(name, component, callback)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
snapshot = mutex.synchronize { entries.values }
|
|
24
|
+
snapshot.each { run_entry(it) }
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def after_fork!
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reset!
|
|
34
|
+
mutex.synchronize { entries.clear }
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
attr_reader :entries, :mutex
|
|
41
|
+
|
|
42
|
+
def register_entry(name, component, callback)
|
|
43
|
+
mutex.synchronize do
|
|
44
|
+
entries[[name, component]] = Entry.new(name, component, callback)
|
|
45
|
+
end
|
|
46
|
+
nil
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
Diagnostics::ProcessIntegrationHealth.record_failure(
|
|
49
|
+
name,
|
|
50
|
+
e,
|
|
51
|
+
action: :register_after_fork,
|
|
52
|
+
component: component
|
|
53
|
+
)
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run_entry(entry)
|
|
58
|
+
entry.callback.call
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
Diagnostics::ProcessIntegrationHealth.record_failure(
|
|
61
|
+
entry.integration,
|
|
62
|
+
e,
|
|
63
|
+
action: :after_fork,
|
|
64
|
+
component: entry.component
|
|
65
|
+
)
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def integration_name(value)
|
|
70
|
+
name = value.to_s
|
|
71
|
+
raise ArgumentError, "integration name is required" if name.empty?
|
|
72
|
+
|
|
73
|
+
name.to_sym
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module Health
|
|
8
|
+
class << self
|
|
9
|
+
def record_failure(integration, error, runtime: nil, **metadata)
|
|
10
|
+
if runtime
|
|
11
|
+
runtime.record_integration_failure(integration, error, **metadata)
|
|
12
|
+
else
|
|
13
|
+
Diagnostics::ProcessIntegrationHealth.record_failure(integration, error, **metadata)
|
|
14
|
+
end
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def record_success(integration, runtime: nil, **)
|
|
19
|
+
if runtime
|
|
20
|
+
runtime.record_integration_success(integration)
|
|
21
|
+
else
|
|
22
|
+
Diagnostics::ProcessIntegrationHealth.record_success(integration)
|
|
23
|
+
end
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def with_failure_health(integration, component:, action:, runtime: nil, **metadata)
|
|
28
|
+
yield.tap { record_success(integration, runtime: runtime) }
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
record_failure(integration, e, runtime: runtime, component: component, action: action, **metadata)
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def scoped(integration, runtime: nil)
|
|
35
|
+
Scoped.new(integration, runtime: runtime)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
class IvarState
|
|
8
|
+
def initialize(marker)
|
|
9
|
+
@marker = marker
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fetch(owner)
|
|
13
|
+
return unless owner.respond_to?(:instance_variable_get)
|
|
14
|
+
|
|
15
|
+
owner.instance_variable_get(@marker)
|
|
16
|
+
rescue StandardError
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def store(owner, value)
|
|
21
|
+
return value unless owner.respond_to?(:instance_variable_set)
|
|
22
|
+
|
|
23
|
+
owner.instance_variable_set(@marker, value)
|
|
24
|
+
value
|
|
25
|
+
rescue StandardError
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch_or_store(owner)
|
|
30
|
+
existing = fetch(owner)
|
|
31
|
+
return existing if existing
|
|
32
|
+
|
|
33
|
+
store(owner, yield)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module Lifecycle
|
|
8
|
+
class << self
|
|
9
|
+
def require_optional(path)
|
|
10
|
+
require path
|
|
11
|
+
rescue LoadError
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register_after_fork(integration, component:, &)
|
|
16
|
+
ForkHooks.register(integration, component: component, &)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
class Scoped
|
|
8
|
+
def initialize(integration, runtime: nil)
|
|
9
|
+
@integration = integration
|
|
10
|
+
@runtime = runtime
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def record_failure(error, **metadata)
|
|
14
|
+
Health.record_failure(@integration, error, runtime: @runtime, **metadata)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def record_success(*, **)
|
|
18
|
+
Health.record_success(@integration, runtime: @runtime)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def with_failure_health(component:, action:, **metadata, &)
|
|
22
|
+
Health.with_failure_health(
|
|
23
|
+
@integration,
|
|
24
|
+
component: component,
|
|
25
|
+
action: action,
|
|
26
|
+
runtime: @runtime,
|
|
27
|
+
**metadata,
|
|
28
|
+
&
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module Settings
|
|
8
|
+
class << self
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def setting(name, default: nil, predicate: false, validate: nil, &block)
|
|
16
|
+
settings_defaults[name] = block || proc { default }
|
|
17
|
+
settings_validators[name] = validate if validate
|
|
18
|
+
ivar = :"@#{name}"
|
|
19
|
+
|
|
20
|
+
define_method(name) { instance_variable_get(ivar) }
|
|
21
|
+
define_method(:"#{name}=") do |value|
|
|
22
|
+
instance_variable_set(ivar, validate_setting(name, value))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
define_method(:"#{name}?") { !!public_send(name) } if predicate
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def byte_limit
|
|
29
|
+
proc { |value, name| Core::Validation.validate_byte_limit!(value, name: name) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def integer_limit(positive: false)
|
|
33
|
+
proc { |value, name| Core::Validation.validate_integer_limit!(value, name: name, positive: positive) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def settings_defaults
|
|
37
|
+
@settings_defaults ||= {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def settings_validators
|
|
41
|
+
@settings_validators ||= {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize
|
|
46
|
+
initialize_settings
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate!
|
|
50
|
+
validate_settings!
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def initialize_settings
|
|
57
|
+
self.class.settings_defaults.each do |name, default|
|
|
58
|
+
public_send(:"#{name}=", setting_default(default))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def setting_default(default)
|
|
63
|
+
Core::Fields::FieldSet.deep_dup(instance_exec(&default))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def validate_settings!
|
|
67
|
+
self.class.settings_defaults.each_key do |name|
|
|
68
|
+
public_send(:"#{name}=", public_send(name))
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_setting(name, value)
|
|
73
|
+
validator = self.class.settings_validators[name]
|
|
74
|
+
return value unless validator
|
|
75
|
+
|
|
76
|
+
result = call_setting_validator(validator, name, value)
|
|
77
|
+
result.nil? ? value : result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def call_setting_validator(validator, name, value)
|
|
81
|
+
case validator
|
|
82
|
+
when Symbol
|
|
83
|
+
validator_method = method(validator)
|
|
84
|
+
validator_method.arity == 1 ? validator_method.call(value) : validator_method.call(value, name)
|
|
85
|
+
else
|
|
86
|
+
validator.arity == 1 ? instance_exec(value, &validator) : instance_exec(value, name, &validator)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Integration
|
|
6
|
+
# @api integration_spi
|
|
7
|
+
module SubscriberInstall
|
|
8
|
+
def subscriber = @subscription&.subscriber
|
|
9
|
+
|
|
10
|
+
def installed? = !subscriber.nil?
|
|
11
|
+
|
|
12
|
+
def reset!
|
|
13
|
+
@subscription&.reset
|
|
14
|
+
@subscription = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def update_subscription(configuration)
|
|
20
|
+
@subscription&.update(configuration)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def store_subscription(subscriber, unsubscribe: nil)
|
|
24
|
+
@subscription = Subscription.new(subscriber, unsubscribe: unsubscribe)
|
|
25
|
+
subscriber
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def install_subscriber(configuration, enabled:)
|
|
29
|
+
return reset! unless enabled
|
|
30
|
+
return update_subscription(configuration) if installed?
|
|
31
|
+
|
|
32
|
+
subscriber = new(configuration)
|
|
33
|
+
unsubscribe = yield subscriber
|
|
34
|
+
store_subscription(subscriber, unsubscribe: unsubscribe)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|