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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +20 -0
- data/docs/contracts.md +23 -49
- data/docs/extensions-and-api.md +78 -82
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +21 -20
- data/docs/outputs-and-lifecycle.md +10 -6
- data/docs/records-and-data-policy.md +36 -5
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +51 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +13 -63
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +16 -9
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +12 -17
- data/lib/julewire/core/execution/lineage.rb +16 -56
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +15 -20
- data/lib/julewire/core/execution/scope_fields.rb +3 -12
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +29 -19
- data/lib/julewire/core/fields/field_stack.rb +68 -134
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +19 -38
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +7 -12
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +17 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +14 -14
- data/lib/julewire/core/propagation.rb +15 -2
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +125 -262
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +19 -37
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +67 -71
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
- data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +23 -50
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
- data/lib/julewire/core/serialization/value_copy.rb +60 -227
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +6 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -40,7 +40,10 @@ strings.
|
|
|
40
40
|
|
|
41
41
|
Processors may intentionally return `:drop`. Core counts those records in
|
|
42
42
|
`health[:pipeline][:counts][:processor_dropped]`; processor failures are counted
|
|
43
|
-
separately as `processor_error`.
|
|
43
|
+
separately as `processor_error`. Unsupported non-`nil`/non-draft return values
|
|
44
|
+
keep the current draft and increment `processor_invalid`. A processor that
|
|
45
|
+
leaves an invalid draft also increments `processor_invalid`, but that record is
|
|
46
|
+
dropped before the next processor and also increments `processor_dropped`.
|
|
44
47
|
|
|
45
48
|
Async queues, files, fanout appenders, batching, retries, acknowledgements,
|
|
46
49
|
reopen, rotation, shutdown hooks, and delivery policy belong in custom
|
|
@@ -149,11 +152,12 @@ out, `Julewire.close` returns `false`, but later emits still drop as
|
|
|
149
152
|
`runtime_closed` until the next `configure` or `reset!`.
|
|
150
153
|
|
|
151
154
|
`Julewire.after_fork!` resets process-local counters, failure snapshots,
|
|
152
|
-
current context, warning state, and
|
|
153
|
-
It also forwards `after_fork!` to destinations
|
|
154
|
-
then runs integration after-fork hooks
|
|
155
|
-
queue, and async transports should
|
|
156
|
-
destination or output `after_fork!`
|
|
155
|
+
current context, warning state, schedulers, and registries that cannot be
|
|
156
|
+
shared with the child process. It also forwards `after_fork!` to destinations
|
|
157
|
+
and outputs that implement it, then runs integration after-fork hooks
|
|
158
|
+
registered through core. File, socket, queue, and async transports should
|
|
159
|
+
reopen worker-local resources from their destination or output `after_fork!`
|
|
160
|
+
method.
|
|
157
161
|
|
|
158
162
|
Core does not install an `at_exit` hook. Small scripts should call close from
|
|
159
163
|
their own shutdown path:
|
|
@@ -27,10 +27,11 @@ string or symbol keys defensively. Processors receive a mutable
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Raw input is normalized through `Julewire::RecordDraft.build`, then processors
|
|
30
|
-
mutate the draft owned by the current emit.
|
|
31
|
-
|
|
32
|
-
before destinations and formatters see it.
|
|
33
|
-
|
|
30
|
+
mutate the draft owned by the current emit. Core validates the draft after
|
|
31
|
+
every processor, then freezes it through `Julewire::RecordDraft#to_record`
|
|
32
|
+
before destinations and formatters see it. A malformed processor result is
|
|
33
|
+
attributed to that processor and never reaches the next one. Immutable
|
|
34
|
+
`Record` objects are not the public raw-input construction API.
|
|
34
35
|
|
|
35
36
|
`Julewire::RecordFormatter` turns that normalized record into a public log
|
|
36
37
|
projection. It omits internal keys such as `:carry` and execution lineage
|
|
@@ -59,6 +60,11 @@ source/event metadata when available. Level filtering then applies normally, so
|
|
|
59
60
|
an invalid explicit severity can still be dropped when `config.level` is above
|
|
60
61
|
`:info`. Configuration severities remain strict.
|
|
61
62
|
|
|
63
|
+
Severity Symbols are the canonical lowercase values `:debug`, `:info`, `:warn`,
|
|
64
|
+
`:error`, `:fatal`, and `:unknown`. String ingress is case-insensitive and
|
|
65
|
+
standard-library Logger severity integers are accepted. Non-canonical Symbols
|
|
66
|
+
are invalid rather than silently rewritten at an internal or cross-gem boundary.
|
|
67
|
+
|
|
62
68
|
## Structured Sections
|
|
63
69
|
|
|
64
70
|
Structured sections prefer hashes:
|
|
@@ -87,13 +93,38 @@ Julewire.emit(payload: "raw")
|
|
|
87
93
|
```
|
|
88
94
|
|
|
89
95
|
Runtime mutation helpers are forgiving for the same reason: logging should not
|
|
90
|
-
crash the app.
|
|
96
|
+
crash the app when a positional field value is not a hash.
|
|
91
97
|
|
|
92
98
|
String keys inside structured sections are normalized to symbols before
|
|
93
99
|
processors and destinations run. Encoders and custom destinations may convert
|
|
94
100
|
the record to string-key payloads, but the core Ruby record contract stays
|
|
95
101
|
symbol-keyed.
|
|
96
102
|
|
|
103
|
+
## Key Boundaries
|
|
104
|
+
|
|
105
|
+
Public application input and decoded propagation envelopes may use String or
|
|
106
|
+
Symbol keys. Core converts String keys to Symbols once as those values enter
|
|
107
|
+
core. Other key types are invalid; they are never stringified or silently
|
|
108
|
+
accepted.
|
|
109
|
+
|
|
110
|
+
Integration-owned, bridge-owned, processor-owned, and normalized-record sections
|
|
111
|
+
are different: each section must be a Hash and every recursive hash key must
|
|
112
|
+
already be a Symbol. They are strict contracts, not another normalization
|
|
113
|
+
boundary. Core does not convert their String keys. JSON carrier extraction is
|
|
114
|
+
the explicit exception because JSON decode is wire ingress and necessarily
|
|
115
|
+
produces String keys.
|
|
116
|
+
|
|
117
|
+
Direct constructors and integration field APIs raise `TypeError` for a non-Hash
|
|
118
|
+
section or invalid key. Strict owned validation visits every recursive
|
|
119
|
+
container iteratively, including data deeper than the later copy bound. A
|
|
120
|
+
normal `emit` contains such a failure: it drops the invalid
|
|
121
|
+
record, records the failure in health and `on_failure`, and does not raise into
|
|
122
|
+
application code. Integration input is validated recursively before draft
|
|
123
|
+
construction or any configured processor. Unknown integration top-level fields
|
|
124
|
+
are errors instead of being ignored; integrations put custom data in
|
|
125
|
+
`:payload`, `:attributes`, or another documented section. An unconfigured
|
|
126
|
+
pipeline drops before record construction, as it does for every emit.
|
|
127
|
+
|
|
97
128
|
## Optional Metadata
|
|
98
129
|
|
|
99
130
|
`logger` is the logical logger entrypoint, for example `"app"` or
|
|
@@ -23,21 +23,22 @@ module Julewire
|
|
|
23
23
|
def call
|
|
24
24
|
options = doctor_options
|
|
25
25
|
report = Julewire.doctor
|
|
26
|
-
options
|
|
26
|
+
if options[:format] == :text
|
|
27
|
+
write_text(report, options)
|
|
28
|
+
else
|
|
29
|
+
write_json(report)
|
|
30
|
+
end
|
|
27
31
|
0
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
private
|
|
31
35
|
|
|
32
36
|
def doctor_options
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
else
|
|
39
|
-
raise ArgumentError, "unknown option #{value}"
|
|
40
|
-
end
|
|
37
|
+
@argv.each_with_object(color: color_output?) do |value, options|
|
|
38
|
+
if (assignment = FLAGS[value])
|
|
39
|
+
apply_option(options, assignment)
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, "unknown option #{value}"
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
end
|
|
@@ -46,7 +47,7 @@ module Julewire
|
|
|
46
47
|
key = assignment.fetch(0)
|
|
47
48
|
value = assignment.fetch(1)
|
|
48
49
|
options[key] = value
|
|
49
|
-
options[:format] = :text if
|
|
50
|
+
options[:format] = :text if value == :punk
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def color_output?
|
|
@@ -59,18 +60,19 @@ module Julewire
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def write_text(report, options)
|
|
62
|
-
theme = options
|
|
63
|
+
theme = options[:theme]
|
|
63
64
|
color = options.fetch(:color)
|
|
65
|
+
pipeline = report.fetch(:pipeline)
|
|
64
66
|
lines = [
|
|
65
67
|
title(theme),
|
|
66
68
|
status_line(report, theme: theme, color: color),
|
|
67
69
|
runtime_line(report.fetch(:runtime)),
|
|
68
|
-
pipeline_line(
|
|
69
|
-
component_line("destinations",
|
|
70
|
+
pipeline_line(pipeline),
|
|
71
|
+
component_line("destinations", pipeline.fetch(:destinations)),
|
|
70
72
|
component_line("runtime_integrations", report.fetch(:integrations)),
|
|
71
73
|
component_line("process_integrations", report.fetch(:process_integrations)),
|
|
72
74
|
warning_lines(report.fetch(:warnings), theme: theme)
|
|
73
|
-
]
|
|
75
|
+
]
|
|
74
76
|
@stdout.write("#{lines.join("\n")}\n")
|
|
75
77
|
end
|
|
76
78
|
|
|
@@ -108,7 +110,9 @@ module Julewire
|
|
|
108
110
|
return "warnings=none" if warnings.empty?
|
|
109
111
|
|
|
110
112
|
header = theme == :punk ? "!! warnings=#{warnings.length}" : "warnings=#{warnings.length}"
|
|
111
|
-
[header
|
|
113
|
+
lines = [header]
|
|
114
|
+
warnings.each { lines << warning_line(it, theme: theme) }
|
|
115
|
+
lines
|
|
112
116
|
end
|
|
113
117
|
|
|
114
118
|
def warning_line(warning, theme:)
|
|
@@ -125,7 +129,7 @@ module Julewire
|
|
|
125
129
|
return value unless color
|
|
126
130
|
|
|
127
131
|
styles = severity_styles(theme)
|
|
128
|
-
"\e[#{styles.fetch(severity
|
|
132
|
+
"\e[#{styles.fetch(severity)}m#{value}\e[0m"
|
|
129
133
|
end
|
|
130
134
|
|
|
131
135
|
def severity_for_status(status)
|
|
@@ -7,8 +7,12 @@ module Julewire
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def parse_command_options(options, command:)
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
@argv.length.times do
|
|
11
|
+
break if @argv.empty?
|
|
12
|
+
|
|
13
|
+
yield(options, @argv.shift)
|
|
14
|
+
end
|
|
15
|
+
raise ArgumentError, "#{command} log path is required" unless options.fetch(:path)
|
|
12
16
|
|
|
13
17
|
options
|
|
14
18
|
end
|
|
@@ -24,26 +28,33 @@ module Julewire
|
|
|
24
28
|
value = @argv.shift
|
|
25
29
|
raise ArgumentError, "#{name} value is required" unless value
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
begin
|
|
32
|
+
integer = Integer(value, 10)
|
|
33
|
+
raise ArgumentError unless integer.positive?
|
|
34
|
+
|
|
35
|
+
integer
|
|
36
|
+
rescue ArgumentError
|
|
37
|
+
raise ArgumentError, "#{name} must be a positive integer"
|
|
38
|
+
end
|
|
30
39
|
end
|
|
31
40
|
|
|
32
41
|
def apply_path_option(options, value, command:)
|
|
33
|
-
raise ArgumentError, "unknown option #{value}" if value.start_with?("-") && value
|
|
34
|
-
raise ArgumentError, "#{command} accepts one log path" if options
|
|
42
|
+
raise ArgumentError, "unknown option #{value}" if value.start_with?("-") && !value.eql?("-")
|
|
43
|
+
raise ArgumentError, "#{command} accepts one log path" if options.fetch(:path)
|
|
35
44
|
|
|
36
45
|
options[:path] = value
|
|
37
46
|
end
|
|
38
47
|
|
|
39
|
-
def handle_invalid_line(line,
|
|
48
|
+
def handle_invalid_line(line, mode)
|
|
40
49
|
case mode
|
|
41
50
|
when :skip
|
|
42
51
|
nil
|
|
43
52
|
when :raw
|
|
44
53
|
@stdout.write(raw_line(line))
|
|
54
|
+
when :fail
|
|
55
|
+
raise
|
|
45
56
|
else
|
|
46
|
-
raise
|
|
57
|
+
raise ArgumentError, "invalid line policy must be fail, raw, or skip"
|
|
47
58
|
end
|
|
48
59
|
end
|
|
49
60
|
|
|
@@ -53,7 +64,7 @@ module Julewire
|
|
|
53
64
|
|
|
54
65
|
def indexed_lines(lines)
|
|
55
66
|
lines.each_with_index.filter_map do |line, index|
|
|
56
|
-
[index + 1, line]
|
|
67
|
+
[index + 1, line] if line.match?(/\S/)
|
|
57
68
|
end
|
|
58
69
|
end
|
|
59
70
|
|
|
@@ -68,8 +79,8 @@ module Julewire
|
|
|
68
79
|
def write_encoded_record_line(line, line_number, input_format:, invalid:, encoder:)
|
|
69
80
|
record = LogFormats.record_from_json_line(line, line_number: line_number, format: input_format)
|
|
70
81
|
@stdout.write(encoder.call(record))
|
|
71
|
-
rescue ArgumentError
|
|
72
|
-
handle_invalid_line(line,
|
|
82
|
+
rescue ArgumentError
|
|
83
|
+
handle_invalid_line(line, invalid)
|
|
73
84
|
end
|
|
74
85
|
end
|
|
75
86
|
end
|
|
@@ -25,9 +25,9 @@ module Julewire
|
|
|
25
25
|
|
|
26
26
|
def record_base(source)
|
|
27
27
|
{
|
|
28
|
-
timestamp: source
|
|
29
|
-
severity: Records::Severity.normalize(source
|
|
30
|
-
kind: RecordDecoder.kind(source
|
|
28
|
+
timestamp: source.fetch("timestamp"),
|
|
29
|
+
severity: Records::Severity.normalize(source.fetch("severity")),
|
|
30
|
+
kind: RecordDecoder.kind(source.fetch("kind")),
|
|
31
31
|
event: source["event"],
|
|
32
32
|
message: source["message"],
|
|
33
33
|
logger: source["logger"],
|
|
@@ -11,17 +11,17 @@ module Julewire
|
|
|
11
11
|
@entries = []
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
|
-
def register(name, decoder: nil, encoder: nil, priority:
|
|
14
|
+
def register(name, decoder: nil, encoder: nil, priority: nil)
|
|
15
15
|
name = normalize(name)
|
|
16
16
|
validate_component(decoder, :decoder) if decoder
|
|
17
17
|
validate_component(encoder, :encoder) if encoder
|
|
18
18
|
existing = @entries.find { it.name == name }
|
|
19
|
-
priority = priority.
|
|
19
|
+
priority = priority.nil? ? existing&.priority || 0 : Integer(priority)
|
|
20
20
|
entry = Entry.new(
|
|
21
21
|
name: name,
|
|
22
22
|
decoder: decoder || existing&.decoder,
|
|
23
23
|
encoder: encoder || existing&.encoder,
|
|
24
|
-
priority: priority
|
|
24
|
+
priority: priority
|
|
25
25
|
)
|
|
26
26
|
@entries = @entries.reject { it.name == name } + [entry]
|
|
27
27
|
entry
|
|
@@ -43,21 +43,21 @@ module Julewire
|
|
|
43
43
|
|
|
44
44
|
def record_from_json_line(line, line_number:, format: AUTO_FORMAT)
|
|
45
45
|
payload = JSON.parse(line)
|
|
46
|
-
|
|
46
|
+
Record.from_normalized_hash(decode(payload, format: format))
|
|
47
47
|
rescue JSON::ParserError => e
|
|
48
|
-
raise ArgumentError, "line #{line_number}: invalid JSON: #{e
|
|
48
|
+
raise ArgumentError, "line #{line_number}: invalid JSON: #{e}"
|
|
49
49
|
rescue TypeError, ArgumentError => e
|
|
50
|
-
raise ArgumentError, "line #{line_number}: #{e
|
|
50
|
+
raise ArgumentError, "line #{line_number}: #{e}"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def normalize(value)
|
|
54
54
|
name = Core.normalize_name(value, name: "log format")
|
|
55
|
-
return name if name.
|
|
55
|
+
return name if name.match?(FORMAT_NAME_PATTERN)
|
|
56
56
|
|
|
57
57
|
raise ArgumentError, "log format must contain lowercase letters, digits, or underscores"
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def
|
|
60
|
+
def load_gem_format(name)
|
|
61
61
|
path = "julewire/#{name}"
|
|
62
62
|
require path
|
|
63
63
|
rescue LoadError => e
|
|
@@ -99,7 +99,7 @@ module Julewire
|
|
|
99
99
|
def load_format(name)
|
|
100
100
|
return if @entries.any? { it.name == name }
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
load_gem_format(name)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def decode_entries
|
|
@@ -55,17 +55,19 @@ module Julewire
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def apply_tail_option(options, value)
|
|
58
|
-
|
|
58
|
+
case value
|
|
59
|
+
when *FLAGS.keys
|
|
60
|
+
assignment = FLAGS.fetch(value)
|
|
59
61
|
options[assignment.fetch(0)] = assignment.fetch(1)
|
|
60
|
-
|
|
61
|
-
options[:format] =
|
|
62
|
-
|
|
62
|
+
when /\A--format=(.+)\z/
|
|
63
|
+
options[:format] = Regexp.last_match(1)
|
|
64
|
+
when "--format"
|
|
63
65
|
options[:format] = next_symbol_option("--format")
|
|
64
|
-
|
|
66
|
+
when "--theme"
|
|
65
67
|
options[:theme] = next_symbol_option("--theme")
|
|
66
|
-
|
|
68
|
+
when "--limit"
|
|
67
69
|
options[:limit] = positive_integer_option("--limit")
|
|
68
|
-
|
|
70
|
+
when "--max-value-bytes"
|
|
69
71
|
options[:max_value_bytes] = positive_integer_option("--max-value-bytes")
|
|
70
72
|
else
|
|
71
73
|
apply_path_option(options, value, command: "tail")
|
|
@@ -74,7 +76,7 @@ module Julewire
|
|
|
74
76
|
|
|
75
77
|
def tail_renderer(options)
|
|
76
78
|
encoder = console_text_encoder(options)
|
|
77
|
-
|
|
79
|
+
lambda do |line, line_number|
|
|
78
80
|
write_encoded_record_line(
|
|
79
81
|
line,
|
|
80
82
|
line_number,
|
|
@@ -87,13 +89,16 @@ module Julewire
|
|
|
87
89
|
|
|
88
90
|
def tail_stdin(options, renderer)
|
|
89
91
|
limit = options.fetch(:limit)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
if limit
|
|
93
|
+
render_limited_stdin(limit, renderer)
|
|
94
|
+
else
|
|
95
|
+
render_stream(@stdin.each_line, renderer)
|
|
96
|
+
end
|
|
93
97
|
end
|
|
94
98
|
|
|
95
99
|
def tail_file(options, renderer)
|
|
96
|
-
|
|
100
|
+
descriptor = IO.sysopen(options.fetch(:path))
|
|
101
|
+
IO.open(descriptor) do |file|
|
|
97
102
|
line_number = render_file_snapshot(file, options, renderer)
|
|
98
103
|
follow_file(file, line_number, options, renderer) if options.fetch(:follow)
|
|
99
104
|
end
|
|
@@ -113,7 +118,7 @@ module Julewire
|
|
|
113
118
|
render_entries([[line_number, line]], renderer)
|
|
114
119
|
else
|
|
115
120
|
line_number = reset_follow_position(file) if file.stat.size < file.pos
|
|
116
|
-
sleep(options.fetch(:poll_interval))
|
|
121
|
+
Kernel.sleep(options.fetch(:poll_interval))
|
|
117
122
|
end
|
|
118
123
|
end
|
|
119
124
|
end
|
|
@@ -132,7 +137,7 @@ module Julewire
|
|
|
132
137
|
line_number = 0
|
|
133
138
|
lines.each do |line|
|
|
134
139
|
line_number += 1
|
|
135
|
-
next
|
|
140
|
+
next unless line.match?(/\S/)
|
|
136
141
|
|
|
137
142
|
renderer.call(line, line_number)
|
|
138
143
|
end
|
|
@@ -69,7 +69,7 @@ module Julewire
|
|
|
69
69
|
|
|
70
70
|
def apply_named_option(options, value)
|
|
71
71
|
if value.start_with?("--from=")
|
|
72
|
-
options[:from] = value.delete_prefix("--from=")
|
|
72
|
+
options[:from] = value.delete_prefix("--from=")
|
|
73
73
|
elsif value.start_with?("--to=")
|
|
74
74
|
options[:to] = value.delete_prefix("--to=").to_sym
|
|
75
75
|
else
|
|
@@ -84,7 +84,7 @@ module Julewire
|
|
|
84
84
|
when "--theme" then options[:theme] = next_symbol_option("--theme")
|
|
85
85
|
when "--max-value-bytes" then options[:max_value_bytes] = positive_integer_option("--max-value-bytes")
|
|
86
86
|
else
|
|
87
|
-
|
|
87
|
+
raise ArgumentError, "unknown option #{value}"
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -95,9 +95,8 @@ module Julewire
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def each_entry(path, &)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
File.open(path, "r") { |file| indexed_lines(file.each_line).each(&) }
|
|
98
|
+
lines = path == "-" ? @stdin.each_line : File.foreach(path)
|
|
99
|
+
indexed_lines(lines).each(&)
|
|
101
100
|
end
|
|
102
101
|
end
|
|
103
102
|
end
|
data/lib/julewire/core/cli.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Julewire
|
|
|
24
24
|
reject_unknown_options!(options)
|
|
25
25
|
@destinations = options.fetch(:destinations) { Destinations::Registry.new }
|
|
26
26
|
@emit_non_standard_exception_summaries = options.fetch(:emit_non_standard_exception_summaries, false)
|
|
27
|
-
@error_backtrace_lines = options.fetch(:error_backtrace_lines,
|
|
27
|
+
@error_backtrace_lines = options.fetch(:error_backtrace_lines, MAX_BACKTRACE_LINES)
|
|
28
28
|
@labels = options.fetch(:labels) { Fields::StaticLabels.new }
|
|
29
29
|
@level = options.fetch(:level, :debug)
|
|
30
30
|
@on_drop = options.fetch(:on_drop, nil)
|