philiprehberger-structured_logger 0.3.4 → 0.5.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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +54 -0
- data/lib/philiprehberger/structured_logger/logger.rb +87 -0
- data/lib/philiprehberger/structured_logger/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8cf0e19aa5820ae2ee81a3cf73ece78e151e6a53f7fc48f8c74e94f9b7841ed
|
|
4
|
+
data.tar.gz: c7ab4e1bc11362adc93bfe54dcf06360b846a232d1d433c597a60f7d2e4a1fd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d239e014b25dd71f821e97213b1ad979c2444d6cae3f4db330debec29e6f6a045b6dde4abc84e350f6cca3f384886fccc2430d413709131f58a6b81f8b09657e
|
|
7
|
+
data.tar.gz: 9a390ecf766ec22be918f9fb3a2daf5fcc8643cafe7848e82b72ce6fae7fc90c3cc176ba15da5dc2fb7f2544cf51730088851ec4fa801fe80ab70dfdd98eca7f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-04-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Logger#with_tags(*tags)` for tagged context blocks with automatic restoration
|
|
14
|
+
- `Logger#measure_value(message)` variant of `#measure` that returns the block result while still emitting the timing entry
|
|
15
|
+
|
|
16
|
+
## [0.4.0] - 2026-04-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `Logger#measure(event, **context) { block }` yields to the block, captures elapsed time, and emits a single info-level structured event with `duration_ms` (plus `error`/`error_class` on failure; original exception re-raised)
|
|
20
|
+
|
|
21
|
+
## [0.3.5] - 2026-03-31
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Add GitHub issue templates, dependabot config, and PR template
|
|
25
|
+
|
|
10
26
|
## [0.3.4] - 2026-03-31
|
|
11
27
|
|
|
12
28
|
### Changed
|
data/README.md
CHANGED
|
@@ -121,6 +121,23 @@ end
|
|
|
121
121
|
logger.log_exception(e, level: :fatal, user_id: 42)
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### Timing a block
|
|
125
|
+
|
|
126
|
+
Use `measure` to time a block and emit a single structured event with `duration_ms`:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
logger.measure("db.query", table: "users") { User.find(1) }
|
|
130
|
+
# => {"timestamp":"...","level":"info","message":"db.query","event":"db.query","table":"users","duration_ms":12.345}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
On failure, the original exception is re-raised and the log entry also includes `error` and `error_class`:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
logger.measure("db.query") { raise "boom" }
|
|
137
|
+
# => {"timestamp":"...","level":"info","message":"db.query","event":"db.query","duration_ms":0.123,"error":"boom","error_class":"RuntimeError"}
|
|
138
|
+
# RuntimeError: boom
|
|
139
|
+
```
|
|
140
|
+
|
|
124
141
|
### Multiple Outputs
|
|
125
142
|
|
|
126
143
|
Log to multiple destinations simultaneously. Each output can have its own level filter and formatter:
|
|
@@ -239,6 +256,40 @@ logger.close
|
|
|
239
256
|
|
|
240
257
|
When the buffer is full, writes fall back to synchronous mode (backpressure) to avoid dropping log entries.
|
|
241
258
|
|
|
259
|
+
### Tagged Context
|
|
260
|
+
|
|
261
|
+
Use `with_tags` to add tags to the logging context for the duration of a block. Tags merge with any existing tags (de-duplicated, preserving order) and the original context is restored on exit:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
require "philiprehberger/structured_logger"
|
|
265
|
+
|
|
266
|
+
logger = Philiprehberger::StructuredLogger::Logger.new
|
|
267
|
+
|
|
268
|
+
logger.with_tags("auth", "request") do
|
|
269
|
+
logger.info("Login attempt")
|
|
270
|
+
# => {"timestamp":"...","level":"info","message":"Login attempt","tags":["auth","request"]}
|
|
271
|
+
end
|
|
272
|
+
# Tags are restored after the block
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Measuring with Return Value
|
|
276
|
+
|
|
277
|
+
Use `measure_value` when you need the block's result while still emitting a timing entry:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
require "philiprehberger/structured_logger"
|
|
281
|
+
|
|
282
|
+
logger = Philiprehberger::StructuredLogger::Logger.new
|
|
283
|
+
|
|
284
|
+
result = logger.measure_value("db.query") do
|
|
285
|
+
# ...query the database...
|
|
286
|
+
[{ id: 1, name: "Alice" }]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# result == [{ id: 1, name: "Alice" }]
|
|
290
|
+
# => {"timestamp":"...","level":"info","message":"db.query","event":"db.query","duration_ms":12.345}
|
|
291
|
+
```
|
|
292
|
+
|
|
242
293
|
## API
|
|
243
294
|
|
|
244
295
|
### `Philiprehberger::StructuredLogger::Logger`
|
|
@@ -257,6 +308,9 @@ When the buffer is full, writes fall back to synchronous mode (backpressure) to
|
|
|
257
308
|
| `with_context(**extra, &block)` | Temporarily merge context for a block |
|
|
258
309
|
| `silence(level = :fatal, &block)` | Temporarily raise log level for a block |
|
|
259
310
|
| `log_exception(exception, level: :error, **extra)` | Log exception details |
|
|
311
|
+
| `measure(event_name, **context) { block }` | Time a block, emit an info event with `duration_ms`, and re-raise on failure |
|
|
312
|
+
| `#with_tags(*tags) { block }` | Add tags to context for the block |
|
|
313
|
+
| `#measure_value(message) { block }` | Like #measure but returns the block's value |
|
|
260
314
|
| `add_output(io, level: nil, formatter: nil)` | Add an output destination at runtime |
|
|
261
315
|
| `with_correlation_id(id = nil, &block)` | Set a correlation ID for the block |
|
|
262
316
|
| `flush` | Force write of all buffered log entries |
|
|
@@ -50,6 +50,41 @@ module Philiprehberger
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Adds the given tags to the logger's context under the `:tags`
|
|
54
|
+
# key, merging with any existing tags (de-duplicated, preserving
|
|
55
|
+
# insertion order). When a block is given, the previous context is
|
|
56
|
+
# restored when the block exits (even on exception). Without a
|
|
57
|
+
# block, the change persists like {#with_context}.
|
|
58
|
+
#
|
|
59
|
+
# @param tags [Array<String, Symbol>] one or more tags to add.
|
|
60
|
+
# @yield (optional) executes within the tagged context; the
|
|
61
|
+
# original context is restored on exit.
|
|
62
|
+
# @return [Object, Hash] the block's return value when a block is
|
|
63
|
+
# given, otherwise the new merged context hash.
|
|
64
|
+
#
|
|
65
|
+
# @example Block form
|
|
66
|
+
# logger.with_tags('auth', 'request') do
|
|
67
|
+
# logger.info('Login attempt')
|
|
68
|
+
# # entry includes tags: ['auth', 'request']
|
|
69
|
+
# end
|
|
70
|
+
def with_tags(*tags)
|
|
71
|
+
existing = @context[:tags] || []
|
|
72
|
+
merged_tags = (existing + tags).uniq
|
|
73
|
+
if block_given?
|
|
74
|
+
@monitor.synchronize do
|
|
75
|
+
original = @context
|
|
76
|
+
@context = @context.merge(tags: merged_tags).freeze
|
|
77
|
+
yield
|
|
78
|
+
ensure
|
|
79
|
+
@context = original
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
@monitor.synchronize do
|
|
83
|
+
@context = @context.merge(tags: merged_tags).freeze
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
53
88
|
def silence(temp_level = :fatal, &block)
|
|
54
89
|
@monitor.synchronize do
|
|
55
90
|
original = @level
|
|
@@ -76,6 +111,58 @@ module Philiprehberger
|
|
|
76
111
|
**extra)
|
|
77
112
|
end
|
|
78
113
|
|
|
114
|
+
# Yields to the given block, measures its monotonic wall-clock
|
|
115
|
+
# duration, and emits a single info-level log entry describing the
|
|
116
|
+
# outcome. On success, the block's return value is returned. On
|
|
117
|
+
# exception, the failure is logged and the original exception is
|
|
118
|
+
# re-raised.
|
|
119
|
+
#
|
|
120
|
+
# @param event_name [String, Symbol] the event name to record as
|
|
121
|
+
# the `event` field in the log entry.
|
|
122
|
+
# @param context [Hash] extra context merged into the log entry.
|
|
123
|
+
# @yield executes the measured block.
|
|
124
|
+
# @return [Object] the block's return value on success.
|
|
125
|
+
# @raise re-raises any exception raised by the block.
|
|
126
|
+
#
|
|
127
|
+
# @example Measuring a database query
|
|
128
|
+
# logger.measure('db.query', table: 'users') { User.find(1) }
|
|
129
|
+
# # logs event: 'db.query', table: 'users', duration_ms: 12.345
|
|
130
|
+
def measure(event_name, **context)
|
|
131
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
132
|
+
begin
|
|
133
|
+
result = yield
|
|
134
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0).round(3)
|
|
135
|
+
log(:info, event_name.to_s, event: event_name, duration_ms: duration_ms, **context)
|
|
136
|
+
result
|
|
137
|
+
rescue StandardError => e
|
|
138
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0).round(3)
|
|
139
|
+
log(:info, event_name.to_s,
|
|
140
|
+
event: event_name,
|
|
141
|
+
duration_ms: duration_ms,
|
|
142
|
+
error: e.message,
|
|
143
|
+
error_class: e.class.name,
|
|
144
|
+
**context)
|
|
145
|
+
raise
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Variant of {#measure} that emits the same timing log entry but
|
|
150
|
+
# also returns the block's return value. Captures and re-raises
|
|
151
|
+
# exceptions like {#measure}.
|
|
152
|
+
#
|
|
153
|
+
# @param event_name [String, Symbol] the event name to record as
|
|
154
|
+
# the `event` field in the log entry.
|
|
155
|
+
# @param context [Hash] extra context merged into the log entry.
|
|
156
|
+
# @yield executes the measured block.
|
|
157
|
+
# @return [Object] the block's return value on success.
|
|
158
|
+
# @raise re-raises any exception raised by the block.
|
|
159
|
+
#
|
|
160
|
+
# @example Capturing a query result
|
|
161
|
+
# result = logger.measure_value('db.query') { query_database }
|
|
162
|
+
def measure_value(event_name, **context, &)
|
|
163
|
+
measure(event_name, **context, &)
|
|
164
|
+
end
|
|
165
|
+
|
|
79
166
|
def flush
|
|
80
167
|
@monitor.synchronize { @outputs.each { |out| out[:io].flush if out[:io].respond_to?(:flush) } }
|
|
81
168
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-structured_logger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A zero-dependency Ruby gem for structured JSON logging with context merging,
|
|
14
14
|
child loggers, level filtering, and pluggable outputs.
|
|
@@ -26,11 +26,11 @@ files:
|
|
|
26
26
|
- lib/philiprehberger/structured_logger/formatter.rb
|
|
27
27
|
- lib/philiprehberger/structured_logger/logger.rb
|
|
28
28
|
- lib/philiprehberger/structured_logger/version.rb
|
|
29
|
-
homepage: https://
|
|
29
|
+
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-structured_logger
|
|
30
30
|
licenses:
|
|
31
31
|
- MIT
|
|
32
32
|
metadata:
|
|
33
|
-
homepage_uri: https://
|
|
33
|
+
homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-structured_logger
|
|
34
34
|
source_code_uri: https://github.com/philiprehberger/rb-structured-logger
|
|
35
35
|
changelog_uri: https://github.com/philiprehberger/rb-structured-logger/blob/main/CHANGELOG.md
|
|
36
36
|
rubygems_mfa_required: 'true'
|