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,567 @@
|
|
|
1
|
+
# Extensions and API
|
|
2
|
+
|
|
3
|
+
Core extension contracts are intentionally Ruby-ish: small objects, `call`,
|
|
4
|
+
`write`, named destinations, and optional lifecycle methods.
|
|
5
|
+
|
|
6
|
+
`contracts.md` is the contract-tier source of truth. This page adds usage
|
|
7
|
+
detail for extension and integration authors.
|
|
8
|
+
|
|
9
|
+
Core uses duck typing. Configuration-time checks only require the expected
|
|
10
|
+
method names (`call`, `write`, `emit`, `name`). Arity, keyword, and return-value
|
|
11
|
+
mistakes are contained when the extension is invoked and are reported through
|
|
12
|
+
small health counters where possible.
|
|
13
|
+
|
|
14
|
+
## Processors
|
|
15
|
+
|
|
16
|
+
Processors respond to:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
processor.call(draft)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
They receive the current `Julewire::RecordDraft`. Public `Julewire.emit`
|
|
23
|
+
input is defensive; processors own the mutable draft until the final immutable
|
|
24
|
+
record boundary.
|
|
25
|
+
|
|
26
|
+
Allowed processor returns:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
nil # draft was mutated in place or unchanged
|
|
30
|
+
draft # explicit draft return
|
|
31
|
+
:drop # stop delivery
|
|
32
|
+
anything else # ignored; current draft continues
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Mutate the draft for ordinary enrichment:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
draft[:severity] = :warn
|
|
39
|
+
draft[:payload][:sampled] = true
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Draft sections are owned by the processor pipeline, so direct mutation is the
|
|
43
|
+
primary processor API:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
draft.fetch(:context).fetch(:account)[:id] = "changed"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use transform helpers when replacing values or sections. They invalidate cached
|
|
50
|
+
records and keep execution lineage when execution identity is unchanged:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
draft.transform_field!(:severity) { :warn }
|
|
54
|
+
draft.transform_section!(:payload) { |payload| payload.merge(sampled: true) }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use `transform_record!` for whole-record replacement transforms:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
draft.transform_record! { |data| redact(data) }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Do not call `to_record` and then mutate a fetched section in place; direct
|
|
64
|
+
section mutation cannot invalidate an already-cached immutable record.
|
|
65
|
+
|
|
66
|
+
Whole-record transforms receive core-owned draft data. Return a normalized
|
|
67
|
+
record hash; the pipeline validates it when the draft becomes immutable. Values
|
|
68
|
+
set on a draft may be frozen at that boundary.
|
|
69
|
+
|
|
70
|
+
Class entries pass positional and keyword constructor arguments to the
|
|
71
|
+
processor class. Register an already-built object when the application owns the
|
|
72
|
+
instance lifecycle.
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
class AddAttribute
|
|
76
|
+
def call(draft)
|
|
77
|
+
draft[:attributes][:app] = { processed: true }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
Julewire.configure do |config|
|
|
82
|
+
config.processors.use AddTag
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For simple predicate policies, `Julewire::Match` is a processor:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
config.processors.use Julewire::Match.new do
|
|
90
|
+
on(event: /^active_record\./, payload: { duration_ms: 100.. }) do |draft|
|
|
91
|
+
draft[:labels][:slow_sql] = true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
on(severity: :debug) { :drop }
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For deterministic head sampling, use the registered `:sampling` processor:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
config.processors.use(
|
|
102
|
+
:sampling,
|
|
103
|
+
rate: 0.1,
|
|
104
|
+
key: ->(draft) { draft.lineage.root_reference&.fetch(:id, nil) }
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The default key uses execution/root identifiers, then `context.request_id`, then
|
|
109
|
+
deterministic record fields. A custom `key:` callable returning `nil` drops the
|
|
110
|
+
record. `Julewire::Sampling.keep?(rate:, key:)` exposes the same deterministic
|
|
111
|
+
rate decision for custom processor and destination policies.
|
|
112
|
+
|
|
113
|
+
Processor exceptions are contained according to the registration policy.
|
|
114
|
+
The default is `on_error: :fail_closed`: core attempts to emit a minimal
|
|
115
|
+
`julewire.processor_error` record, the original record is not delivered, and
|
|
116
|
+
later processors are not run. Use `on_error: :fail_open` for non-critical
|
|
117
|
+
enrichment processors that should record the failure, keep the current draft,
|
|
118
|
+
and continue. Use `on_error: :drop` when a failing processor should suppress
|
|
119
|
+
the record. `on_error:` is a registry option, not a processor constructor
|
|
120
|
+
keyword.
|
|
121
|
+
|
|
122
|
+
Non-raising draft corruption is detected at the final immutable
|
|
123
|
+
`Julewire::Record` boundary and contained as an `emit_record` failure
|
|
124
|
+
without per-processor attribution.
|
|
125
|
+
|
|
126
|
+
Processors can inspect execution lineage before the default formatter strips it
|
|
127
|
+
from public output. Promote only the pieces you want to expose:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
config.processors.use do |draft|
|
|
131
|
+
root_id = draft.lineage.root_reference&.fetch(:id, nil)
|
|
132
|
+
ancestor_count = draft.lineage.ancestors.length
|
|
133
|
+
|
|
134
|
+
draft[:labels][:root_execution_id] = root_id if root_id
|
|
135
|
+
draft[:payload][:ancestor_count] = ancestor_count
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Registry methods:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
config.processors.use ProcessorClass
|
|
143
|
+
config.processors.use ProcessorClass, "constructor-arg", enabled: true
|
|
144
|
+
config.processors.use EnrichmentProcessor, on_error: :fail_open
|
|
145
|
+
config.processors.prepend FirstProcessor
|
|
146
|
+
config.processors.prepend(:redaction, on_error: :fail_closed)
|
|
147
|
+
config.processors.clear
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Class entries are instantiated at configure time. Stateful processors should be
|
|
151
|
+
designed with that lifecycle in mind.
|
|
152
|
+
Integration gems may register processor kinds; applications wire them through
|
|
153
|
+
the same registry instead of constructing the common processor object directly.
|
|
154
|
+
Processor kind names are part of each integration's extension contract.
|
|
155
|
+
|
|
156
|
+
## Emit Input Lifecycle
|
|
157
|
+
|
|
158
|
+
Application emit input crosses a few small objects before it becomes a draft:
|
|
159
|
+
|
|
160
|
+
| Step | Owner | Job |
|
|
161
|
+
| ---- | ----- | --- |
|
|
162
|
+
| Facade merge | `Core.emit_input` | Combine positional input and keyword fields without normalizing app objects. |
|
|
163
|
+
| Lazy block | `Records::LazyEmitInput` | Keep block-built payloads lazy until the level gate passes and preserve eager severity helpers. |
|
|
164
|
+
| Threshold peek | `Records::RawInput` | Read severity, source, and event from raw input without building a record. |
|
|
165
|
+
| Draft build | `Draft::BuildInput` | Split raw input into normalized top-level fields plus payload. |
|
|
166
|
+
|
|
167
|
+
This split keeps below-threshold eager input and lazy blocks cheap while the
|
|
168
|
+
final immutable `Record` boundary still validates the full shape.
|
|
169
|
+
|
|
170
|
+
## Formatters
|
|
171
|
+
|
|
172
|
+
Formatters respond to:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
formatter.call(record)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
They return a payload object. The default formatter is
|
|
179
|
+
`Julewire::RecordFormatter`, which returns a public projection of the record. It
|
|
180
|
+
omits internal keys such as `:carry` and execution lineage internals.
|
|
181
|
+
`Julewire::Core::Records::PublicProjection.public_execution` exposes the same
|
|
182
|
+
execution projection without building the full output hash.
|
|
183
|
+
|
|
184
|
+
Custom formatters receive container-frozen `Julewire::Record` objects,
|
|
185
|
+
including the top-level `:carry` section. Hashes, arrays, and copied strings
|
|
186
|
+
inside the record are frozen; arbitrary app objects inside fields are still
|
|
187
|
+
object references. Use `record.to_h` for a mutable hash copy.
|
|
188
|
+
Formatters are responsible for destination-specific shape and must not mutate
|
|
189
|
+
the record; redaction policy belongs in processors or application code before
|
|
190
|
+
formatting.
|
|
191
|
+
|
|
192
|
+
## Encoders
|
|
193
|
+
|
|
194
|
+
Encoders respond to:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
encoder.call(payload)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
They receive formatter output and return the string written to the destination
|
|
201
|
+
output. The default encoder is `Julewire::JsonEncoder`, which writes one
|
|
202
|
+
serialized JSON object plus a newline. It applies Julewire serialization,
|
|
203
|
+
including JSON-safe primitives, string keys, bounds, and empty-field
|
|
204
|
+
compaction, before calling `JSON.generate`. `Julewire::TextEncoder` renders a
|
|
205
|
+
console payload or pre-rendered string as one text line.
|
|
206
|
+
|
|
207
|
+
Formatters own shape. Encoders own serialization and bytes. Keep provider
|
|
208
|
+
mapping, field names, and record projection in formatters; keep JSON, Oj, YAML,
|
|
209
|
+
raw text, or other byte encoding decisions in encoders.
|
|
210
|
+
|
|
211
|
+
## Destinations
|
|
212
|
+
|
|
213
|
+
Direct core destinations pair one formatter, one encoder, and one output:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
config.destinations.use(
|
|
217
|
+
:json_stdout,
|
|
218
|
+
formatter: Julewire::RecordFormatter.new,
|
|
219
|
+
encoder: Julewire::JsonEncoder.new,
|
|
220
|
+
output: $stdout
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Integration gems may register destination kinds. Use those through the same
|
|
225
|
+
runtime registry:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
config.destinations.use(:provider_json, output: $stdout)
|
|
229
|
+
config.destinations.use(:transport, formatter: formatter, io: $stdout)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Destination kind names are part of each integration's extension contract. A
|
|
233
|
+
leaf gem may also claim a familiar kind such as `:default` when it deliberately
|
|
234
|
+
changes that destination's runtime behavior.
|
|
235
|
+
|
|
236
|
+
When no destinations are configured, core runs in no-output mode and increments
|
|
237
|
+
`health[:pipeline][:counts][:no_output_dropped]`.
|
|
238
|
+
|
|
239
|
+
Destination names must be unique. Destination formatters get immutable
|
|
240
|
+
`Julewire::Record` objects after processors have run. The destination
|
|
241
|
+
boundary freezes normalized record containers so all formatters see the same
|
|
242
|
+
consistent container shape.
|
|
243
|
+
Use `Julewire::Core::Destinations.normalize_name` when custom destination
|
|
244
|
+
adapters accept a user-provided destination name.
|
|
245
|
+
Encoders turn formatter payload objects into strings for direct destinations.
|
|
246
|
+
Destination `on_failure` and `on_drop` callbacks inherit from global callbacks
|
|
247
|
+
unless overridden in `config.destinations.use`.
|
|
248
|
+
`processors:` may be passed to `config.destinations.use` for destination-local
|
|
249
|
+
policy. Those processors run after global processors and before that destination
|
|
250
|
+
formats the record. Their drops and failures are scoped to that destination.
|
|
251
|
+
|
|
252
|
+
Custom destinations can bypass the built-in formatter/JSON/output destination
|
|
253
|
+
and add an object directly:
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
config.destinations.add(MyDestination.new(name: :custom))
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Custom destinations must respond to `name`, `emit(record)`, `flush(timeout:)`,
|
|
260
|
+
`close(timeout:)`, and `health`. `flush` and `close` are successful when they
|
|
261
|
+
return without raising unless they return `false`.
|
|
262
|
+
`emit(record)` is successful when it returns without raising unless it returns
|
|
263
|
+
`false`. A custom destination exception is both a destination failure and a
|
|
264
|
+
dropped record; a plain `false` is a rejected record and calls `on_drop` with
|
|
265
|
+
`:destination_rejected`.
|
|
266
|
+
|
|
267
|
+
Custom destinations may also implement `after_fork!` for fork reset and
|
|
268
|
+
`resource_identity` when multiple destinations share the same closeable
|
|
269
|
+
resource. Transport adapters may expose adapter-specific lifecycle methods such
|
|
270
|
+
as `reopen`.
|
|
271
|
+
|
|
272
|
+
The registered `:tail_sampling` destination kind wraps another destination for
|
|
273
|
+
execution-level tail sampling. It buffers execution records until a summary
|
|
274
|
+
record arrives, keeps error and slow executions, samples the rest with
|
|
275
|
+
`Julewire::Sampling`, and forwards kept records to the wrapped destination:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
config.destinations.use(
|
|
279
|
+
:tail_sampling,
|
|
280
|
+
destination: Julewire::Core::Destinations::Destination.new(output: $stdout),
|
|
281
|
+
sample_rate: 0.1,
|
|
282
|
+
slow_ms: 250
|
|
283
|
+
)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Outputs
|
|
287
|
+
|
|
288
|
+
Outputs respond to:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
output.write(string)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
They may also implement:
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
flush
|
|
298
|
+
close
|
|
299
|
+
health
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Output lifecycle hooks are sync and local. Runtime timeouts provide one carried
|
|
303
|
+
deadline while core walks destinations, but plain outputs do not receive the
|
|
304
|
+
timeout value and core cannot interrupt blocking raw outputs. After the first
|
|
305
|
+
attempted resource, exhausted deadlines skip later resources. Custom
|
|
306
|
+
destinations own async drain, retry, reopen targets, rotation, and
|
|
307
|
+
timeout-aware shutdown.
|
|
308
|
+
|
|
309
|
+
Plain output writes are successful when `write` returns without raising and does
|
|
310
|
+
not return `false`. A plain `false` is treated as a rejected write. A rejection
|
|
311
|
+
is a dropped record at the core destination boundary: the destination increments
|
|
312
|
+
`output_rejected` and calls `on_drop` with `:output_rejected`. Raise an
|
|
313
|
+
exception for ordinary failures.
|
|
314
|
+
|
|
315
|
+
Custom destinations that need richer backpressure, retry, or partial-accept
|
|
316
|
+
semantics should keep that policy inside the destination and expose it through
|
|
317
|
+
destination health.
|
|
318
|
+
|
|
319
|
+
## Utility APIs
|
|
320
|
+
|
|
321
|
+
`contracts.md` owns the public utility inventory. The notes below cover the
|
|
322
|
+
parts with important ownership or boundary rules.
|
|
323
|
+
|
|
324
|
+
`EncodingSanitizer.call` repairs strings into valid UTF-8. It is intentionally
|
|
325
|
+
string-only; passing other objects is a type error.
|
|
326
|
+
|
|
327
|
+
`FieldSet` is the public helper for integration-owned field hashes. Its
|
|
328
|
+
documented surface is:
|
|
329
|
+
|
|
330
|
+
- `coerce`
|
|
331
|
+
- `merge` and `merge!`
|
|
332
|
+
- `deep_dup` and `deep_symbolize_keys`
|
|
333
|
+
- `frozen_copy`
|
|
334
|
+
- `value_for`
|
|
335
|
+
- `VALUE_KEY`
|
|
336
|
+
|
|
337
|
+
`coerce`, `merge`, and `merge!` normalize string keys to symbols and
|
|
338
|
+
defensive-copy values before inserting them, so later caller mutation does not
|
|
339
|
+
mutate core field containers. Use symbol keys after that boundary. `VALUE_KEY`
|
|
340
|
+
is the key used when non-hash field input is wrapped instead of dropped.
|
|
341
|
+
|
|
342
|
+
Other `FieldSet` singleton helpers are core-internal implementation support and
|
|
343
|
+
are not part of the extension contract.
|
|
344
|
+
|
|
345
|
+
`FieldSet.deep_dup` is intentionally narrow: it copies `Hash`, `Array`, and
|
|
346
|
+
mutable `String` values, relies on Ruby's hash-key string safety for string
|
|
347
|
+
keys, and handles cycles. Arbitrary mutable objects remain caller-owned.
|
|
348
|
+
Encoders and transport boundaries that need pure log-safe data should use
|
|
349
|
+
`Serializer.call` there.
|
|
350
|
+
|
|
351
|
+
`RecordFieldTransform` walks core's normalized record containers with
|
|
352
|
+
`BoundedTransform`. It owns record-shape policy only; processors supply the
|
|
353
|
+
actual filtering or replacement policy.
|
|
354
|
+
|
|
355
|
+
`Carrier` serializes propagation envelopes into flat string carriers for
|
|
356
|
+
external boundaries. It is provider-neutral and does not parse or synthesize
|
|
357
|
+
external headers. Use `max_bytes:` to leave a carrier unchanged and return
|
|
358
|
+
`nil` when the serialized envelope is too large for the target boundary.
|
|
359
|
+
|
|
360
|
+
`Julewire::RecordDraft.build` is the raw-input construction path used by core and
|
|
361
|
+
integration code. `Julewire::RecordDraft#to_record` freezes the final normalized data into
|
|
362
|
+
an immutable `Record` for formatters and destinations. `Record` is not a raw
|
|
363
|
+
input builder; it is the read-only destination boundary. Use
|
|
364
|
+
`Record.from_normalized_hash` only when an extension already owns a complete
|
|
365
|
+
symbol-key normalized record hash and needs the immutable destination shape.
|
|
366
|
+
|
|
367
|
+
## Public Facade
|
|
368
|
+
|
|
369
|
+
`contracts.md` owns the public facade inventory. This section covers usage
|
|
370
|
+
details that extension and integration authors usually need.
|
|
371
|
+
|
|
372
|
+
Integrations that keep process-local state can register a reset hook with
|
|
373
|
+
`Julewire::Core::Integration::Lifecycle.register_after_fork(:integration_name,
|
|
374
|
+
component: :component_name) { ... }`. The hook runs after core has refreshed its
|
|
375
|
+
own process-local state and after the active pipeline has forwarded
|
|
376
|
+
`after_fork!` to destinations.
|
|
377
|
+
|
|
378
|
+
`Julewire.observe_self!(runtime_name = :default, target: :meta)` starts a
|
|
379
|
+
`Julewire::Core::Diagnostics::MetaObserver`. The observer samples one runtime's
|
|
380
|
+
health and emits health-change records into another named runtime. Pass
|
|
381
|
+
`start: false` and call `sample!` manually when deterministic polling is
|
|
382
|
+
preferred.
|
|
383
|
+
|
|
384
|
+
Framework and provider adapters may also use the core integration SPI. The
|
|
385
|
+
`Julewire::Core::Integration` namespace is split by concern:
|
|
386
|
+
|
|
387
|
+
Health:
|
|
388
|
+
|
|
389
|
+
- `Integration::Health.record_failure` for contained process-level adapter
|
|
390
|
+
failures.
|
|
391
|
+
- `Integration::Health.record_success` for recovery after a successful
|
|
392
|
+
integration operation.
|
|
393
|
+
- `Integration::Health.scoped(:name)` for bound process-integration health
|
|
394
|
+
helpers. Pass `runtime:` only when a failure belongs to a known runtime rather
|
|
395
|
+
than a process-level framework edge.
|
|
396
|
+
|
|
397
|
+
Lifecycle:
|
|
398
|
+
|
|
399
|
+
- `Integration::Lifecycle.require_optional(path)` for contained optional
|
|
400
|
+
requires.
|
|
401
|
+
- `Integration::Lifecycle.register_after_fork(:integration_name,
|
|
402
|
+
component: :component_name) { ... }` for process-local integration state.
|
|
403
|
+
- `Integration::IvarState` for idempotent framework subscriber state.
|
|
404
|
+
- `Integration::Subscription` for subscriber installs that can update
|
|
405
|
+
configuration and best-effort unsubscribe on reset.
|
|
406
|
+
- `Integration::SubscriberInstall` for class-level subscriber `install!`
|
|
407
|
+
implementations that expose `subscriber`, `installed?`, and `reset!`.
|
|
408
|
+
- `Integration::EventSubscriber` for integration event subscribers that share
|
|
409
|
+
configuration assignment and contained integration-health `emit` handling.
|
|
410
|
+
- `Integration::Settings` for small integration configuration objects with
|
|
411
|
+
deep-copied defaults, assignment-time validation, and optional predicate
|
|
412
|
+
accessors.
|
|
413
|
+
|
|
414
|
+
`Integration::Settings.setting` validators may be instance method names or
|
|
415
|
+
procs; return a normalized value or raise.
|
|
416
|
+
|
|
417
|
+
Runtime access:
|
|
418
|
+
|
|
419
|
+
- `Integration::Facade.with_execution` for framework integrations that
|
|
420
|
+
build fresh execution attributes and want the same execution boundary as
|
|
421
|
+
`Julewire.with_execution` without copying already-owned attribute hashes.
|
|
422
|
+
- `Integration::Facade.emit` for framework/provider integrations that
|
|
423
|
+
emit already-normalized, adapter-owned record hashes. This is not the
|
|
424
|
+
app-facing `Julewire.emit` input path; integrations should pass explicit
|
|
425
|
+
record keys such as `:event`, `:source`, `:payload`, and `:attributes`.
|
|
426
|
+
|
|
427
|
+
Owned field overlays:
|
|
428
|
+
|
|
429
|
+
- `Integration::Facade.with_context`, `with_carry`, `with_attributes`, and
|
|
430
|
+
`with_neutral` for block-scoped, already-normalized, adapter-owned field
|
|
431
|
+
hashes around callback, request, or message processing.
|
|
432
|
+
- `Integration::Facade.add_context`, `add_carry`, `add_attributes`, and
|
|
433
|
+
`add_neutral` for already-normalized, adapter-owned field hashes added to the
|
|
434
|
+
current execution or ambient context.
|
|
435
|
+
`add_carry` and `add_neutral` are deliberate symmetry points for integrations
|
|
436
|
+
that need ambient propagation or formatter-coordination fields outside a
|
|
437
|
+
scoped callback.
|
|
438
|
+
- `Integration::Facade.add_summary_attributes` and `add_summary_neutral` for
|
|
439
|
+
enriching the current execution summary.
|
|
440
|
+
- `Integration::Facade.summary_active?` and `increment_summary_attribute` for
|
|
441
|
+
integrations that observe framework events inside an existing execution and
|
|
442
|
+
need to enrich summary counters without using the application facade.
|
|
443
|
+
|
|
444
|
+
Payload reads and shaping:
|
|
445
|
+
|
|
446
|
+
- `Integration::Values::Read.value`, `hash_value`, `nested_value`,
|
|
447
|
+
`path_value`, `first_value`, and `blank?` for defensive reads from hashes,
|
|
448
|
+
framework objects, and indexed payloads.
|
|
449
|
+
- `Integration::Values::Shape.timestamp`, `payload_hash`, `hash_or_empty`,
|
|
450
|
+
`append_field`, `append_compact_field`, and `source_location_attributes` for
|
|
451
|
+
common event-payload shaping.
|
|
452
|
+
- `Julewire::Core.sentinel(:name)` for private integration sentinels that
|
|
453
|
+
should print readably in failures and diffs.
|
|
454
|
+
- `Julewire::Core::Validation` for shared option and byte-limit validation.
|
|
455
|
+
|
|
456
|
+
`hash_value` is for strict hash reads and only bridges symbol/string key forms.
|
|
457
|
+
Use `value`, `nested_value`, or `path_value` for foreign objects that expose
|
|
458
|
+
methods or indexed access.
|
|
459
|
+
|
|
460
|
+
Bounded transforms:
|
|
461
|
+
|
|
462
|
+
- `Julewire::Core::Serialization::BoundedTransform` when a processor or adapter needs a bounded
|
|
463
|
+
walk with core-compatible depth, array, hash, string, cycle, and truncation
|
|
464
|
+
behavior.
|
|
465
|
+
It can insert `_julewire_truncation` metadata before the final encoder sees
|
|
466
|
+
the payload.
|
|
467
|
+
- `Julewire::Serializer.truncation_metadata` and serializer truncation
|
|
468
|
+
constants when an adapter must emit core-compatible truncation markers before
|
|
469
|
+
handing data back to core.
|
|
470
|
+
|
|
471
|
+
This SPI is documented support for integration gems, but not a compatibility
|
|
472
|
+
freeze. It may change when the ecosystem gets cleaner. `contracts.md` is the
|
|
473
|
+
source of truth for the current tier inventory.
|
|
474
|
+
|
|
475
|
+
## Extension Contract Tests
|
|
476
|
+
|
|
477
|
+
Extensions can require `julewire/core/testing` for small test primitives:
|
|
478
|
+
|
|
479
|
+
- `Julewire::Testing::CaptureDestination`
|
|
480
|
+
- `Julewire::Testing::NullOutput`
|
|
481
|
+
- `Julewire::Testing.configure_capture_destination`
|
|
482
|
+
- `Julewire::Testing::Chaos`
|
|
483
|
+
- `Julewire::Testing::Contracts`
|
|
484
|
+
- `Julewire::Testing::Coverage`
|
|
485
|
+
|
|
486
|
+
These helpers are shipped support for Julewire extension and integration gems,
|
|
487
|
+
not runtime application API.
|
|
488
|
+
|
|
489
|
+
`Julewire::Testing::Chaos.assert_contained(test_context) { |error| ... }`
|
|
490
|
+
runs a small `StandardError` corpus through containment checks. Use it for
|
|
491
|
+
extension paths that promise to absorb formatter, processor, destination, or
|
|
492
|
+
subscriber failures.
|
|
493
|
+
`Julewire::Testing::Chaos.assert_core_runtime_containment(test_context)` runs
|
|
494
|
+
the same corpus through core's curated runtime containment surfaces: processors,
|
|
495
|
+
formatters, encoders, outputs, callbacks, and lifecycle hooks.
|
|
496
|
+
`Julewire::Testing::Chaos.assert_destination_chaos_contract(...)` runs the
|
|
497
|
+
same corpus through a destination's formatter, encoder, output or transport,
|
|
498
|
+
and callback containment paths using destination builders supplied by the
|
|
499
|
+
extension test.
|
|
500
|
+
`Julewire::Testing::Chaos.assert_emitter_chaos_contract(...)` runs the same
|
|
501
|
+
corpus through a subscriber/listener-style entrypoint while the extension test
|
|
502
|
+
keeps ownership of framework-shaped failing inputs.
|
|
503
|
+
`Julewire::Testing::Chaos.catalog { ... }` builds a deterministic component
|
|
504
|
+
catalog, and `assert_discovered_chaos_contracts(...)` runs the corpus through
|
|
505
|
+
registered processor, formatter, encoder, destination, subscriber, and listener
|
|
506
|
+
entries. Use it when an extension can describe its containment surfaces without
|
|
507
|
+
reflecting over framework internals.
|
|
508
|
+
`Julewire::Testing::Chaos.raiser(error)` builds a callable that raises the
|
|
509
|
+
supplied error.
|
|
510
|
+
|
|
511
|
+
`Julewire::Testing::Contracts` contains shared extension assertions.
|
|
512
|
+
`contracts.md` owns the current helper inventory.
|
|
513
|
+
|
|
514
|
+
Contract helper tiers:
|
|
515
|
+
|
|
516
|
+
- Component contracts (`processor`, `formatter`, `destination`,
|
|
517
|
+
`record_draft`, record shape/source) are the documented extension test surface.
|
|
518
|
+
- Runtime, execution, propagation, integration, validation, truncation, bounded
|
|
519
|
+
transform, and scheduler contracts are integration SPI tests.
|
|
520
|
+
- Chaos helpers are shipped support for containment checks. They are intended
|
|
521
|
+
for extension/integration test suites, not app runtime code.
|
|
522
|
+
|
|
523
|
+
The runtime integration helper emits one point record inside an execution,
|
|
524
|
+
adds context, carry, and summary data, flushes, and asserts that destination
|
|
525
|
+
health is visible. Extensions provide their own output decoder and record paths,
|
|
526
|
+
because formatters may move Julewire fields into a different output shape.
|
|
527
|
+
|
|
528
|
+
`Julewire::Testing::Coverage` is shipped test support for Julewire
|
|
529
|
+
extension gems. It only requires SimpleCov when `Coverage.start!` runs with
|
|
530
|
+
`COVERAGE` set, so runtime users do not load coverage dependencies.
|
|
531
|
+
|
|
532
|
+
The execution-boundary helper gives integration and propagation extensions the
|
|
533
|
+
same probe data and lets the extension run it through its own unit of work. The
|
|
534
|
+
failure-containment helper verifies that extension failures do not escape
|
|
535
|
+
application calls and that health reports degradation.
|
|
536
|
+
|
|
537
|
+
## Internal or Advanced
|
|
538
|
+
|
|
539
|
+
These are not ordinary application APIs:
|
|
540
|
+
|
|
541
|
+
- runtime internals
|
|
542
|
+
- pipeline private helpers
|
|
543
|
+
- remote-envelope runtime hook
|
|
544
|
+
- test helpers
|
|
545
|
+
|
|
546
|
+
Direct `Julewire::Core::Processing::Pipeline` construction is advanced test and extension plumbing.
|
|
547
|
+
Application code should use the `Julewire` facade. A pipeline is built from a
|
|
548
|
+
frozen configuration copy; ordinary destination extension goes through
|
|
549
|
+
`config.destinations.use`.
|
|
550
|
+
|
|
551
|
+
`Processing::Pipeline#emit` is the raw-input path and may emit internal Julewire error
|
|
552
|
+
records when normalization or processing fails. `Processing::Pipeline#emit_record` is a
|
|
553
|
+
trusted extension path: callers must pass a normalized `Julewire::Record`.
|
|
554
|
+
It reports contained failures through callbacks and health without recursively
|
|
555
|
+
building another internal record.
|
|
556
|
+
|
|
557
|
+
`Julewire::Core::RuntimeLocator.current=` is an advanced runtime hook for bridge
|
|
558
|
+
code. A bridge runtime must support the child-side facade methods it exposes and
|
|
559
|
+
the parent-side bridge calls it forwards: `emit_envelope`,
|
|
560
|
+
`emit_summary_record`, and `flush`. It is deliberately duck-typed; incompatible
|
|
561
|
+
runtimes fail when called, so application code should not replace it casually.
|
|
562
|
+
|
|
563
|
+
## Remote Envelope Hook
|
|
564
|
+
|
|
565
|
+
Core exposes the bridge SPI runtime envelope hook used by bridge code. The hook
|
|
566
|
+
accepts detached input, context, attributes, carry, neutral data, and a scope
|
|
567
|
+
snapshot, then routes them through the active pipeline.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Health Schema
|
|
2
|
+
|
|
3
|
+
`Julewire.health` is an in-process operational snapshot. It is useful for
|
|
4
|
+
metrics, smoke checks, and debug pages. It is not a delivery receipt.
|
|
5
|
+
|
|
6
|
+
## Stable Fields
|
|
7
|
+
|
|
8
|
+
These fields are intended for integration dashboards and contract tests:
|
|
9
|
+
|
|
10
|
+
- `status`
|
|
11
|
+
- `closed`
|
|
12
|
+
- `generation`
|
|
13
|
+
- `counts`
|
|
14
|
+
- `last_failure`
|
|
15
|
+
- `last_callback_failure`
|
|
16
|
+
- `integrations.*.status`
|
|
17
|
+
- `integrations.*.counts`
|
|
18
|
+
- `integrations.*.last_failure`
|
|
19
|
+
- `process_integrations.*.status`
|
|
20
|
+
- `process_integrations.*.counts`
|
|
21
|
+
- `process_integrations.*.last_failure`
|
|
22
|
+
- `pipeline.configured`
|
|
23
|
+
- `pipeline.status`
|
|
24
|
+
- `pipeline.counts`
|
|
25
|
+
- `pipeline.last_callback_failure`
|
|
26
|
+
- `pipeline.last_failure`
|
|
27
|
+
- `destinations.*.status`
|
|
28
|
+
- `destinations.*.counts`
|
|
29
|
+
- `destinations.*.last_callback_failure`
|
|
30
|
+
- `destinations.*.last_failure`
|
|
31
|
+
- `destinations.*.last_loss`
|
|
32
|
+
|
|
33
|
+
Custom destination objects may expose their own nested health, but they own
|
|
34
|
+
those fields. Core direct destinations expose only core counters and loss
|
|
35
|
+
state.
|
|
36
|
+
|
|
37
|
+
`integrations.*` entries are runtime-local integration diagnostics. Use them
|
|
38
|
+
only when a failure can be honestly tied to a specific runtime.
|
|
39
|
+
|
|
40
|
+
`process_integrations.*` entries are process-owned integration diagnostics for
|
|
41
|
+
installs, framework subscribers, listeners, fork hooks, and other callbacks that
|
|
42
|
+
can fail before a record reaches a runtime. They expose only safe coordinates
|
|
43
|
+
such as integration name, component, action, phase, exception class, and
|
|
44
|
+
timestamp. A contained integration failure remains visible in `last_failure`,
|
|
45
|
+
but `status` can recover after the integration reports a later successful
|
|
46
|
+
operation.
|
|
47
|
+
|
|
48
|
+
Named runtimes have separate runtime-local `integrations`, pipeline, and
|
|
49
|
+
destination health, but they share `process_integrations`.
|
|
50
|
+
|
|
51
|
+
Top-level `counts` are process-runtime counters for the current runtime object.
|
|
52
|
+
`counts[:post_close_emits]` is scoped to the active runtime generation and resets
|
|
53
|
+
when a new pipeline is installed. `counts[:post_close_emits_total]` is the
|
|
54
|
+
runtime-lifetime total for the same rejected emits.
|
|
55
|
+
`counts[:invalid_record_severities]` is runtime-local; named runtimes do not
|
|
56
|
+
share that diagnostic count.
|
|
57
|
+
`pipeline.counts` and destination counters belong to the active pipeline and
|
|
58
|
+
reset on reconfigure. Top-level `generation` increments when configuration
|
|
59
|
+
installs a new pipeline.
|
|
60
|
+
`pipeline.counts[:processor_dropped]` counts intentional processor drops such
|
|
61
|
+
as sampling decisions; processor failures are counted separately under
|
|
62
|
+
`pipeline.counts[:processor_error]`.
|
|
63
|
+
Destination-local processors report the same `processor_dropped` and
|
|
64
|
+
`processor_error` counters under that destination's `counts`.
|
|
65
|
+
|
|
66
|
+
`status` fields describe current health for the active runtime generation.
|
|
67
|
+
Runtime, pipeline, and destination failure counters plus `last_failure` /
|
|
68
|
+
`last_loss` are historical. They remain visible after status recovers.
|
|
69
|
+
Destination loss or failure state recovers after a successful write or flush.
|
|
70
|
+
Integration state recovers after a successful integration operation. Pipeline
|
|
71
|
+
failure state recovers after a later emit completes without a pipeline failure.
|
|
72
|
+
Top-level `status` is `:ok`, `:degraded`, or `:closed`.
|
|
73
|
+
Pipeline `status` is `:unconfigured` before any destination is installed, then
|
|
74
|
+
`:ok` or `:degraded` for configured pipelines.
|
|
75
|
+
|
|
76
|
+
`destinations.*.last_loss` carries the last loss reason plus safe source,
|
|
77
|
+
event, severity, and timestamp metadata. It intentionally omits raw record data
|
|
78
|
+
and exception messages.
|
|
79
|
+
|
|
80
|
+
`destinations.*.last_failure` carries safe failure coordinates such as
|
|
81
|
+
exception class, phase, action, output class, and record coordinates when
|
|
82
|
+
available. It intentionally omits exception messages and raw output errors.
|
|
83
|
+
|
|
84
|
+
`Julewire.doctor` returns a scriptable summary built from `Julewire.health`.
|
|
85
|
+
It includes runtime, pipeline, destination, and integration status plus
|
|
86
|
+
warnings for unconfigured or degraded components.
|
|
87
|
+
|
|
88
|
+
## Diagnostic Fields
|
|
89
|
+
|
|
90
|
+
These are useful while debugging, but they are more implementation-shaped:
|
|
91
|
+
|
|
92
|
+
- output class names
|
|
93
|
+
- last callback failure phase
|
|
94
|
+
- invalid severity details
|
|
95
|
+
- runtime, pipeline, and destination failure details
|
|
96
|
+
- callback failure counts
|
|
97
|
+
- detailed loss taxonomies
|
|
98
|
+
|
|
99
|
+
Diagnostic fields may move or grow as internals change. Avoid long-lived alerts
|
|
100
|
+
that depend on exact nested delivery details unless the integration owns the
|
|
101
|
+
mapping.
|
|
102
|
+
|
|
103
|
+
Health intentionally omits raw exception messages. Do not expose private error
|
|
104
|
+
objects or raw sink messages from app health endpoints.
|