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,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Processing
|
|
6
|
+
# @api extension
|
|
7
|
+
module Sampling
|
|
8
|
+
HASH_SPACE = 1 << 64
|
|
9
|
+
FNV_OFFSET = 14_695_981_039_346_656_037
|
|
10
|
+
FNV_PRIME = 1_099_511_628_211
|
|
11
|
+
HASH_MASK = HASH_SPACE - 1
|
|
12
|
+
MIX_ONE = 0xff51afd7ed558ccd
|
|
13
|
+
MIX_TWO = 0xc4ceb9fe1a85ec53
|
|
14
|
+
private_constant :FNV_OFFSET, :FNV_PRIME, :HASH_MASK, :HASH_SPACE, :MIX_ONE, :MIX_TWO
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def head(rate:, key: nil)
|
|
18
|
+
Head.new(rate: rate, key: key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def keep?(rate:, key:)
|
|
22
|
+
threshold = threshold_for(rate)
|
|
23
|
+
return false if key.nil?
|
|
24
|
+
|
|
25
|
+
stable_hash(key) < threshold
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def threshold_for(rate)
|
|
29
|
+
raise ArgumentError, "rate must be a finite Numeric between 0 and 1" unless valid_rate?(rate)
|
|
30
|
+
|
|
31
|
+
(rate * HASH_SPACE).floor
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stable_hash(value)
|
|
35
|
+
hash = key_string(value).each_byte.reduce(FNV_OFFSET) do |hash, byte|
|
|
36
|
+
((hash ^ byte) * FNV_PRIME) & HASH_MASK
|
|
37
|
+
end
|
|
38
|
+
mix_hash(hash)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def valid_rate?(rate)
|
|
44
|
+
rate.between?(0, 1)
|
|
45
|
+
rescue StandardError
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mix_hash(hash)
|
|
50
|
+
hash ^= hash >> 33
|
|
51
|
+
hash = (hash * MIX_ONE) & HASH_MASK
|
|
52
|
+
hash ^= hash >> 33
|
|
53
|
+
hash = (hash * MIX_TWO) & HASH_MASK
|
|
54
|
+
hash ^ (hash >> 33)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def key_string(value)
|
|
58
|
+
case value
|
|
59
|
+
when String then value
|
|
60
|
+
when Symbol then value.name
|
|
61
|
+
else value.inspect
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Head
|
|
67
|
+
def initialize(rate:, key:)
|
|
68
|
+
@threshold = Sampling.threshold_for(rate)
|
|
69
|
+
@key = key
|
|
70
|
+
Validation.validate_callable!(key, name: :key, allow_nil: true)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def call(draft)
|
|
74
|
+
return :drop if @threshold.zero?
|
|
75
|
+
|
|
76
|
+
value = key_for(draft)
|
|
77
|
+
return :drop if value.nil?
|
|
78
|
+
return if @threshold == HASH_SPACE
|
|
79
|
+
|
|
80
|
+
Sampling.stable_hash(value) < @threshold ? nil : :drop
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def key_for(draft)
|
|
86
|
+
@key ? @key.call(draft) : default_key(draft)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def default_key(draft)
|
|
90
|
+
root_id(draft) ||
|
|
91
|
+
field_value(draft[:execution], :id) ||
|
|
92
|
+
field_value(draft[:context], :request_id) ||
|
|
93
|
+
[draft[:source], draft[:event], draft[:message]].join("\0")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def root_id(draft)
|
|
97
|
+
field_value(draft.lineage.root_reference, :id) if draft.respond_to?(:lineage)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def field_value(hash, key)
|
|
101
|
+
return unless hash.is_a?(Hash)
|
|
102
|
+
|
|
103
|
+
Fields::FieldSet.value_for(hash, key)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Processing
|
|
6
|
+
@factories = {}
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def register(kind, &factory)
|
|
10
|
+
raise ArgumentError, "processor factory block required" unless factory
|
|
11
|
+
|
|
12
|
+
@factories[normalize_kind(kind)] = factory
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build(kind, ...)
|
|
17
|
+
factory = factory_for(kind)
|
|
18
|
+
raise ArgumentError, "unknown processor kind #{kind.inspect}" unless factory
|
|
19
|
+
|
|
20
|
+
factory.call(...)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def factory_for(kind)
|
|
24
|
+
@factories[normalize_kind(kind)]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def normalize_kind(kind)
|
|
30
|
+
raise ArgumentError, "processor kind is required" if kind.nil?
|
|
31
|
+
raise ArgumentError, "processor kind must respond to #to_sym" unless kind.respond_to?(:to_sym)
|
|
32
|
+
|
|
33
|
+
name = kind.to_sym
|
|
34
|
+
raise ArgumentError, "processor kind cannot be empty" if name.name.empty?
|
|
35
|
+
|
|
36
|
+
name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Julewire
|
|
6
|
+
module Core
|
|
7
|
+
module Propagation
|
|
8
|
+
# @api public
|
|
9
|
+
module Carrier
|
|
10
|
+
DEFAULT_KEY = "julewire"
|
|
11
|
+
DEFAULT_ENVELOPE = Core.sentinel(:default_envelope)
|
|
12
|
+
private_constant :DEFAULT_ENVELOPE
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def encode(envelope: DEFAULT_ENVELOPE, max_bytes: nil)
|
|
16
|
+
Validation.validate_byte_limit!(max_bytes, name: :max_bytes)
|
|
17
|
+
|
|
18
|
+
encoded = JSON.generate(serialized_envelope(envelope), allow_nan: false)
|
|
19
|
+
return if max_bytes && encoded.bytesize > max_bytes
|
|
20
|
+
|
|
21
|
+
encoded
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inject(carrier = {}, envelope: DEFAULT_ENVELOPE, key: DEFAULT_KEY, max_bytes: nil)
|
|
25
|
+
validate_carrier!(carrier)
|
|
26
|
+
encoded = encode(envelope: envelope, max_bytes: max_bytes)
|
|
27
|
+
clear_carrier_key!(carrier, key) unless encoded
|
|
28
|
+
return unless encoded
|
|
29
|
+
|
|
30
|
+
carrier[key.to_s] = encoded
|
|
31
|
+
carrier
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def extract(carrier, key: DEFAULT_KEY)
|
|
35
|
+
value = carrier_value(carrier, key)
|
|
36
|
+
return {} unless value
|
|
37
|
+
|
|
38
|
+
parsed = JSON.parse(value.to_s)
|
|
39
|
+
parsed.is_a?(Hash) ? Fields::FieldSet.deep_symbolize_keys(parsed) : {}
|
|
40
|
+
rescue StandardError
|
|
41
|
+
{}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def restore(carrier, key: DEFAULT_KEY, link_executions: false, &)
|
|
45
|
+
Propagation.restore(extract(carrier, key: key), link_executions: link_executions, &)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def serialized_envelope(envelope)
|
|
49
|
+
return Propagation.capture if envelope.equal?(DEFAULT_ENVELOPE)
|
|
50
|
+
|
|
51
|
+
Serialization::Serializer.call(envelope)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def carrier_value(carrier, key)
|
|
57
|
+
return unless carrier.respond_to?(:[])
|
|
58
|
+
|
|
59
|
+
carrier[key.to_s] || carrier[Fields::Internal.normalize_key(key)]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_carrier!(carrier)
|
|
63
|
+
return if carrier.respond_to?(:[]=)
|
|
64
|
+
|
|
65
|
+
raise ArgumentError, "carrier must support []="
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def clear_carrier_key!(carrier, key)
|
|
69
|
+
string_key = key.to_s
|
|
70
|
+
symbol_key = Fields::Internal.normalize_key(key)
|
|
71
|
+
if carrier.respond_to?(:delete)
|
|
72
|
+
begin
|
|
73
|
+
carrier.delete(string_key)
|
|
74
|
+
carrier.delete(symbol_key)
|
|
75
|
+
rescue StandardError
|
|
76
|
+
clear_carrier_key_by_assignment(carrier, string_key, symbol_key)
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
clear_carrier_key_by_assignment(carrier, string_key, symbol_key)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def clear_carrier_key_by_assignment(carrier, string_key, symbol_key)
|
|
84
|
+
carrier[string_key] = nil
|
|
85
|
+
carrier[symbol_key] = nil
|
|
86
|
+
rescue StandardError
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
# @api public
|
|
6
|
+
module Propagation
|
|
7
|
+
FIELD_SECTIONS = (Fields::Bags.propagation_sections - [:execution]).freeze
|
|
8
|
+
private_constant :FIELD_SECTIONS
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def capture
|
|
12
|
+
capture_with { Serialization::Serializer.call(it) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def capture_local
|
|
16
|
+
capture_with { Fields::FieldSet.deep_dup(it) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def restore(envelope, link_executions: false, &)
|
|
20
|
+
raise ArgumentError, "block required" unless block_given?
|
|
21
|
+
|
|
22
|
+
sections = FIELD_SECTIONS.to_h { |section| [section, hash_value(envelope, section)] }
|
|
23
|
+
execution = hash_value(envelope, :execution)
|
|
24
|
+
ContextStore.current.with_propagation(
|
|
25
|
+
**sections,
|
|
26
|
+
execution: execution,
|
|
27
|
+
link_executions: link_executions,
|
|
28
|
+
&
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def capture_with
|
|
35
|
+
store = ContextStore.current
|
|
36
|
+
scope = store.current_scope_or_snapshot
|
|
37
|
+
envelope = FIELD_SECTIONS.to_h { |section| [section, yield(store.public_send(:"#{section}_hash"))] }
|
|
38
|
+
execution = scope ? scope.execution_hash : {}
|
|
39
|
+
envelope[:execution] = yield(execution) unless execution.empty?
|
|
40
|
+
envelope
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hash_value(hash, key)
|
|
44
|
+
value = Fields::FieldSet.value_for(hash, key)
|
|
45
|
+
value.is_a?(Hash) ? value : {}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Records
|
|
6
|
+
# @api extension
|
|
7
|
+
class ConsoleFormatter
|
|
8
|
+
def call(record)
|
|
9
|
+
Record.validate_normalized!(record)
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
event: record[:event],
|
|
13
|
+
labels: record[:labels],
|
|
14
|
+
message: DisplayMessage.call(record),
|
|
15
|
+
payload: record[:payload],
|
|
16
|
+
severity: record[:severity],
|
|
17
|
+
source: record[:source],
|
|
18
|
+
timestamp: record[:timestamp]
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Records
|
|
6
|
+
module Deconstruct
|
|
7
|
+
# Host record classes expose their immutable field hash through @data.
|
|
8
|
+
def deconstruct_keys(keys)
|
|
9
|
+
return to_h unless keys
|
|
10
|
+
|
|
11
|
+
keys.each_with_object({}) do |key, selected|
|
|
12
|
+
selected[key] = Fields::FieldSet.deep_dup(@data[key]) if @data.key?(key)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
private_constant :Deconstruct
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Records
|
|
6
|
+
class DisplayMessage
|
|
7
|
+
class << self
|
|
8
|
+
def call(record)
|
|
9
|
+
error = value_at(record, :error)
|
|
10
|
+
metrics = value_at(record, :metrics)
|
|
11
|
+
neutral = value_at(record, :neutral)
|
|
12
|
+
|
|
13
|
+
explicit_message(record) || neutral_message(neutral, error, metrics) || error_summary(error)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def error_summary(error)
|
|
17
|
+
error = hash_error(error)
|
|
18
|
+
|
|
19
|
+
error_class = value_at(error, :class)
|
|
20
|
+
error_message = value_at(error, :message)
|
|
21
|
+
error_class = nil if blank?(error_class)
|
|
22
|
+
error_message = nil if blank?(error_message)
|
|
23
|
+
return unless error_class || error_message
|
|
24
|
+
return error_message.to_s unless error_class
|
|
25
|
+
return error_class.to_s unless error_message
|
|
26
|
+
|
|
27
|
+
"#{error_class}: #{error_message}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def explicit_message(record)
|
|
33
|
+
message = value_at(record, :message)
|
|
34
|
+
message unless blank?(message)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def neutral_message(neutral, error, metrics)
|
|
38
|
+
http_message(neutral, error, metrics) ||
|
|
39
|
+
job_message(neutral, error, metrics) ||
|
|
40
|
+
messaging_message(neutral, error, metrics) ||
|
|
41
|
+
source_location_message(neutral)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def http_message(neutral, error, metrics)
|
|
45
|
+
method = neutral_value(neutral, Fields::AttributeKeys::HTTP_REQUEST_METHOD)
|
|
46
|
+
path = neutral_value(neutral, Fields::AttributeKeys::URL_PATH) ||
|
|
47
|
+
neutral_value(neutral, Fields::AttributeKeys::URL_FULL)
|
|
48
|
+
status = neutral_value(neutral, Fields::AttributeKeys::HTTP_RESPONSE_STATUS_CODE)
|
|
49
|
+
return if blank?(method) || blank?(path) || blank?(status)
|
|
50
|
+
|
|
51
|
+
message = "#{method} #{path} -> #{status}"
|
|
52
|
+
append_part(message, error_class(error))
|
|
53
|
+
append_part(message, duration(metrics))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def job_message(neutral, error, metrics)
|
|
57
|
+
system = neutral_value(neutral, Fields::AttributeKeys::JOB_SYSTEM)
|
|
58
|
+
name = neutral_value(neutral, Fields::AttributeKeys::JOB_NAME)
|
|
59
|
+
id = neutral_value(neutral, Fields::AttributeKeys::JOB_ID)
|
|
60
|
+
status = neutral_value(neutral, Fields::AttributeKeys::JOB_STATUS)
|
|
61
|
+
return if blank?(system) && blank?(name) && blank?(id)
|
|
62
|
+
|
|
63
|
+
message = phrase(system || "job", name || id)
|
|
64
|
+
append_part(message, key_value("queue", job_queue(neutral)))
|
|
65
|
+
append_part(message, status_phrase(status, error, metrics))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def messaging_message(neutral, error, metrics)
|
|
69
|
+
system = neutral_value(neutral, Fields::AttributeKeys::MESSAGING_SYSTEM)
|
|
70
|
+
operation = neutral_value(neutral, Fields::AttributeKeys::MESSAGING_OPERATION_NAME)
|
|
71
|
+
destination = neutral_value(neutral, Fields::AttributeKeys::MESSAGING_DESTINATION_NAME)
|
|
72
|
+
return if blank?(system) && blank?(operation) && blank?(destination)
|
|
73
|
+
|
|
74
|
+
message = phrase(system || "messaging", operation)
|
|
75
|
+
append_part(message, destination)
|
|
76
|
+
append_part(message, key_value(
|
|
77
|
+
"partition",
|
|
78
|
+
neutral_value(neutral, Fields::AttributeKeys::MESSAGING_DESTINATION_PARTITION_ID)
|
|
79
|
+
))
|
|
80
|
+
append_part(message, key_value("offset", neutral_value(neutral, Fields::AttributeKeys::MESSAGING_KAFKA_OFFSET)))
|
|
81
|
+
append_part(message, key_value(
|
|
82
|
+
"messages",
|
|
83
|
+
neutral_value(neutral, Fields::AttributeKeys::MESSAGING_BATCH_MESSAGE_COUNT)
|
|
84
|
+
))
|
|
85
|
+
append_part(message, error_class(error))
|
|
86
|
+
append_part(message, duration(metrics))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def source_location_message(neutral)
|
|
90
|
+
file = neutral_value(neutral, Fields::AttributeKeys::CODE_FILE_PATH)
|
|
91
|
+
return if blank?(file)
|
|
92
|
+
|
|
93
|
+
line = neutral_value(neutral, Fields::AttributeKeys::CODE_LINE_NUMBER)
|
|
94
|
+
function = neutral_value(neutral, Fields::AttributeKeys::CODE_FUNCTION_NAME)
|
|
95
|
+
message = line ? "#{file}:#{line}" : file.to_s.dup
|
|
96
|
+
append_part(message, function)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def status_phrase(status, error, metrics)
|
|
100
|
+
message = append_part(nil, status)
|
|
101
|
+
message = append_part(message, error_class(error))
|
|
102
|
+
message = append_part(message, duration(metrics))
|
|
103
|
+
return unless message
|
|
104
|
+
|
|
105
|
+
"-> #{message}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def error_class(error)
|
|
109
|
+
value_at(hash_error(error), :class)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def duration(metrics)
|
|
113
|
+
duration_ms = value_at(metrics, :duration_ms)
|
|
114
|
+
"in #{duration_text(Float(duration_ms))}ms"
|
|
115
|
+
rescue ArgumentError, TypeError
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def duration_text(value)
|
|
120
|
+
text = format("%.3f", value.round(3))
|
|
121
|
+
text.delete_suffix!("0") while text.end_with?("0")
|
|
122
|
+
text.delete_suffix!(".")
|
|
123
|
+
text
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def job_queue(neutral)
|
|
127
|
+
neutral_value(neutral, Fields::AttributeKeys::JOB_QUEUE_NAME)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def hash_error(error)
|
|
131
|
+
error if error.is_a?(Hash)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def neutral_value(neutral, key)
|
|
135
|
+
value_at(neutral, key)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def key_value(name, value)
|
|
139
|
+
"#{name}=#{value}" unless blank?(value)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def phrase(first, second)
|
|
143
|
+
message = first.to_s.dup
|
|
144
|
+
append_part(message, second)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def append_part(message, part)
|
|
148
|
+
return message if blank?(part)
|
|
149
|
+
|
|
150
|
+
return part.to_s.dup unless message
|
|
151
|
+
|
|
152
|
+
message << " " << part.to_s
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def value_at(value, key)
|
|
156
|
+
Fields::Lookup.value(value, key)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def blank?(value)
|
|
160
|
+
Fields::Lookup.blank?(value)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|