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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e20df02ec73ceafd282ab52015e46a28f9aa7b1d69461e2622afc301f9e98d40
|
|
4
|
+
data.tar.gz: 4e12bb12f787534aa5318084f6fa69fc1888590d85874d253c12dbfc6d6701f7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 222e5041e3d984b1e3d50a989c461522cf7622257d1698e7e9fdee9f10f2837537a883fbfe16466ac31abf868558807e1a07642e52c894ac020bf71c628a1896
|
|
7
|
+
data.tar.gz: 75f19494c48c6d46ae6bdf997db3605abc93323180589804d0e6338e1c7e592cd58150fbef9be4ccb5bfba577fb1086745ee8b779b6588aa7996572a55a5563f
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Grebennik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Julewire Core
|
|
2
|
+
|
|
3
|
+
`julewire-core` is the synchronous structured logging runtime behind Julewire.
|
|
4
|
+
|
|
5
|
+
It builds provider-neutral records, execution context, propagated carry,
|
|
6
|
+
non-propagated attributes, final summaries, processor chains, and synchronous
|
|
7
|
+
direct outputs. It does not know about app frameworks, privacy policy, provider
|
|
8
|
+
schemas, async queues, or delivery.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "julewire-core"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
Julewire.configure do |config|
|
|
20
|
+
config.destinations.use(:default, output: $stdout)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Julewire.with_execution(type: :job, id: "job-1") do
|
|
24
|
+
Julewire.context.add(tenant_id: "tenant-1")
|
|
25
|
+
Julewire.summary.increment(:records_seen)
|
|
26
|
+
|
|
27
|
+
Julewire.measure(:process_record) do
|
|
28
|
+
Julewire.emit("processed record", id: 123)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
By default, direct output is newline-delimited JSON.
|
|
34
|
+
|
|
35
|
+
## Field Bags
|
|
36
|
+
|
|
37
|
+
| Bag | Use it for | Propagates? | Default JSON? |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| `context` | queryable facts copied onto records in the current scope | yes | yes |
|
|
40
|
+
| `carry` | hidden forwarding metadata for integrations and formatters | yes | no |
|
|
41
|
+
| `neutral` | provider-neutral formatter-coordination facts | no | no |
|
|
42
|
+
| `attributes` | integration and application namespaces | no | yes |
|
|
43
|
+
| `summary` | final-only counters, timings, and completion facts | no | summary only |
|
|
44
|
+
| `labels` | operator-safe routing/grouping metadata | no | yes |
|
|
45
|
+
| `metrics` | numeric measurements such as duration | no | yes |
|
|
46
|
+
|
|
47
|
+
## Docs
|
|
48
|
+
|
|
49
|
+
- [Quickstart](docs/quickstart.md)
|
|
50
|
+
- [Configuration](docs/configuration.md)
|
|
51
|
+
- [Advanced Configuration](docs/advanced-configuration.md)
|
|
52
|
+
- [Context and Propagation](docs/context-and-propagation.md)
|
|
53
|
+
- [Instrumentation Cheatsheet](docs/instrumentation-cheatsheet.md)
|
|
54
|
+
- [Attribute Keys](docs/attribute-keys.md)
|
|
55
|
+
- [Records and Data Policy](docs/records-and-data-policy.md)
|
|
56
|
+
- [Outputs and Lifecycle](docs/outputs-and-lifecycle.md)
|
|
57
|
+
- [Developer Tail](docs/tail.md)
|
|
58
|
+
- [Health Schema](docs/health-schema.md)
|
|
59
|
+
- [Security and Wire Keys](docs/security-and-wire.md)
|
|
60
|
+
- [Extension Contracts](docs/contracts.md)
|
|
61
|
+
- [Extensions and API](docs/extensions-and-api.md)
|
|
62
|
+
- [Record Sources](docs/record-sources.md)
|
|
63
|
+
- [Internals](docs/internals.md)
|
|
64
|
+
- [Development](docs/development.md)
|
|
65
|
+
|
|
66
|
+
## Runtime Promise
|
|
67
|
+
|
|
68
|
+
Julewire is best-effort logging infrastructure. `StandardError` failures inside
|
|
69
|
+
Julewire's own normalization, processing, formatting, encoding, and output path
|
|
70
|
+
are contained so application code keeps running.
|
|
71
|
+
|
|
72
|
+
Core stays synchronous. Custom destinations own async queues, files, fanout,
|
|
73
|
+
batching, retries, and delivery health.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Advanced Configuration
|
|
2
|
+
|
|
3
|
+
## Runtime Configuration
|
|
4
|
+
|
|
5
|
+
`configure` yields a mutable `Julewire::Core::Configuration` builder, then
|
|
6
|
+
stores a frozen copy with frozen registry containers. Core builds and installs
|
|
7
|
+
the staged pipeline against that frozen copy before swapping runtime state.
|
|
8
|
+
|
|
9
|
+
The active configuration containers exposed by `Julewire.config` and
|
|
10
|
+
`Julewire.labels` are read-only. Mutating runtime configuration after the fact
|
|
11
|
+
is unsupported. Call `Julewire.configure` again.
|
|
12
|
+
|
|
13
|
+
User-supplied destination formatter, encoder, output, and processor instances
|
|
14
|
+
are shared by reference. Processors, formatters, and encoders must be stateless
|
|
15
|
+
or otherwise reentrant; core does not synchronize them. Direct outputs are
|
|
16
|
+
wrapped with a per-destination mutex.
|
|
17
|
+
|
|
18
|
+
When a reconfigure reuses the same output or destination object, core keeps that
|
|
19
|
+
resource open for the new active pipeline and skips teardown through the old
|
|
20
|
+
pipeline. Replacing an output object still flushes or closes the previous one
|
|
21
|
+
according to its `close_output` setting.
|
|
22
|
+
|
|
23
|
+
`configure` is non-reentrant. Do not call runtime APIs such as `emit`,
|
|
24
|
+
`configure`, or `reset!` from inside a `configure` block.
|
|
25
|
+
|
|
26
|
+
## Failure and Drop Callbacks
|
|
27
|
+
|
|
28
|
+
`on_failure` receives contained `StandardError` instances from Julewire's own
|
|
29
|
+
logging path, plus one metadata hash. Metadata may include `:phase`,
|
|
30
|
+
`:record_metadata`, `:action`, and output class when known.
|
|
31
|
+
|
|
32
|
+
`on_drop` receives a symbolic reason for operational drops after processing has
|
|
33
|
+
started, such as oversized encoded records, formatter errors, encoder errors,
|
|
34
|
+
output failures, output rejections, custom destination failures/rejections, and
|
|
35
|
+
post-close `:runtime_closed` drops.
|
|
36
|
+
|
|
37
|
+
Level filtering and intentional no-output mode are counted in health but do not
|
|
38
|
+
call `on_drop`; those are configuration or policy outcomes, not output-loss
|
|
39
|
+
callbacks. Drop callbacks receive the same metadata-hash shape.
|
|
40
|
+
|
|
41
|
+
Callbacks are best effort. Exceptions raised by callbacks are swallowed and
|
|
42
|
+
counted where health can report them. Core health keeps those counters small;
|
|
43
|
+
callbacks that need richer diagnostics should record them locally.
|
|
44
|
+
The recursion guard uses fiber storage, so work handed to a child fiber from
|
|
45
|
+
inside a callback stays inside the same callback-suppression scope.
|
|
46
|
+
|
|
47
|
+
Callbacks are called with two positional arguments:
|
|
48
|
+
`on_failure.call(error, metadata)` and `on_drop.call(reason, metadata)`.
|
|
49
|
+
Core uses Ruby duck typing at configuration time; it only requires callback
|
|
50
|
+
objects to respond to `call`. Arity or keyword mistakes are contained when the
|
|
51
|
+
callback is invoked and are counted as callback failures in health.
|
|
52
|
+
|
|
53
|
+
Drop counters count every dropped record. Use health counters for alerting;
|
|
54
|
+
callback frequency is intentionally not a metric. Applications that need
|
|
55
|
+
callback throttling should implement it in the callback.
|
|
56
|
+
|
|
57
|
+
## Non-Standard Exception Summaries
|
|
58
|
+
|
|
59
|
+
`emit_non_standard_exception_summaries` defaults to `false`.
|
|
60
|
+
|
|
61
|
+
When false, core suppresses execution summaries while unwinding exceptions
|
|
62
|
+
outside `StandardError`, such as `SystemExit` and signal exceptions. When true,
|
|
63
|
+
core tries to emit those summaries too.
|
|
64
|
+
|
|
65
|
+
The application exception still wins; execution boundaries re-raise application
|
|
66
|
+
exits after recording what they can.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Attribute Keys
|
|
2
|
+
|
|
3
|
+
Attributes are record-local fields for integrations, processors, and
|
|
4
|
+
formatters. They do not propagate. Integration-specific detail belongs under a
|
|
5
|
+
namespace such as `attributes.web`, `attributes.job`, or
|
|
6
|
+
`attributes.messaging`; those namespaces are emitted by default.
|
|
7
|
+
|
|
8
|
+
Neutral keys are formatter coordination fields stored in the record's `neutral`
|
|
9
|
+
section. Formatters can promote them to provider-native output, but the default
|
|
10
|
+
formatter strips the section from emitted JSON so the same fact is not logged
|
|
11
|
+
twice. User attributes such as `my_app.some_key` remain normal emitted
|
|
12
|
+
attributes.
|
|
13
|
+
|
|
14
|
+
Neutral keys track current OpenTelemetry semantic-convention names where those
|
|
15
|
+
names fit Julewire records. Julewire intentionally follows current/edge names
|
|
16
|
+
instead of pinning this page to one semconv release. Julewire still owns this
|
|
17
|
+
contract: upstream semconv changes make a key worth rechecking, not
|
|
18
|
+
automatically emitted.
|
|
19
|
+
|
|
20
|
+
## HTTP
|
|
21
|
+
|
|
22
|
+
| Key | Meaning |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `http.request.method` | HTTP request method. |
|
|
25
|
+
| `url.full` | Full filtered request URL when available. |
|
|
26
|
+
| `url.path` | Request path. |
|
|
27
|
+
| `http.response.status_code` | HTTP response status. |
|
|
28
|
+
| `user_agent.original` | Raw user-agent value. |
|
|
29
|
+
| `client.address` | Client address or remote IP. |
|
|
30
|
+
| `http.response.body.size` | Response body size in bytes. |
|
|
31
|
+
|
|
32
|
+
## Code
|
|
33
|
+
|
|
34
|
+
| Key | Meaning |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `code.file.path` | Source file path. |
|
|
37
|
+
| `code.line.number` | Source line number. |
|
|
38
|
+
| `code.function.name` | Source function, method, or label. |
|
|
39
|
+
|
|
40
|
+
## Jobs
|
|
41
|
+
|
|
42
|
+
Current OpenTelemetry semconv does not provide a general job namespace that fits
|
|
43
|
+
ActiveJob-style execution summaries, so these are Julewire neutral job keys.
|
|
44
|
+
|
|
45
|
+
| Key | Meaning |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `job.system` | Job framework or runtime, such as `active_job`. |
|
|
48
|
+
| `job.name` | Job class or logical job name. |
|
|
49
|
+
| `job.id` | Framework job id. |
|
|
50
|
+
| `job.provider_id` | Backend provider job id. |
|
|
51
|
+
| `job.queue.name` | Queue name. |
|
|
52
|
+
| `job.priority` | Queue priority. |
|
|
53
|
+
| `job.execution_count` | Framework execution count or attempt count. |
|
|
54
|
+
| `job.enqueued_at` | Enqueue timestamp. |
|
|
55
|
+
| `job.scheduled_at` | Scheduled timestamp. |
|
|
56
|
+
| `job.status` | Summary status such as `ok` or `error`. |
|
|
57
|
+
|
|
58
|
+
## Messaging
|
|
59
|
+
|
|
60
|
+
| Key | Meaning |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `messaging.system` | Messaging system, such as `kafka`. |
|
|
63
|
+
| `messaging.operation.name` | Operation name from the integration event. |
|
|
64
|
+
| `messaging.operation.type` | Generic type such as `process`, `receive`, or `send`. |
|
|
65
|
+
| `messaging.destination.name` | Topic, stream, or queue name. |
|
|
66
|
+
| `messaging.destination.partition.id` | Partition id. |
|
|
67
|
+
| `messaging.batch.message_count` | Message count in a batch. |
|
|
68
|
+
| `messaging.consumer.group.name` | Consumer group name. |
|
|
69
|
+
| `messaging.kafka.offset` | Kafka offset. |
|
|
70
|
+
| `messaging.kafka.message.key` | Kafka message key. |
|
|
71
|
+
|
|
72
|
+
Formatters should read neutral keys from `record.neutral` when producing
|
|
73
|
+
provider-native fields. They should not inspect integration-specific namespaces
|
|
74
|
+
unless they explicitly document that integration coupling.
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`Julewire.configure` is the only mutation path for runtime configuration:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Julewire.configure do |config|
|
|
7
|
+
config.level = :info
|
|
8
|
+
config.destinations.use(
|
|
9
|
+
:default,
|
|
10
|
+
formatter: Julewire::RecordFormatter.new,
|
|
11
|
+
output: $stdout,
|
|
12
|
+
close_output: false
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
config.labels.add(service: "billing", env: "production")
|
|
16
|
+
|
|
17
|
+
config.on_failure = ->(error, metadata) {
|
|
18
|
+
warn("julewire failure phase=#{metadata[:phase]} error=#{error.class}")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
config.on_drop = ->(reason, metadata) {
|
|
22
|
+
warn("julewire dropped reason=#{reason} phase=#{metadata[:phase]}")
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Core Settings
|
|
28
|
+
|
|
29
|
+
| Setting | Meaning |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `level` | Global minimum severity. Defaults to `:debug`. |
|
|
32
|
+
| `pipeline_close_timeout` | Default timeout for pipeline close. Defaults to `1`. |
|
|
33
|
+
| `emit_non_standard_exception_summaries` | Emit summaries while unwinding `SystemExit`/signals. Defaults to `false`. |
|
|
34
|
+
| `error_backtrace_lines` | Exception backtrace line cap. Defaults to `20`; set `0` to omit backtraces. |
|
|
35
|
+
| `labels` | Static labels merged into every record. |
|
|
36
|
+
| `processors` | Chain that mutates/enriches records before formatting. |
|
|
37
|
+
| `destinations` | Named formatter/encoder/output graph or custom destinations. |
|
|
38
|
+
| `on_failure` | Best-effort callback for contained core failures. |
|
|
39
|
+
| `on_drop` | Best-effort callback for operational drops after a record reaches destination/output handling. |
|
|
40
|
+
|
|
41
|
+
Timeout values must be `nil` or non-negative finite numerics.
|
|
42
|
+
`error_backtrace_lines` must be a non-negative integer.
|
|
43
|
+
|
|
44
|
+
`error_backtrace_lines` applies at core's error-shaping boundaries. When a
|
|
45
|
+
record enters core with an `Exception` under `error`, core builds the error hash
|
|
46
|
+
with that limit before the record is frozen. When a formatter/serializer later
|
|
47
|
+
sees an `Exception`, it applies the same limit while shaping it. Integrations
|
|
48
|
+
should pass real `Exception` objects or core-shaped error hashes; core also
|
|
49
|
+
trims `backtrace` fields on those error hashes at record ingress so a forgotten
|
|
50
|
+
adapter limit cannot leak a full stack by accident.
|
|
51
|
+
|
|
52
|
+
Async queues, files, fanout, batching, retries, and delivery policy belong in
|
|
53
|
+
custom destinations. Core owns sync destination writes only.
|
|
54
|
+
|
|
55
|
+
## Levels
|
|
56
|
+
|
|
57
|
+
Supported severities:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
debug info warn error fatal unknown
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Core accepts symbols, strings, and stdlib `Logger::Severity` integers. String
|
|
64
|
+
and symbol names are case-insensitive.
|
|
65
|
+
|
|
66
|
+
Records with eager severity below `config.level` are dropped before context
|
|
67
|
+
lookup, processors, formatting, and output writes. For lazy `emit` blocks with
|
|
68
|
+
no eager severity, core evaluates the block first, then applies the level check
|
|
69
|
+
to the final record.
|
|
70
|
+
|
|
71
|
+
When `Julewire.emit` receives both a positional hash and keyword fields, keyword
|
|
72
|
+
fields win for duplicate top-level keys before record normalization. In lazy
|
|
73
|
+
emits, the block result wins over eager non-severity fields; an eager severity
|
|
74
|
+
from a severity helper such as `Julewire.warn` remains authoritative.
|
|
75
|
+
|
|
76
|
+
## Static Labels
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Julewire.configure do |config|
|
|
80
|
+
config.labels.add(service: "billing")
|
|
81
|
+
config.labels.remove(:debug_label)
|
|
82
|
+
config.labels.clear
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Labels follow the core field contract: public helpers accept string or symbol
|
|
87
|
+
keys and normalize strings to symbols at the boundary. Per-record labels
|
|
88
|
+
override configured static labels with the same symbol key.
|
|
89
|
+
|
|
90
|
+
Labels are meant for non-sensitive, low-cardinality dimensions such as service,
|
|
91
|
+
environment, shard, region, or runtime. Core may copy labels into internal
|
|
92
|
+
diagnostic records because labels are treated as operator-safe routing/grouping
|
|
93
|
+
metadata. Do not put PII, secrets, or high-cardinality payload data in labels.
|
|
94
|
+
|
|
95
|
+
## Processors
|
|
96
|
+
|
|
97
|
+
Processors run before destinations:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
Julewire.configure do |config|
|
|
101
|
+
config.processors.use MyProcessor
|
|
102
|
+
config.processors.use MyProcessorWithArgs, "constructor-arg", enabled: true
|
|
103
|
+
config.processors.use EnrichmentProcessor, on_error: :fail_open
|
|
104
|
+
config.processors.prepend FirstProcessor
|
|
105
|
+
config.processors.use Julewire::Match.new { on(severity: :debug) { :drop } }
|
|
106
|
+
config.processors.use :sampling, rate: 0.1
|
|
107
|
+
config.processors.use do |draft|
|
|
108
|
+
draft[:attributes][:app] = { source: "app" }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Core ships no default policy processors. It does include small processor
|
|
114
|
+
helpers such as `Julewire::Match` and deterministic head sampling through the
|
|
115
|
+
registered `:sampling` processor. Redaction and enrichment belong in
|
|
116
|
+
processors. Output-specific shape belongs in destination formatters. Processors
|
|
117
|
+
are the mutation stage.
|
|
118
|
+
|
|
119
|
+
Processors receive the current `Julewire::RecordDraft`. Mutate the draft
|
|
120
|
+
directly. Return `:drop` to stop delivery or a different `Julewire::RecordDraft` to
|
|
121
|
+
replace the current draft; any other return value is ignored. Processors own
|
|
122
|
+
the draft until the final record boundary, so direct mutation is the normal hot
|
|
123
|
+
path:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
class AddTenantLabel
|
|
127
|
+
def call(draft)
|
|
128
|
+
tenant = draft.fetch(:context).fetch(:tenant_id, nil)
|
|
129
|
+
return unless tenant
|
|
130
|
+
|
|
131
|
+
draft[:labels][:tenant] = tenant
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Julewire.configure do |config|
|
|
136
|
+
config.processors.prepend AddTenantLabel.new
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `:sampling` processor hashes a deterministic key and returns `:drop` for
|
|
141
|
+
unsampled records. By default it uses execution/root identifiers when present,
|
|
142
|
+
then `context.request_id`, then deterministic record fields. Pass `key:` to choose an
|
|
143
|
+
application-specific key:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
config.processors.use(
|
|
147
|
+
:sampling,
|
|
148
|
+
rate: 0.05,
|
|
149
|
+
key: ->(draft) { draft.dig(:context, :tenant_id) }
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Processor exceptions use the registration policy:
|
|
154
|
+
|
|
155
|
+
| `on_error` | Behavior |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `:fail_closed` | Default. Emit a `julewire.processor_error` replacement record and stop the chain. |
|
|
158
|
+
| `:fail_open` | Record the failure, keep the current draft, and continue with later processors. |
|
|
159
|
+
| `:drop` | Record the failure and suppress the record. |
|
|
160
|
+
|
|
161
|
+
Public emit input remains defensive. The final immutable record boundary
|
|
162
|
+
validates the Julewire record shape before destinations see it.
|
|
163
|
+
|
|
164
|
+
Destination-local processors run after the global processor chain and before
|
|
165
|
+
one destination formats the record. Use them for sink-specific policy such as
|
|
166
|
+
audit-only enrichment or destination-only sampling:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
Julewire.configure do |config|
|
|
170
|
+
config.destinations.use(:stdout, output: $stdout)
|
|
171
|
+
config.destinations.use(
|
|
172
|
+
:audit,
|
|
173
|
+
output: audit_io,
|
|
174
|
+
processors: [
|
|
175
|
+
->(draft) { draft[:labels][:sink] = "audit" }
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
A destination-local drop or processor failure affects only that destination.
|
|
182
|
+
|
|
183
|
+
## Destinations
|
|
184
|
+
|
|
185
|
+
Direct destinations pair a formatter, encoder, and output:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
Julewire.configure do |config|
|
|
189
|
+
config.destinations.use(
|
|
190
|
+
:default,
|
|
191
|
+
formatter: Julewire::RecordFormatter.new,
|
|
192
|
+
encoder: Julewire::JsonEncoder.new,
|
|
193
|
+
output: $stdout
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Integration gems may register destination kinds with adapter-specific options:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
Julewire.configure do |config|
|
|
202
|
+
config.destinations.use(:provider_json, output: $stdout)
|
|
203
|
+
config.destinations.use(:transport, formatter: formatter, io: $stdout)
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
For local human-readable output, pair the console formatter with the text
|
|
208
|
+
encoder:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
Julewire.configure do |config|
|
|
212
|
+
config.destinations.use(
|
|
213
|
+
:console,
|
|
214
|
+
formatter: Julewire::ConsoleFormatter.new,
|
|
215
|
+
encoder: Julewire::TextEncoder.new(color: $stdout.tty?),
|
|
216
|
+
output: $stdout
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For a loud local console, `Julewire.punk!` replaces the named runtime's
|
|
222
|
+
destinations with the console formatter and the punk text theme:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
Julewire.punk!(color: $stdout.tty?)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
For local containment drills, add `chaos: true`. It wraps the output with a
|
|
229
|
+
small chaos sink that occasionally raises, rejects, or stalls writes so the
|
|
230
|
+
runtime health path visibly degrades while application calls stay contained.
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
Julewire.punk!(color: $stdout.tty?, chaos: true)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`Julewire.dev!` is the same core-local dev shape with a bounded in-memory tail
|
|
237
|
+
attached:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
tail = Julewire.dev!(chaos: true, tail: { capacity: 500 })
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Additional destinations are explicit:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
Julewire.configure do |config|
|
|
247
|
+
config.destinations.use(
|
|
248
|
+
:json_stdout,
|
|
249
|
+
formatter: Julewire::RecordFormatter.new,
|
|
250
|
+
encoder: Julewire::JsonEncoder.new,
|
|
251
|
+
output: $stdout
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
config.destinations.use(
|
|
255
|
+
:debug_file,
|
|
256
|
+
formatter: Julewire::RecordFormatter.new,
|
|
257
|
+
encoder: Julewire::JsonEncoder.new,
|
|
258
|
+
output: File.open("log/debug.json", "a"),
|
|
259
|
+
close_output: true
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Destination options:
|
|
265
|
+
|
|
266
|
+
- `formatter`
|
|
267
|
+
- `encoder`
|
|
268
|
+
- `output`
|
|
269
|
+
- `close_output`
|
|
270
|
+
- `max_record_bytes` (defaults to `1 MiB`)
|
|
271
|
+
- `on_failure`
|
|
272
|
+
- `on_drop`
|
|
273
|
+
- `processors`
|
|
274
|
+
|
|
275
|
+
Extensions can also install an already-built destination object:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
config.destinations.add(MyDestination.new(name: :custom))
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Custom destination objects must respond to `name`, `emit(record)`, `flush`,
|
|
282
|
+
`close`, and `health`.
|
|
283
|
+
|
|
284
|
+
`Julewire.configure` starts from the active configuration. To replace the output
|
|
285
|
+
graph in a later configure call, clear destinations first:
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
Julewire.configure do |config|
|
|
289
|
+
config.destinations.clear
|
|
290
|
+
config.destinations.use(:default, output: $stdout)
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Split Pipelines
|
|
295
|
+
|
|
296
|
+
The top-level `Julewire` facade uses the default runtime. Use named runtimes
|
|
297
|
+
when one process needs separate pipeline policy, such as redacted stdout and a
|
|
298
|
+
locked-down audit sink:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
Julewire.runtime(:audit).configure do |config|
|
|
302
|
+
config.destinations.use(:audit, output: audit_io)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
Julewire.runtime(:audit).emit(message: "audit-only")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Named runtimes have their own configuration, processors, destinations, health,
|
|
309
|
+
and close/flush lifecycle. `Julewire.after_fork!` resets every known runtime.
|
|
310
|
+
|
|
311
|
+
Destinations receive the immutable processed `Julewire::Record`. Hashes,
|
|
312
|
+
arrays, and copied strings inside the record are frozen; arbitrary app objects
|
|
313
|
+
inside fields are not deep-frozen. Formatters must treat the record as read-only
|
|
314
|
+
and return a payload object. Encoders turn formatter payloads into strings
|
|
315
|
+
before writing to direct outputs. The default encoder is
|
|
316
|
+
`Julewire::JsonEncoder`, which applies Julewire serialization before
|
|
317
|
+
`JSON.generate`. A formatter that mutates frozen containers raises and is
|
|
318
|
+
handled as a formatter failure.
|
|
319
|
+
|
|
320
|
+
Destination callbacks inherit from the global callbacks unless overridden.
|
|
321
|
+
|
|
322
|
+
For reconfigure semantics, callback details, and other low-level knobs, see
|
|
323
|
+
[Advanced Configuration](advanced-configuration.md).
|
|
324
|
+
|
|
325
|
+
Calling `Julewire.emit` from callbacks may still run the nested emit, but nested
|
|
326
|
+
callback delivery is suppressed as callback recursion. Components that track
|
|
327
|
+
callback failures count the suppression.
|