julewire-semantic_logger 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 +46 -0
- data/docs/advanced-configuration.md +54 -0
- data/docs/configuration.md +87 -0
- data/docs/health.md +43 -0
- data/docs/transport.md +108 -0
- data/julewire-semantic_logger.gemspec +39 -0
- data/lib/julewire/semantic_logger/appender_health.rb +68 -0
- data/lib/julewire/semantic_logger/destination.rb +135 -0
- data/lib/julewire/semantic_logger/exact_formatter.rb +25 -0
- data/lib/julewire/semantic_logger/lifecycle_warnings.rb +21 -0
- data/lib/julewire/semantic_logger/transport.rb +190 -0
- data/lib/julewire/semantic_logger/version.rb +7 -0
- data/lib/julewire/semantic_logger.rb +18 -0
- data/lib/julewire-semantic_logger.rb +3 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e701488ff85db0998210de87726f8fe7aeb5d8aacefcf6721b86e8d60b5df01b
|
|
4
|
+
data.tar.gz: e461c66f174f9d83105cbac6e6fa0a457e5387c8ea293ae91bf5599b2d7305d8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ada163b0d2af3819d88a210193063d7188151a1f298508816bc82ecd4e131790450e7c646c91414db22711fee7c434f5ed2d7d9d5649e2e0dc502b090c6e3d3a
|
|
7
|
+
data.tar.gz: 98d7f0cb14e358b81164a5f131d7d7e99602554668fa302d7c3e9466747b6f674b2ece1bbae01fb24e0bdc637bd81987f6b9b6b60217ad509f4804503452e885
|
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,46 @@
|
|
|
1
|
+
# Julewire Semantic Logger
|
|
2
|
+
|
|
3
|
+
`julewire-semantic_logger` is a Semantic Logger transport destination for
|
|
4
|
+
Julewire.
|
|
5
|
+
|
|
6
|
+
Julewire keeps its own record shape. Semantic Logger owns appender plumbing,
|
|
7
|
+
file/stdout output, optional async queues, flush, close, and reopen.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "julewire-semantic_logger"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
Julewire.configure do |config|
|
|
19
|
+
config.destinations.use(
|
|
20
|
+
:semantic_logger,
|
|
21
|
+
formatter: Julewire::RecordFormatter.new,
|
|
22
|
+
io: $stdout
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The adapter is synchronous by default. Enable async explicitly:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
Julewire.configure do |config|
|
|
31
|
+
config.destinations.use(
|
|
32
|
+
:semantic_logger,
|
|
33
|
+
formatter: Julewire::RecordFormatter.new,
|
|
34
|
+
io: $stdout,
|
|
35
|
+
async: true,
|
|
36
|
+
max_queue_size: 10_000
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Docs
|
|
42
|
+
|
|
43
|
+
- [Configuration](docs/configuration.md)
|
|
44
|
+
- [Advanced Configuration](docs/advanced-configuration.md)
|
|
45
|
+
- [Transport](docs/transport.md)
|
|
46
|
+
- [Health](docs/health.md)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Advanced Configuration
|
|
2
|
+
|
|
3
|
+
## Prebuilt Transport
|
|
4
|
+
|
|
5
|
+
Pass `transport:` when construction needs to happen outside the destination:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
transport = Julewire::SemanticLogger::Transport.new(io: $stdout)
|
|
9
|
+
|
|
10
|
+
config.destinations.use(
|
|
11
|
+
:semantic_logger,
|
|
12
|
+
formatter: Julewire::RecordFormatter.new,
|
|
13
|
+
transport: transport
|
|
14
|
+
)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Prebuilt Appenders
|
|
18
|
+
|
|
19
|
+
Pass an existing Semantic Logger appender with `appender:`:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
config.destinations.use(
|
|
23
|
+
:semantic_logger,
|
|
24
|
+
formatter: Julewire::RecordFormatter.new,
|
|
25
|
+
appender: my_appender
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Async Lag Options
|
|
30
|
+
|
|
31
|
+
These options are passed to `SemanticLogger::Appender::Async` when
|
|
32
|
+
`async: true`:
|
|
33
|
+
|
|
34
|
+
| Option | Default |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `lag_check_interval:` | `1_000` |
|
|
37
|
+
| `lag_threshold_s:` | `30` |
|
|
38
|
+
|
|
39
|
+
## Appender Defaults
|
|
40
|
+
|
|
41
|
+
Unknown transport options are merged into each appender spec. This is useful for
|
|
42
|
+
Semantic Logger appender-specific options:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
config.destinations.use(
|
|
46
|
+
:semantic_logger,
|
|
47
|
+
formatter: Julewire::RecordFormatter.new,
|
|
48
|
+
appenders: [
|
|
49
|
+
{ io: $stdout },
|
|
50
|
+
{ file_name: "log/julewire.log" }
|
|
51
|
+
],
|
|
52
|
+
level: :debug
|
|
53
|
+
)
|
|
54
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
`julewire-semantic_logger` registers the `:semantic_logger` destination kind.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Julewire.configure do |config|
|
|
7
|
+
config.destinations.use(
|
|
8
|
+
:semantic_logger,
|
|
9
|
+
formatter: Julewire::RecordFormatter.new,
|
|
10
|
+
io: $stdout
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Destination Options
|
|
16
|
+
|
|
17
|
+
| Option | Meaning |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `name:` | Destination name. Required. |
|
|
20
|
+
| `formatter:` | Julewire formatter object. Required. |
|
|
21
|
+
| `encoder:` | Julewire encoder object. Defaults to core JSON without a trailing newline. |
|
|
22
|
+
| `transport:` | Prebuilt `Julewire::SemanticLogger::Transport`. |
|
|
23
|
+
| `**transport_options` | Passed to `Transport.new` when `transport:` is omitted. |
|
|
24
|
+
|
|
25
|
+
The destination passes the immutable Julewire record to `formatter`, then hands
|
|
26
|
+
the formatter result through `encoder`. The transport receives the encoded
|
|
27
|
+
string. String formatter results are treated as already encoded and lose one
|
|
28
|
+
trailing newline when present.
|
|
29
|
+
|
|
30
|
+
## Transport Options
|
|
31
|
+
|
|
32
|
+
At least one appender target is required.
|
|
33
|
+
|
|
34
|
+
| Option | Default | Meaning |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `io:` | none | IO appender target such as `$stdout`. |
|
|
37
|
+
| `file_name:` | none | File appender target. |
|
|
38
|
+
| `appender:` | none | Existing Semantic Logger appender. |
|
|
39
|
+
| `appenders:` | none | Array of appender specs. |
|
|
40
|
+
| `async:` | `false` | Wrap the sink in `SemanticLogger::Appender::Async`. |
|
|
41
|
+
| `max_queue_size:` | `10_000` | Async queue size. `-1` means unbounded in Semantic Logger. |
|
|
42
|
+
|
|
43
|
+
Unknown transport options are passed to Semantic Logger appender construction.
|
|
44
|
+
|
|
45
|
+
## Appender Specs
|
|
46
|
+
|
|
47
|
+
Single stdout appender:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
config.destinations.use(
|
|
51
|
+
:semantic_logger,
|
|
52
|
+
formatter: Julewire::RecordFormatter.new,
|
|
53
|
+
io: $stdout
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Multiple appenders:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
config.destinations.use(
|
|
61
|
+
:semantic_logger,
|
|
62
|
+
formatter: Julewire::RecordFormatter.new,
|
|
63
|
+
appenders: [
|
|
64
|
+
{ io: $stdout },
|
|
65
|
+
{ file_name: "log/julewire.log" }
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Async output:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
config.destinations.use(
|
|
74
|
+
:semantic_logger,
|
|
75
|
+
formatter: Julewire::RecordFormatter.new,
|
|
76
|
+
io: $stdout,
|
|
77
|
+
async: true,
|
|
78
|
+
max_queue_size: 10_000
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Async moves blocking and drop behavior into Semantic Logger's queue. Keep
|
|
83
|
+
`max_queue_size` explicit and call `Julewire.flush` before shutdown when queued
|
|
84
|
+
records matter.
|
|
85
|
+
|
|
86
|
+
For multi-appender output, async lag options, and prebuilt appenders, see
|
|
87
|
+
[Advanced Configuration](advanced-configuration.md).
|
data/docs/health.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Health
|
|
2
|
+
|
|
3
|
+
The destination health surface is transport-shaped.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Julewire.health
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Destination health includes:
|
|
10
|
+
|
|
11
|
+
- destination `status`
|
|
12
|
+
- destination write/failure counts
|
|
13
|
+
- transport write/failure counts
|
|
14
|
+
- async queue state
|
|
15
|
+
- file appender metadata
|
|
16
|
+
- child appender shape for multi-appender output
|
|
17
|
+
- lifecycle warnings
|
|
18
|
+
|
|
19
|
+
Lifecycle warnings currently include:
|
|
20
|
+
|
|
21
|
+
- `:async_queue_blocks_when_full`
|
|
22
|
+
- `:async_queue_unbounded`
|
|
23
|
+
- `:sync_multi_appender_blocks_emitters`
|
|
24
|
+
|
|
25
|
+
The adapter does not recreate transport-level drop taxonomy. Semantic Logger
|
|
26
|
+
owns async worker behavior; Julewire reports the transport state it can observe.
|
|
27
|
+
|
|
28
|
+
## Metric Mapping
|
|
29
|
+
|
|
30
|
+
One practical mapping is:
|
|
31
|
+
|
|
32
|
+
| Health path | Metric name |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `counts.*` | `julewire_runtime_total{event}` |
|
|
35
|
+
| `pipeline.counts.*` | `julewire_pipeline_total{event}` |
|
|
36
|
+
| `destinations.*.counts.*` | `julewire_destination_total{destination,event}` |
|
|
37
|
+
| `destinations.*.last_loss.reason` | `julewire_destination_last_loss{destination,reason}` |
|
|
38
|
+
| `destinations.*.transport.counts.*` | `julewire_semantic_logger_transport_total{destination,event}` |
|
|
39
|
+
| `destinations.*.transport.appender.queue_size` | `julewire_semantic_logger_queue_size{destination}` |
|
|
40
|
+
| `destinations.*.transport.appender.max_queue_size` | `julewire_semantic_logger_queue_capacity{destination}` |
|
|
41
|
+
| `destinations.*.transport.warnings.*` | `julewire_semantic_logger_warning{destination,reason}` |
|
|
42
|
+
|
|
43
|
+
Treat core health paths as inputs, not as a global metrics schema.
|
data/docs/transport.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Semantic Logger Transport
|
|
2
|
+
|
|
3
|
+
The adapter uses Semantic Logger as transport only.
|
|
4
|
+
|
|
5
|
+
It does not use Semantic Logger's JSON formatter for Julewire records because
|
|
6
|
+
that formatter emits Semantic Logger's schema. Julewire formatters already own
|
|
7
|
+
the output shape.
|
|
8
|
+
|
|
9
|
+
## Destination Path
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Julewire formatter -> Julewire encoder -> Semantic Logger exact formatter -> IO
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This uses the adapter destination. The destination maps the record with the
|
|
16
|
+
configured formatter, encodes the formatter result with Julewire's encoder, then
|
|
17
|
+
hands the encoded payload string to Semantic Logger. The exact formatter
|
|
18
|
+
preserves that string shape for appenders.
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class LogShape
|
|
22
|
+
def call(record)
|
|
23
|
+
{
|
|
24
|
+
level: record.fetch(:severity),
|
|
25
|
+
message: record.fetch(:message),
|
|
26
|
+
labels: record.fetch(:labels),
|
|
27
|
+
payload: record.fetch(:payload)
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Julewire.configure do |config|
|
|
33
|
+
config.destinations.use(
|
|
34
|
+
:semantic_logger,
|
|
35
|
+
formatter: LogShape.new,
|
|
36
|
+
io: $stdout,
|
|
37
|
+
async: true
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Core passes a frozen, normalized, symbol-key record to the destination
|
|
43
|
+
formatter. The adapter formatter returns the output object; the destination
|
|
44
|
+
encoder turns that object into the final JSON payload.
|
|
45
|
+
|
|
46
|
+
## Design
|
|
47
|
+
|
|
48
|
+
Semantic Logger fits as transport if Julewire owns record mapping.
|
|
49
|
+
|
|
50
|
+
Semantic Logger does not fit as the default formatter unless Julewire accepts
|
|
51
|
+
Semantic Logger's log schema.
|
|
52
|
+
|
|
53
|
+
## Verified Paths
|
|
54
|
+
|
|
55
|
+
Verified paths:
|
|
56
|
+
|
|
57
|
+
- custom destination object formatter -> Julewire-encoded JSON line through Semantic Logger
|
|
58
|
+
- async Semantic Logger appender -> flush drains queued entries
|
|
59
|
+
- file appender -> writes exact Julewire payload JSON lines
|
|
60
|
+
- multiple appenders -> multi-appender output without Julewire owning transport code
|
|
61
|
+
- health -> appender type, async queue state, file metadata, child appenders,
|
|
62
|
+
transport warnings
|
|
63
|
+
|
|
64
|
+
## Metric Mapping
|
|
65
|
+
|
|
66
|
+
The adapter owns metric names because queue, file, and appender behavior is
|
|
67
|
+
transport-specific. One practical mapping is:
|
|
68
|
+
|
|
69
|
+
| Health path | Metric name |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `counts.*` | `julewire_runtime_total{event}` |
|
|
72
|
+
| `pipeline.counts.*` | `julewire_pipeline_total{event}` |
|
|
73
|
+
| `destinations.*.counts.*` | `julewire_destination_total{destination,event}` |
|
|
74
|
+
| `destinations.*.last_loss.reason` | `julewire_destination_last_loss{destination,reason}` |
|
|
75
|
+
| `destinations.*.transport.counts.*` | `julewire_semantic_logger_transport_total{destination,event}` |
|
|
76
|
+
| `destinations.*.transport.appender.queue_size` | `julewire_semantic_logger_queue_size{destination}` |
|
|
77
|
+
| `destinations.*.transport.appender.max_queue_size` | `julewire_semantic_logger_queue_capacity{destination}` |
|
|
78
|
+
| `destinations.*.transport.warnings.*` | `julewire_semantic_logger_warning{destination,reason}` |
|
|
79
|
+
|
|
80
|
+
Extensions should treat core health paths as inputs, not as a global metrics
|
|
81
|
+
schema.
|
|
82
|
+
|
|
83
|
+
Observed behavior:
|
|
84
|
+
|
|
85
|
+
- Semantic Logger's IO appender appends the final newline, so this adapter strips
|
|
86
|
+
one trailing newline when it receives an already encoded string.
|
|
87
|
+
- Output-shaped severity values such as `"INFO"` should not be treated as
|
|
88
|
+
Semantic Logger levels. The transport maps only known levels and otherwise
|
|
89
|
+
uses `:info`. Julewire `:unknown` maps to Semantic Logger `:fatal`.
|
|
90
|
+
- bounded Semantic Logger async queues block producers when full; the adapter
|
|
91
|
+
reports this as a lifecycle warning.
|
|
92
|
+
- `max_queue_size: -1` is unbounded queueing; the adapter reports that as a
|
|
93
|
+
lifecycle warning.
|
|
94
|
+
- sync multi-appender output writes through all child appenders on the caller
|
|
95
|
+
thread; the adapter reports that as a lifecycle warning when more than one
|
|
96
|
+
appender is configured and `async: false`.
|
|
97
|
+
- `flush(timeout:)`, `close(timeout:)`, and `reopen(timeout:)` accept Julewire's
|
|
98
|
+
lifecycle keyword for the destination contract, but Semantic Logger appenders
|
|
99
|
+
own the actual blocking behavior. The timeout is advisory for this adapter.
|
|
100
|
+
- `after_fork!` delegates to `reopen`, matching Semantic Logger's fork contract:
|
|
101
|
+
async appender queues and worker threads are recreated in the child process.
|
|
102
|
+
|
|
103
|
+
Architecture implication:
|
|
104
|
+
|
|
105
|
+
- using Semantic Logger as a destination transport is clean
|
|
106
|
+
- using Semantic Logger as the record schema is not
|
|
107
|
+
- core formatters should return objects; encoders own final serialization
|
|
108
|
+
- core should not grow async/file/multi-appender transport primitives
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/julewire/semantic_logger/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "julewire-semantic_logger"
|
|
7
|
+
spec.version = Julewire::SemanticLogger::VERSION
|
|
8
|
+
spec.authors = ["Alexander Grebennik"]
|
|
9
|
+
spec.email = ["slbug@users.noreply.github.com", "sl.bug.sl@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Semantic Logger transport adapter for Julewire."
|
|
12
|
+
spec.description = "Semantic Logger transport adapter for Julewire destination output."
|
|
13
|
+
spec.homepage = "https://github.com/slbug/julewire"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.4"
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/slbug/julewire/tree/main/gems/semantic_logger"
|
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/slbug/julewire/blob/main/gems/semantic_logger/CHANGELOG.md"
|
|
19
|
+
|
|
20
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
21
|
+
|
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
|
23
|
+
Dir[
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"LICENSE.txt",
|
|
26
|
+
"README.md",
|
|
27
|
+
"docs/**/*.md",
|
|
28
|
+
"julewire-semantic_logger.gemspec",
|
|
29
|
+
"lib/**/*.rb"
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
spec.executables = []
|
|
33
|
+
spec.require_paths = ["lib"]
|
|
34
|
+
|
|
35
|
+
spec.add_dependency "julewire-core", ">= 1.0"
|
|
36
|
+
spec.add_dependency "logger", ">= 1.7"
|
|
37
|
+
spec.add_dependency "semantic_logger", ">= 4.18"
|
|
38
|
+
spec.add_dependency "zeitwerk", ">= 2.8.1"
|
|
39
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module SemanticLogger
|
|
5
|
+
module AppenderHealth
|
|
6
|
+
class << self
|
|
7
|
+
def call(value, index: nil)
|
|
8
|
+
{
|
|
9
|
+
appender_class: value.class.name,
|
|
10
|
+
index: index,
|
|
11
|
+
type: appender_type(value)
|
|
12
|
+
}.compact
|
|
13
|
+
.merge(async_health(value))
|
|
14
|
+
.merge(file_health(value))
|
|
15
|
+
.merge(collection_health(value))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def appender_type(value)
|
|
19
|
+
case value
|
|
20
|
+
when ::SemanticLogger::Appender::Async
|
|
21
|
+
"async"
|
|
22
|
+
when ::SemanticLogger::Appenders
|
|
23
|
+
"multi_appender"
|
|
24
|
+
when ::SemanticLogger::Appender::File
|
|
25
|
+
"file"
|
|
26
|
+
when ::SemanticLogger::Appender::IO
|
|
27
|
+
"io"
|
|
28
|
+
else
|
|
29
|
+
"appender"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def async_health(value)
|
|
34
|
+
return {} unless value.is_a?(::SemanticLogger::Appender::Async)
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
active: value.active?,
|
|
38
|
+
capped: value.capped?,
|
|
39
|
+
max_queue_size: value.max_queue_size,
|
|
40
|
+
queue_size: value.queue.size,
|
|
41
|
+
wrapped: call(value.appender)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def file_health(value)
|
|
46
|
+
return {} unless value.is_a?(::SemanticLogger::Appender::File)
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
current_file_name: value.current_file_name,
|
|
50
|
+
file_name: value.file_name,
|
|
51
|
+
log_count: value.log_count,
|
|
52
|
+
log_size: value.log_size,
|
|
53
|
+
reopen_at: value.reopen_at&.utc&.iso8601
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def collection_health(value)
|
|
58
|
+
return {} unless value.is_a?(::SemanticLogger::Appenders)
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
appender_count: value.length,
|
|
62
|
+
appenders: value.each_with_index.map { |child, index| call(child, index: index) }
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module SemanticLogger
|
|
5
|
+
class Destination
|
|
6
|
+
attr_reader :name
|
|
7
|
+
|
|
8
|
+
def initialize(name:, formatter:, encoder: ENCODER, transport: nil, on_drop: nil, on_failure: nil,
|
|
9
|
+
**transport_options)
|
|
10
|
+
@name = Core::Destinations.normalize_name(name)
|
|
11
|
+
@formatter = formatter
|
|
12
|
+
@encoder = encoder
|
|
13
|
+
@on_drop = validate_optional_callback(on_drop, name: :on_drop)
|
|
14
|
+
@on_failure = validate_optional_callback(on_failure, name: :on_failure)
|
|
15
|
+
@transport = transport || Transport.new(**transport_options)
|
|
16
|
+
@health = Core::Integration::DestinationHealth.new(
|
|
17
|
+
counter_keys: %i[received formatted written failed],
|
|
18
|
+
failure_counter: :failed
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def emit(record)
|
|
23
|
+
formatted = false
|
|
24
|
+
increment(:received)
|
|
25
|
+
payload = @formatter.call(record)
|
|
26
|
+
formatted = true
|
|
27
|
+
@transport.write(encoded_payload(payload), severity: record.fetch(:severity))
|
|
28
|
+
record_written
|
|
29
|
+
nil
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
record_failure(e, formatted: formatted, record: record)
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def flush(*) = call_lifecycle(:flush) { @transport.flush }
|
|
36
|
+
|
|
37
|
+
def close(*) = call_lifecycle(:close) { @transport.close }
|
|
38
|
+
|
|
39
|
+
def reopen(*) = call_lifecycle(:reopen) { @transport.reopen }
|
|
40
|
+
|
|
41
|
+
def after_fork! = call_lifecycle(:after_fork) { @transport.after_fork! }
|
|
42
|
+
|
|
43
|
+
def resource_identity = @transport
|
|
44
|
+
|
|
45
|
+
def health
|
|
46
|
+
transport = @transport.health
|
|
47
|
+
@health.snapshot(
|
|
48
|
+
status: status(@health.degraded?, transport),
|
|
49
|
+
type: "semantic_logger_destination",
|
|
50
|
+
transport: transport
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def validate_optional_callback(callback, name:)
|
|
57
|
+
return unless callback
|
|
58
|
+
return callback if callback.respond_to?(:call)
|
|
59
|
+
|
|
60
|
+
raise ArgumentError, "#{name} must respond to #call"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def encoded_payload(payload)
|
|
64
|
+
return payload if payload.is_a?(String)
|
|
65
|
+
|
|
66
|
+
@encoder.call(payload)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def increment(name)
|
|
70
|
+
@health.increment(name)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def record_written
|
|
74
|
+
@health.increment(:formatted)
|
|
75
|
+
@health.increment(:written)
|
|
76
|
+
@health.clear_degraded!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def record_failure(error, formatted: false, record: nil)
|
|
80
|
+
@health.increment(:formatted) if formatted
|
|
81
|
+
@health.record_failure(error, destination: name, phase: :destination, record_metadata: record_metadata(record))
|
|
82
|
+
notify_failure(error, phase: :destination, record_metadata: record_metadata(record))
|
|
83
|
+
record_drop(:destination_exception, record)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def record_lifecycle_failure(error, action:)
|
|
87
|
+
@health.record_failure(error, destination: name, action: action, phase: :destination_lifecycle)
|
|
88
|
+
notify_failure(error, action: action, phase: :destination_lifecycle)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def call_lifecycle(action)
|
|
92
|
+
yield
|
|
93
|
+
clear_degraded
|
|
94
|
+
true
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
record_lifecycle_failure(e, action: action)
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def notify_failure(error, **metadata)
|
|
101
|
+
Core::Diagnostics::CallbackNotifier.call(@on_failure, error, { destination: name }.merge(metadata))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def record_drop(reason, record)
|
|
105
|
+
Core::Diagnostics::CallbackNotifier.call(
|
|
106
|
+
@on_drop,
|
|
107
|
+
reason,
|
|
108
|
+
{
|
|
109
|
+
destination: name,
|
|
110
|
+
phase: :destination,
|
|
111
|
+
reason: reason,
|
|
112
|
+
record_metadata: record_metadata(record)
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def record_metadata(record)
|
|
118
|
+
Core::Records::Metadata.call(record) if record
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def status(currently_degraded, transport)
|
|
122
|
+
transport_status = transport[:status]
|
|
123
|
+
return :closed if transport_status == :closed
|
|
124
|
+
return :degraded if currently_degraded
|
|
125
|
+
return :degraded if transport_status && transport_status != :ok
|
|
126
|
+
|
|
127
|
+
:ok
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def clear_degraded
|
|
131
|
+
@health.clear_degraded!
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module SemanticLogger
|
|
5
|
+
class ExactFormatter
|
|
6
|
+
PAYLOAD_KEY = :julewire_value
|
|
7
|
+
|
|
8
|
+
def call(log, _logger = nil)
|
|
9
|
+
value = log.payload.fetch(PAYLOAD_KEY)
|
|
10
|
+
return string_value(value) if value.is_a?(String)
|
|
11
|
+
|
|
12
|
+
ENCODER.call(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def string_value(value)
|
|
18
|
+
return value.delete_suffix("\n") if value.end_with?("\n")
|
|
19
|
+
return value.dup if value.frozen?
|
|
20
|
+
|
|
21
|
+
value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module SemanticLogger
|
|
5
|
+
module LifecycleWarnings
|
|
6
|
+
class << self
|
|
7
|
+
def call(async:, appender_count:, max_queue_size:)
|
|
8
|
+
if async && max_queue_size == -1
|
|
9
|
+
[{ reason: :async_queue_unbounded }]
|
|
10
|
+
elsif async
|
|
11
|
+
[{ reason: :async_queue_blocks_when_full, max_queue_size: max_queue_size }]
|
|
12
|
+
elsif appender_count > 1
|
|
13
|
+
[{ reason: :sync_multi_appender_blocks_emitters, appender_count: appender_count }]
|
|
14
|
+
else
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Julewire
|
|
6
|
+
module SemanticLogger
|
|
7
|
+
class Transport
|
|
8
|
+
LOGGER_NAME = "julewire"
|
|
9
|
+
DEFAULT_MAX_QUEUE_SIZE = 10_000
|
|
10
|
+
LEVEL_MAP = {
|
|
11
|
+
# SemanticLogger has no unknown level; fatal keeps unknown core records visible.
|
|
12
|
+
unknown: :fatal
|
|
13
|
+
}.freeze
|
|
14
|
+
LEVEL_SET = ::SemanticLogger::LEVELS.to_h { [it, true] }.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(**options)
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
@async = options.delete(:async) { false }
|
|
19
|
+
@max_queue_size = options.delete(:max_queue_size) { DEFAULT_MAX_QUEUE_SIZE }
|
|
20
|
+
@lag_check_interval = options.delete(:lag_check_interval) { 1_000 }
|
|
21
|
+
@lag_threshold_s = options.delete(:lag_threshold_s) { 30 }
|
|
22
|
+
@write_count = 0
|
|
23
|
+
@failure_count = 0
|
|
24
|
+
@degraded = false
|
|
25
|
+
@closed = false
|
|
26
|
+
@appenders = build_appenders(
|
|
27
|
+
appenders: options.delete(:appenders),
|
|
28
|
+
appender: options.delete(:appender),
|
|
29
|
+
file_name: options.delete(:file_name),
|
|
30
|
+
io: options.delete(:io),
|
|
31
|
+
options: options
|
|
32
|
+
)
|
|
33
|
+
@sink = build_sink(@appenders)
|
|
34
|
+
@appender = build_transport_appender(@sink)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write(value, severity:)
|
|
38
|
+
log = log_for(value, severity: severity)
|
|
39
|
+
@mutex.synchronize do
|
|
40
|
+
@write_count += 1
|
|
41
|
+
# Synchronous appenders write under our mutex; async appenders own
|
|
42
|
+
# queue synchronization and may block on bounded queues.
|
|
43
|
+
appender.log(log) unless @async
|
|
44
|
+
end
|
|
45
|
+
appender.log(log) if @async
|
|
46
|
+
clear_degraded
|
|
47
|
+
nil
|
|
48
|
+
rescue StandardError
|
|
49
|
+
@mutex.synchronize do
|
|
50
|
+
@failure_count += 1
|
|
51
|
+
@degraded = true
|
|
52
|
+
end
|
|
53
|
+
raise
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def flush
|
|
57
|
+
appender.flush if appender.respond_to?(:flush)
|
|
58
|
+
clear_degraded
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def close
|
|
63
|
+
appender.close if appender.respond_to?(:close)
|
|
64
|
+
@mutex.synchronize { @closed = true }
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def reopen
|
|
69
|
+
appender.reopen if appender.respond_to?(:reopen)
|
|
70
|
+
@mutex.synchronize do
|
|
71
|
+
@closed = false
|
|
72
|
+
@degraded = false
|
|
73
|
+
end
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def after_fork! = reopen
|
|
78
|
+
|
|
79
|
+
def health
|
|
80
|
+
counts = @mutex.synchronize do
|
|
81
|
+
{
|
|
82
|
+
closed: @closed,
|
|
83
|
+
degraded: @degraded,
|
|
84
|
+
failures: @failure_count,
|
|
85
|
+
writes: @write_count
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
type: "semantic_logger",
|
|
91
|
+
status: status(counts),
|
|
92
|
+
async: @async,
|
|
93
|
+
warnings: lifecycle_warnings,
|
|
94
|
+
counts: {
|
|
95
|
+
writes: counts.fetch(:writes),
|
|
96
|
+
failures: counts.fetch(:failures)
|
|
97
|
+
},
|
|
98
|
+
appender: appender_health(appender),
|
|
99
|
+
appenders: @appenders.each_with_index.map { |child, index| appender_health(child, index: index) }
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
attr_reader :appender
|
|
106
|
+
|
|
107
|
+
def build_appenders(appenders:, appender:, file_name:, io:, options:)
|
|
108
|
+
specs = appenders.is_a?(Hash) ? [appenders] : Array(appenders).dup
|
|
109
|
+
specs << { appender: appender } if appender
|
|
110
|
+
specs << { file_name: file_name } if file_name
|
|
111
|
+
specs << { io: io } if io
|
|
112
|
+
raise ArgumentError, "semantic logger transport requires io, file_name, appender, or appenders" if specs.empty?
|
|
113
|
+
|
|
114
|
+
specs.map { build_appender(it, defaults: options) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def build_appender(spec, defaults:)
|
|
118
|
+
options = normalize_appender_spec(spec, defaults: defaults)
|
|
119
|
+
options[:formatter] ||= ExactFormatter.new
|
|
120
|
+
::SemanticLogger::Appender.factory(**options, async: false, batch: false)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def normalize_appender_spec(spec, defaults:)
|
|
124
|
+
case spec
|
|
125
|
+
when Hash
|
|
126
|
+
defaults.merge(spec)
|
|
127
|
+
else
|
|
128
|
+
defaults.merge(appender: spec)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_sink(appenders)
|
|
133
|
+
return appenders.first if appenders.one?
|
|
134
|
+
|
|
135
|
+
::SemanticLogger::Appenders.new.tap do |collection|
|
|
136
|
+
appenders.each { collection << it }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def build_transport_appender(sink)
|
|
141
|
+
return sink unless @async
|
|
142
|
+
|
|
143
|
+
::SemanticLogger::Appender::Async.new(
|
|
144
|
+
appender: sink,
|
|
145
|
+
lag_check_interval: @lag_check_interval,
|
|
146
|
+
lag_threshold_s: @lag_threshold_s,
|
|
147
|
+
max_queue_size: @max_queue_size
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def log_for(value, severity:)
|
|
152
|
+
log = ::SemanticLogger::Log.new(LOGGER_NAME, level_for(severity))
|
|
153
|
+
log.assign(payload: { ExactFormatter::PAYLOAD_KEY => value })
|
|
154
|
+
log
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def level_for(severity)
|
|
158
|
+
semantic_level(severity)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def semantic_level(value)
|
|
162
|
+
level = value.is_a?(Symbol) ? value : value.to_s.downcase.to_sym
|
|
163
|
+
level = LEVEL_MAP.fetch(level, level)
|
|
164
|
+
return level if LEVEL_SET.key?(level)
|
|
165
|
+
|
|
166
|
+
:info
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def status(counts)
|
|
170
|
+
return :closed if counts.fetch(:closed)
|
|
171
|
+
return :degraded if appender.is_a?(::SemanticLogger::Appender::Async) && !appender.active?
|
|
172
|
+
return :degraded if counts.fetch(:degraded)
|
|
173
|
+
|
|
174
|
+
:ok
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def clear_degraded
|
|
178
|
+
@mutex.synchronize { @degraded = false }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def lifecycle_warnings
|
|
182
|
+
LifecycleWarnings.call(async: @async, appender_count: @appenders.length, max_queue_size: @max_queue_size)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def appender_health(value, index: nil)
|
|
186
|
+
AppenderHealth.call(value, index: index)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
require "julewire/core"
|
|
5
|
+
require "semantic_logger"
|
|
6
|
+
|
|
7
|
+
module Julewire
|
|
8
|
+
module SemanticLogger
|
|
9
|
+
ENCODER = Julewire::JsonEncoder.new(append_newline: false).freeze
|
|
10
|
+
private_constant :ENCODER
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
loader = Zeitwerk::Loader.for_gem_extension(self)
|
|
14
|
+
loader.setup
|
|
15
|
+
Core::Destinations.register(:semantic_logger) do |name:, **options|
|
|
16
|
+
Julewire::SemanticLogger::Destination.new(name: name, **options)
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: julewire-semantic_logger
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alexander Grebennik
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: julewire-core
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: logger
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.7'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.7'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: semantic_logger
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4.18'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '4.18'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: zeitwerk
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 2.8.1
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 2.8.1
|
|
68
|
+
description: Semantic Logger transport adapter for Julewire destination output.
|
|
69
|
+
email:
|
|
70
|
+
- slbug@users.noreply.github.com
|
|
71
|
+
- sl.bug.sl@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- LICENSE.txt
|
|
78
|
+
- README.md
|
|
79
|
+
- docs/advanced-configuration.md
|
|
80
|
+
- docs/configuration.md
|
|
81
|
+
- docs/health.md
|
|
82
|
+
- docs/transport.md
|
|
83
|
+
- julewire-semantic_logger.gemspec
|
|
84
|
+
- lib/julewire-semantic_logger.rb
|
|
85
|
+
- lib/julewire/semantic_logger.rb
|
|
86
|
+
- lib/julewire/semantic_logger/appender_health.rb
|
|
87
|
+
- lib/julewire/semantic_logger/destination.rb
|
|
88
|
+
- lib/julewire/semantic_logger/exact_formatter.rb
|
|
89
|
+
- lib/julewire/semantic_logger/lifecycle_warnings.rb
|
|
90
|
+
- lib/julewire/semantic_logger/transport.rb
|
|
91
|
+
- lib/julewire/semantic_logger/version.rb
|
|
92
|
+
homepage: https://github.com/slbug/julewire
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata:
|
|
96
|
+
homepage_uri: https://github.com/slbug/julewire
|
|
97
|
+
source_code_uri: https://github.com/slbug/julewire/tree/main/gems/semantic_logger
|
|
98
|
+
changelog_uri: https://github.com/slbug/julewire/blob/main/gems/semantic_logger/CHANGELOG.md
|
|
99
|
+
rubygems_mfa_required: 'true'
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '3.4'
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '0'
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 4.0.14
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: Semantic Logger transport adapter for Julewire.
|
|
117
|
+
test_files: []
|