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.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +73 -0
  5. data/docs/advanced-configuration.md +66 -0
  6. data/docs/attribute-keys.md +74 -0
  7. data/docs/configuration.md +327 -0
  8. data/docs/context-and-propagation.md +353 -0
  9. data/docs/contracts.md +211 -0
  10. data/docs/development.md +49 -0
  11. data/docs/extensions-and-api.md +567 -0
  12. data/docs/health-schema.md +104 -0
  13. data/docs/instrumentation-cheatsheet.md +29 -0
  14. data/docs/internals.md +135 -0
  15. data/docs/outputs-and-lifecycle.md +206 -0
  16. data/docs/quickstart.md +133 -0
  17. data/docs/record-sources.md +17 -0
  18. data/docs/records-and-data-policy.md +230 -0
  19. data/docs/security-and-wire.md +45 -0
  20. data/docs/tail.md +91 -0
  21. data/exe/julewire +6 -0
  22. data/julewire-core.gemspec +41 -0
  23. data/lib/julewire/core/cli/doctor.rb +143 -0
  24. data/lib/julewire/core/cli/line_helpers.rb +77 -0
  25. data/lib/julewire/core/cli/log_formats/console_text.rb +25 -0
  26. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +46 -0
  27. data/lib/julewire/core/cli/log_formats/core_json_encoder.rb +21 -0
  28. data/lib/julewire/core/cli/log_formats/record_decoder.rb +39 -0
  29. data/lib/julewire/core/cli/log_formats.rb +123 -0
  30. data/lib/julewire/core/cli/tail.rb +153 -0
  31. data/lib/julewire/core/cli/transcode.rb +105 -0
  32. data/lib/julewire/core/cli.rb +73 -0
  33. data/lib/julewire/core/configuration.rb +99 -0
  34. data/lib/julewire/core/context_store.rb +384 -0
  35. data/lib/julewire/core/destinations/chaos_output.rb +91 -0
  36. data/lib/julewire/core/destinations/collection.rb +177 -0
  37. data/lib/julewire/core/destinations/definition.rb +125 -0
  38. data/lib/julewire/core/destinations/destination.rb +268 -0
  39. data/lib/julewire/core/destinations/registry.rb +81 -0
  40. data/lib/julewire/core/destinations/sink.rb +35 -0
  41. data/lib/julewire/core/destinations/synchronized_output.rb +57 -0
  42. data/lib/julewire/core/destinations/tail_sampling.rb +321 -0
  43. data/lib/julewire/core/destinations/write_step.rb +119 -0
  44. data/lib/julewire/core/destinations.rb +33 -0
  45. data/lib/julewire/core/diagnostics/callback_notifier.rb +63 -0
  46. data/lib/julewire/core/diagnostics/doctor.rb +114 -0
  47. data/lib/julewire/core/diagnostics/failure_snapshot.rb +39 -0
  48. data/lib/julewire/core/diagnostics/health.rb +144 -0
  49. data/lib/julewire/core/diagnostics/integration_health_store.rb +64 -0
  50. data/lib/julewire/core/diagnostics/internal_records.rb +61 -0
  51. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +112 -0
  52. data/lib/julewire/core/diagnostics/meta_observer.rb +161 -0
  53. data/lib/julewire/core/diagnostics/process_integration_health.rb +26 -0
  54. data/lib/julewire/core/diagnostics/tail/renderer.rb +36 -0
  55. data/lib/julewire/core/diagnostics/tail.rb +168 -0
  56. data/lib/julewire/core/diagnostics.rb +8 -0
  57. data/lib/julewire/core/error.rb +7 -0
  58. data/lib/julewire/core/execution/boundary.rb +106 -0
  59. data/lib/julewire/core/execution/handle.rb +77 -0
  60. data/lib/julewire/core/execution/lineage.rb +192 -0
  61. data/lib/julewire/core/execution/measurement_handle.rb +28 -0
  62. data/lib/julewire/core/execution/no_current_error.rb +9 -0
  63. data/lib/julewire/core/execution/scope.rb +246 -0
  64. data/lib/julewire/core/execution/scope_fields.rb +76 -0
  65. data/lib/julewire/core/execution/scope_identity.rb +71 -0
  66. data/lib/julewire/core/execution/scope_snapshot.rb +92 -0
  67. data/lib/julewire/core/execution/summary_state.rb +206 -0
  68. data/lib/julewire/core/execution/view.rb +56 -0
  69. data/lib/julewire/core/facade_methods.rb +181 -0
  70. data/lib/julewire/core/fields/attribute_keys.rb +54 -0
  71. data/lib/julewire/core/fields/attributes_proxy.rb +11 -0
  72. data/lib/julewire/core/fields/bags.rb +123 -0
  73. data/lib/julewire/core/fields/carry_proxy.rb +22 -0
  74. data/lib/julewire/core/fields/context_proxy.rb +11 -0
  75. data/lib/julewire/core/fields/field_set.rb +78 -0
  76. data/lib/julewire/core/fields/field_stack.rb +269 -0
  77. data/lib/julewire/core/fields/internal/deletion.rb +68 -0
  78. data/lib/julewire/core/fields/internal.rb +87 -0
  79. data/lib/julewire/core/fields/lookup.rb +35 -0
  80. data/lib/julewire/core/fields/section_proxy.rb +88 -0
  81. data/lib/julewire/core/fields/stack_set.rb +69 -0
  82. data/lib/julewire/core/fields/static_labels.rb +43 -0
  83. data/lib/julewire/core/fields/summary_proxy.rb +62 -0
  84. data/lib/julewire/core/integration/configurable.rb +52 -0
  85. data/lib/julewire/core/integration/destination_health.rb +43 -0
  86. data/lib/julewire/core/integration/event_subscriber.rb +62 -0
  87. data/lib/julewire/core/integration/facade.rb +131 -0
  88. data/lib/julewire/core/integration/fork_hooks.rb +79 -0
  89. data/lib/julewire/core/integration/health.rb +41 -0
  90. data/lib/julewire/core/integration/ivar_state.rb +38 -0
  91. data/lib/julewire/core/integration/lifecycle.rb +22 -0
  92. data/lib/julewire/core/integration/scoped.rb +34 -0
  93. data/lib/julewire/core/integration/settings.rb +92 -0
  94. data/lib/julewire/core/integration/subscriber_install.rb +39 -0
  95. data/lib/julewire/core/integration/subscription.rb +29 -0
  96. data/lib/julewire/core/integration/values.rb +192 -0
  97. data/lib/julewire/core/lifecycle_error.rb +7 -0
  98. data/lib/julewire/core/local_storage.rb +91 -0
  99. data/lib/julewire/core/processing/level_threshold.rb +53 -0
  100. data/lib/julewire/core/processing/match.rb +74 -0
  101. data/lib/julewire/core/processing/pipeline.rb +360 -0
  102. data/lib/julewire/core/processing/processor_chain.rb +69 -0
  103. data/lib/julewire/core/processing/processor_registry.rb +115 -0
  104. data/lib/julewire/core/processing/processor_wrapper.rb +44 -0
  105. data/lib/julewire/core/processing/record_field_transform.rb +124 -0
  106. data/lib/julewire/core/processing/sampling.rb +109 -0
  107. data/lib/julewire/core/processing.rb +41 -0
  108. data/lib/julewire/core/propagation/carrier.rb +93 -0
  109. data/lib/julewire/core/propagation.rb +50 -0
  110. data/lib/julewire/core/records/console_formatter.rb +24 -0
  111. data/lib/julewire/core/records/deconstruct.rb +19 -0
  112. data/lib/julewire/core/records/display_message.rb +166 -0
  113. data/lib/julewire/core/records/draft.rb +576 -0
  114. data/lib/julewire/core/records/formatter.rb +14 -0
  115. data/lib/julewire/core/records/lazy_emit_input.rb +99 -0
  116. data/lib/julewire/core/records/metadata.rb +23 -0
  117. data/lib/julewire/core/records/public_projection.rb +51 -0
  118. data/lib/julewire/core/records/raw_input.rb +41 -0
  119. data/lib/julewire/core/records/record.rb +175 -0
  120. data/lib/julewire/core/records/severity.rb +44 -0
  121. data/lib/julewire/core/runtime.rb +515 -0
  122. data/lib/julewire/core/runtime_locator.rb +20 -0
  123. data/lib/julewire/core/runtime_registry.rb +48 -0
  124. data/lib/julewire/core/runtime_state.rb +39 -0
  125. data/lib/julewire/core/scheduling/deadline.rb +24 -0
  126. data/lib/julewire/core/scheduling/deadline_scheduler.rb +207 -0
  127. data/lib/julewire/core/scheduling/shared_scheduler.rb +48 -0
  128. data/lib/julewire/core/sentinel.rb +18 -0
  129. data/lib/julewire/core/serialization/backtrace_limiter.rb +50 -0
  130. data/lib/julewire/core/serialization/bounded_transform.rb +55 -0
  131. data/lib/julewire/core/serialization/bounded_traversal.rb +274 -0
  132. data/lib/julewire/core/serialization/deep_compact_empty.rb +67 -0
  133. data/lib/julewire/core/serialization/deep_freeze.rb +63 -0
  134. data/lib/julewire/core/serialization/encoding_sanitizer.rb +40 -0
  135. data/lib/julewire/core/serialization/exception_shape.rb +88 -0
  136. data/lib/julewire/core/serialization/json_encoder.rb +69 -0
  137. data/lib/julewire/core/serialization/serializer.rb +233 -0
  138. data/lib/julewire/core/serialization/serializer_pool.rb +21 -0
  139. data/lib/julewire/core/serialization/text_encoder.rb +147 -0
  140. data/lib/julewire/core/serialization/value_copy.rb +209 -0
  141. data/lib/julewire/core/serialization/value_traversal.rb +150 -0
  142. data/lib/julewire/core/testing/chaos/catalog.rb +72 -0
  143. data/lib/julewire/core/testing/chaos/core_runtime.rb +120 -0
  144. data/lib/julewire/core/testing/chaos/destination.rb +55 -0
  145. data/lib/julewire/core/testing/chaos/emitter.rb +20 -0
  146. data/lib/julewire/core/testing/chaos/raising_output.rb +42 -0
  147. data/lib/julewire/core/testing/chaos.rb +80 -0
  148. data/lib/julewire/core/testing/contracts/component.rb +162 -0
  149. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +59 -0
  150. data/lib/julewire/core/testing/contracts/integration.rb +166 -0
  151. data/lib/julewire/core/testing/contracts/integration_fields.rb +36 -0
  152. data/lib/julewire/core/testing/contracts/record_draft.rb +37 -0
  153. data/lib/julewire/core/testing/contracts/runtime.rb +178 -0
  154. data/lib/julewire/core/testing/contracts/wire.rb +60 -0
  155. data/lib/julewire/core/testing/contracts.rb +24 -0
  156. data/lib/julewire/core/testing/coverage.rb +58 -0
  157. data/lib/julewire/core/testing/test_reports.rb +78 -0
  158. data/lib/julewire/core/testing.rb +122 -0
  159. data/lib/julewire/core/validation.rb +69 -0
  160. data/lib/julewire/core/version.rb +7 -0
  161. data/lib/julewire/core.rb +80 -0
  162. data/lib/julewire/error.rb +5 -0
  163. data/lib/julewire-core.rb +3 -0
  164. 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