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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c97dff36fddffd8ef26c0e08eee6912b41509c203803ef11d467b8e64fa8a1a
4
- data.tar.gz: 6bb760bfb58992ca97db27ef7b5a220617984256f488a2eae4e7f1bd7320eab9
3
+ metadata.gz: c8cf0e19aa5820ae2ee81a3cf73ece78e151e6a53f7fc48f8c74e94f9b7841ed
4
+ data.tar.gz: c7ab4e1bc11362adc93bfe54dcf06360b846a232d1d433c597a60f7d2e4a1fd0
5
5
  SHA512:
6
- metadata.gz: 935b133170aacfd15bd0b9152cd30dd845a807fcf0e31d417fda1c2d6186c2ed0acedb11156554420370630a6151037697fa2a0f903cabe989fbb6a59234dae5
7
- data.tar.gz: 2f30001a038326c9eb53e2427f85a905f001a1781bc91968e86e2ceff0a49b3bb4b0e606609242675630a73bebcd5eb3829546413f1113225ebdb06f99ca7cb3
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module StructuredLogger
5
- VERSION = '0.3.4'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  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.3.4
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-03-31 00:00:00.000000000 Z
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://github.com/philiprehberger/rb-structured-logger
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://github.com/philiprehberger/rb-structured-logger
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'