logsy 0.3.0 → 0.4.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 +12 -0
- data/README.md +34 -23
- data/lib/logsy/configuration.rb +7 -2
- data/lib/logsy/json_formatter.rb +17 -10
- data/lib/logsy/railtie.rb +13 -0
- data/lib/logsy/sidekiq_middleware.rb +28 -10
- data/lib/logsy/version.rb +1 -1
- data/lib/logsy.rb +29 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 980d9365473b25931ea00736958f1980b5872ec25901dbd09a58347f4eca8c70
|
|
4
|
+
data.tar.gz: 0646f18af7c441b599e61ec5dfb133268cf4d371594b8a08363c6511a7f758d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6687e938d72ff9054851a86f10cc7450046110d472a88043e125aef5b4c3797d236b9406b1c3645798cb8486b15614a1fc454aaa252cfd2ff2c39714e5a6221d
|
|
7
|
+
data.tar.gz: 6cd7238d880da15217483ecbaefe19f21f1bb2f6a591059b4a9feb3507e1b1942f21ec936b5af59ee5c35179d6d0efa22bada6e423b616bd2bf0fe6ae0782f78
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-06-29
|
|
4
|
+
|
|
5
|
+
- **Sidekiq middleware is now auto-registered** — the Railtie calls `Logsy::SidekiqMiddleware.install!` for you once Sidekiq is loaded, so `request_id` (and any `job_propagated_keys`) propagates into jobs with zero per-app wiring. No more `require 'logsy/sidekiq_middleware'` + three `chain.add` lines in `config/initializers/sidekiq.rb`. Opt out with `config.auto_register_sidekiq_middleware = false`. The registration initializer runs `after: :load_config_initializers`, so it works even when the app uses `gem 'sidekiq', require: false`.
|
|
6
|
+
- **`Logsy::SidekiqMiddleware.install!(sidekiq = ::Sidekiq)`**: new single entry point that registers the client + server middleware (client on both client and server chains, server on the server chain). Non-Rails apps now wire Sidekiq with one line instead of two config blocks. Idempotent — Sidekiq's `Chain#add` replaces, so it's safe alongside a stray manual registration.
|
|
7
|
+
- **`Logsy.tag(**tags)`**: set several tags at once (`Logsy.tag(user_id: u.id, plan: 'pro')`), with the same String coercion as `Logsy[]=`.
|
|
8
|
+
- **`Logsy.with(**tags) { ... }`**: set tags for the duration of a block and restore the previous values afterwards (deleting keys that were unset before), even if the block raises. Scopes context to a unit of work without leaking it — useful in service objects, rake tasks, and threads the Rails executor / Sidekiq middleware don't reset. Returns the block's value.
|
|
9
|
+
- `Configuration`: new `auto_register_sidekiq_middleware` (default `true`).
|
|
10
|
+
- **Performance** (`JsonFormatter`): no behaviour or output change, just less work on the hot path —
|
|
11
|
+
- Hash (wide-event) messages now skip the caller-location stack walk entirely. They're logged from a Logsy hook frame with no app frame to attribute, so the walk only ever scanned the full stack to emit no `file` anyway. Cuts a wide-event line with `include_caller_location` on from ~6.0µs to ~2.5µs (~2.4x) — and these fire once per request and once per job.
|
|
12
|
+
- The ANSI-code strip on String messages is now guarded by a cheap `include?("\e")` byte scan, so the common plain log line skips the regex and its allocation (~2.3x faster on that step).
|
|
13
|
+
- `app_root` is computed once per log line and threaded through the frame walk instead of being rebuilt for every stack frame inspected, removing an allocation per frame on the caller-location path.
|
|
14
|
+
|
|
3
15
|
## [0.3.0] - 2026-06-21
|
|
4
16
|
|
|
5
17
|
- **`Logsy::JobHooks`**: new ActiveJob concern — the background-job counterpart of `ControllerHooks`. Emits one wide event (`event: "job"`) when each job finishes, carrying `job_class`, `queue`, `active_job_id`, `executions`, `duration_ms`, `status` (`"ok"`/`"error"`), `error`, and every Logsy tag set during the run. Emitted even when the job raises (then re-raised). Override `logsy_job_summary_extras` to add fields. Job arguments are deliberately omitted — ActiveJob already logs them at enqueue time, correlatable by `active_job_id`.
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Tagged structured logging for Rails apps. JSON output, one line per log call.
|
|
4
4
|
|
|
5
|
-
Logsy gives you **one wide JSON event per request** plus **correlated breadcrumb logs**, all tagged with whatever per-request context you set via `Logsy[:key] = value`. Tags propagate across background jobs (Sidekiq middleware
|
|
5
|
+
Logsy gives you **one wide JSON event per request** plus **correlated breadcrumb logs**, all tagged with whatever per-request context you set via `Logsy[:key] = value`. Tags propagate across background jobs (Sidekiq middleware auto-wired — zero config). Where you ship the JSON is your call — Logsy just writes structured lines to stdout.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -53,26 +53,14 @@ end
|
|
|
53
53
|
|
|
54
54
|
That's it. No `Current` model to define, no attribute declarations — just key/value.
|
|
55
55
|
|
|
56
|
-
### 5.
|
|
56
|
+
### 5. Background job propagation — automatic for Sidekiq
|
|
57
57
|
|
|
58
|
-
If you use Sidekiq
|
|
58
|
+
If you use Sidekiq, there's **nothing to do**: the Railtie registers the bundled middleware for you once Sidekiq is loaded, so `request_id` (and any other `job_propagated_keys`) flows from the enqueueing request into the job — and on into any jobs that job enqueues.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
# config/initializers/sidekiq.rb
|
|
62
|
-
require 'logsy/sidekiq_middleware'
|
|
63
|
-
|
|
64
|
-
Sidekiq.configure_client do |config|
|
|
65
|
-
config.client_middleware { |chain| chain.add(Logsy::SidekiqMiddleware::Client) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
Sidekiq.configure_server do |config|
|
|
69
|
-
config.client_middleware { |chain| chain.add(Logsy::SidekiqMiddleware::Client) }
|
|
70
|
-
config.server_middleware { |chain| chain.add(Logsy::SidekiqMiddleware::Server) }
|
|
71
|
-
end
|
|
72
|
-
```
|
|
60
|
+
Want to propagate more than `request_id`? That's the only thing you'd configure:
|
|
73
61
|
|
|
74
62
|
```ruby
|
|
75
|
-
# config/initializers/logsy.rb (optional
|
|
63
|
+
# config/initializers/logsy.rb (optional)
|
|
76
64
|
Logsy.configure do |c|
|
|
77
65
|
c.job_propagated_keys = %i[request_id user_id tenant_id]
|
|
78
66
|
end
|
|
@@ -84,7 +72,14 @@ The bundled middleware:
|
|
|
84
72
|
|
|
85
73
|
Anything the job code writes via `Logsy[:foo] = bar` while running shows up on every log line emitted during the job. Tags reset automatically after each job (no leak between jobs sharing a worker thread). The server middleware also sets `Logsy[:job_class]` (the wrapped ActiveJob class name, e.g. `"GenerateEodJob"`, falling back to the worker class for plain Sidekiq jobs).
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
To wire it up yourself (non-Rails, or after opting out with `config.auto_register_sidekiq_middleware = false`) it's a one-liner:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
require 'logsy/sidekiq_middleware'
|
|
79
|
+
Logsy::SidekiqMiddleware.install! # registers client + server middleware
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For other job systems (Resque, GoodJob, custom), write your own middleware using the same pattern — Logsy's `[]=` / `[]` / `tag` / `with` / `tags` / `reset` API is generic.
|
|
88
83
|
|
|
89
84
|
### 6. Job wide event — automatic
|
|
90
85
|
|
|
@@ -175,14 +170,24 @@ Notes on the fields:
|
|
|
175
170
|
## API reference
|
|
176
171
|
|
|
177
172
|
```ruby
|
|
178
|
-
Logsy[:user_id] = 'u-1'
|
|
179
|
-
Logsy[:user_id]
|
|
180
|
-
Logsy.
|
|
181
|
-
Logsy.
|
|
173
|
+
Logsy[:user_id] = 'u-1' # set a tag
|
|
174
|
+
Logsy[:user_id] # read it
|
|
175
|
+
Logsy.tag(user_id: 'u-1', plan: 'pro') # set several at once
|
|
176
|
+
Logsy.with(order_id: 'o-9') { ... } # set for the block, restore afterwards
|
|
177
|
+
Logsy.tags # all tags as a hash
|
|
178
|
+
Logsy.reset # clear all tags (the controller/middleware do this for you)
|
|
182
179
|
```
|
|
183
180
|
|
|
184
181
|
Symbol and string keys are equivalent — `Logsy['user_id']` and `Logsy[:user_id]` access the same slot.
|
|
185
182
|
|
|
183
|
+
`Logsy.with` scopes tags to a block and restores the prior values on exit (even if the block raises) — handy in service objects, rake tasks, or threads the Rails executor / Sidekiq middleware don't reset for you:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
Logsy.with(order_id: order.id) do
|
|
187
|
+
process(order) # every log line here carries order_id; gone afterwards
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
186
191
|
## Customizing the wide event
|
|
187
192
|
|
|
188
193
|
Override `logsy_request_summary_extras` in your controller to add fields:
|
|
@@ -234,6 +239,12 @@ Logsy.configure do |c|
|
|
|
234
239
|
# so jobs emit summaries with no setup). Set false to wire it yourself.
|
|
235
240
|
# Default: true
|
|
236
241
|
c.auto_include_job_hooks = true
|
|
242
|
+
|
|
243
|
+
# Register the bundled Sidekiq middleware automatically when Sidekiq is
|
|
244
|
+
# loaded (the Railtie does this so request_id propagates into jobs with
|
|
245
|
+
# no setup). Set false to call Logsy::SidekiqMiddleware.install! yourself.
|
|
246
|
+
# Default: true
|
|
247
|
+
c.auto_register_sidekiq_middleware = true
|
|
237
248
|
end
|
|
238
249
|
```
|
|
239
250
|
|
|
@@ -251,7 +262,7 @@ Logsy fills the gap: a small, dictionary-style API (`Logsy[:foo] = bar`) plus a
|
|
|
251
262
|
|
|
252
263
|
```bash
|
|
253
264
|
bundle install
|
|
254
|
-
bundle exec rspec #
|
|
265
|
+
bundle exec rspec # 64 specs
|
|
255
266
|
bundle exec rubocop # lint
|
|
256
267
|
```
|
|
257
268
|
|
data/lib/logsy/configuration.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Logsy
|
|
|
23
23
|
attr_accessor :job_propagated_keys, :ignored_caller_paths,
|
|
24
24
|
:include_caller_location, :request_summary_event_name,
|
|
25
25
|
:job_summary_event_name, :caller_location_max_depth,
|
|
26
|
-
:auto_include_job_hooks
|
|
26
|
+
:auto_include_job_hooks, :auto_register_sidekiq_middleware
|
|
27
27
|
|
|
28
28
|
def initialize
|
|
29
29
|
@job_propagated_keys = [:request_id]
|
|
@@ -35,11 +35,16 @@ module Logsy
|
|
|
35
35
|
# into ActiveJob::Base, so every job emits a summary wide event with
|
|
36
36
|
# no per-app setup. Set to false to opt out and include it manually.
|
|
37
37
|
@auto_include_job_hooks = true
|
|
38
|
+
# When true (the default), the Railtie registers the bundled Sidekiq
|
|
39
|
+
# middleware (client + server) for you when Sidekiq is loaded, so
|
|
40
|
+
# request_id and friends propagate into jobs with no per-app wiring.
|
|
41
|
+
# Set to false to register them yourself (or not at all).
|
|
42
|
+
@auto_register_sidekiq_middleware = true
|
|
38
43
|
# How many stack frames to inspect before giving up on attributing
|
|
39
44
|
# the log line to app code. App frames sit within a few dozen frames
|
|
40
45
|
# of the logger; framework-only lines (no app frame at all) would
|
|
41
46
|
# otherwise pay a full walk of a 100+ deep Rails stack on every line.
|
|
42
|
-
@caller_location_max_depth
|
|
47
|
+
@caller_location_max_depth = 64
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
end
|
data/lib/logsy/json_formatter.rb
CHANGED
|
@@ -42,7 +42,11 @@ module Logsy
|
|
|
42
42
|
level: severity
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
# Hash messages are structured wide-event emissions from our own
|
|
46
|
+
# ControllerHooks/JobHooks. They're logged from a logsy frame with no
|
|
47
|
+
# app frame above to attribute, so the caller walk only ever scans the
|
|
48
|
+
# full stack to emit no :file — skip it (output is identical either way).
|
|
49
|
+
add_caller_location!(payload) if Logsy.configuration.include_caller_location && !message.is_a?(::Hash)
|
|
46
50
|
merge_context!(payload)
|
|
47
51
|
merge_message!(payload, message)
|
|
48
52
|
|
|
@@ -54,10 +58,11 @@ module Logsy
|
|
|
54
58
|
private
|
|
55
59
|
|
|
56
60
|
def add_caller_location!(payload)
|
|
57
|
-
|
|
61
|
+
root = app_root # computed once per line, not rebuilt for every frame inspected
|
|
62
|
+
location = find_user_frame(root)
|
|
58
63
|
return unless location
|
|
59
64
|
|
|
60
|
-
payload[:file] = "#{relative_path(location.path)}:#{location.lineno}"
|
|
65
|
+
payload[:file] = "#{relative_path(location.path, root)}:#{location.lineno}"
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
# Walk the stack in small batches instead of capturing it all at once:
|
|
@@ -66,14 +71,14 @@ module Logsy
|
|
|
66
71
|
# capturing everything costs ~3x more per log line. The walk is capped
|
|
67
72
|
# at caller_location_max_depth so framework-only lines (no app frame
|
|
68
73
|
# anywhere) don't pay for a full-stack scan either.
|
|
69
|
-
def find_user_frame
|
|
74
|
+
def find_user_frame(root)
|
|
70
75
|
start = 2 # skip find_user_frame and add_caller_location!
|
|
71
76
|
limit = start + Logsy.configuration.caller_location_max_depth
|
|
72
77
|
while start < limit
|
|
73
78
|
batch = caller_locations(start, [FRAME_BATCH_SIZE, limit - start].min)
|
|
74
79
|
return nil if batch.nil? || batch.empty?
|
|
75
80
|
|
|
76
|
-
batch.each { |loc| return loc if user_frame?(loc.path) }
|
|
81
|
+
batch.each { |loc| return loc if user_frame?(loc.path, root) }
|
|
77
82
|
return nil if batch.size < FRAME_BATCH_SIZE
|
|
78
83
|
|
|
79
84
|
start += FRAME_BATCH_SIZE
|
|
@@ -85,10 +90,10 @@ module Logsy
|
|
|
85
90
|
# stdlib, or logging machinery. With an app root (Rails), only frames
|
|
86
91
|
# under it count (vendored gems under root excluded); without one,
|
|
87
92
|
# any non-gem frame counts.
|
|
88
|
-
def user_frame?(path)
|
|
93
|
+
def user_frame?(path, root)
|
|
89
94
|
return false if Logsy.configuration.ignored_caller_paths.any? { |pattern| path.match?(pattern) }
|
|
90
95
|
|
|
91
|
-
if
|
|
96
|
+
if root
|
|
92
97
|
path.start_with?(root) && !path.include?('/vendor/bundle/')
|
|
93
98
|
else
|
|
94
99
|
!gem_frame?(path)
|
|
@@ -105,8 +110,7 @@ module Logsy
|
|
|
105
110
|
defined?(Rails) && Rails.respond_to?(:root) && Rails.root ? "#{Rails.root}/" : nil
|
|
106
111
|
end
|
|
107
112
|
|
|
108
|
-
def relative_path(path)
|
|
109
|
-
root = app_root
|
|
113
|
+
def relative_path(path, root)
|
|
110
114
|
return path unless root && path.start_with?(root)
|
|
111
115
|
|
|
112
116
|
path[root.length..]
|
|
@@ -123,7 +127,10 @@ module Logsy
|
|
|
123
127
|
def merge_message!(payload, message)
|
|
124
128
|
case message
|
|
125
129
|
when ::String
|
|
126
|
-
|
|
130
|
+
# Only ActiveRecord SQL lines carry ANSI codes; skip the regex (and
|
|
131
|
+
# its allocation) on the common plain line with a cheap byte scan.
|
|
132
|
+
cleaned = message.include?("\e") ? message.gsub(ANSI_ESCAPE, '') : message
|
|
133
|
+
payload[:msg] = cleaned.strip
|
|
127
134
|
when ::Hash
|
|
128
135
|
message.each { |k, v| payload[k.to_sym] = v }
|
|
129
136
|
when ::Exception
|
data/lib/logsy/railtie.rb
CHANGED
|
@@ -8,6 +8,8 @@ module Logsy
|
|
|
8
8
|
# that the "Started GET ..." line is already tagged.
|
|
9
9
|
# - Includes Logsy::JobHooks into ActiveJob::Base so every job emits a
|
|
10
10
|
# summary wide event (disable via config.auto_include_job_hooks = false).
|
|
11
|
+
# - Registers the bundled Sidekiq middleware when Sidekiq is loaded
|
|
12
|
+
# (disable via config.auto_register_sidekiq_middleware = false).
|
|
11
13
|
class Railtie < ::Rails::Railtie
|
|
12
14
|
initializer 'logsy.insert_rack_middleware' do |app|
|
|
13
15
|
app.middleware.insert_after(ActionDispatch::RequestId, Logsy::RackMiddleware)
|
|
@@ -21,5 +23,16 @@ module Logsy
|
|
|
21
23
|
include(Logsy::JobHooks) if Logsy.configuration.auto_include_job_hooks
|
|
22
24
|
end
|
|
23
25
|
end
|
|
26
|
+
|
|
27
|
+
# Runs after config/initializers so it sees Sidekiq even when the app
|
|
28
|
+
# uses `gem 'sidekiq', require: false` and loads it in an initializer.
|
|
29
|
+
# Registering here still takes effect: Sidekiq reads its middleware
|
|
30
|
+
# chains when jobs are enqueued/run, which is always after boot.
|
|
31
|
+
initializer 'logsy.register_sidekiq_middleware', after: :load_config_initializers do
|
|
32
|
+
next unless Logsy.configuration.auto_register_sidekiq_middleware && defined?(::Sidekiq)
|
|
33
|
+
|
|
34
|
+
require 'logsy/sidekiq_middleware'
|
|
35
|
+
Logsy::SidekiqMiddleware.install!
|
|
36
|
+
end
|
|
24
37
|
end
|
|
25
38
|
end
|
|
@@ -4,18 +4,36 @@ require 'logsy/sidekiq_middleware/client'
|
|
|
4
4
|
require 'logsy/sidekiq_middleware/server'
|
|
5
5
|
|
|
6
6
|
module Logsy
|
|
7
|
-
# Sidekiq middleware namespace.
|
|
8
|
-
# railtie) to make {Client} and {Server} available, then register them
|
|
9
|
-
# with Sidekiq:
|
|
7
|
+
# Sidekiq middleware namespace.
|
|
10
8
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
9
|
+
# In a Rails app there is nothing to do: the Railtie calls {install!} for
|
|
10
|
+
# you once Sidekiq is loaded (opt out with
|
|
11
|
+
# `config.auto_register_sidekiq_middleware = false`).
|
|
14
12
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
13
|
+
# Outside Rails — or after opting out — require this file and register the
|
|
14
|
+
# middleware with a single call:
|
|
15
|
+
#
|
|
16
|
+
# require 'logsy/sidekiq_middleware'
|
|
17
|
+
# Logsy::SidekiqMiddleware.install!
|
|
19
18
|
module SidekiqMiddleware
|
|
19
|
+
# Registers the bundled middleware with Sidekiq:
|
|
20
|
+
# - Client on the client chain (web/console enqueues a job)
|
|
21
|
+
# - Client on the server's client chain (a running job enqueues another)
|
|
22
|
+
# - Server on the server chain (a worker runs a job)
|
|
23
|
+
#
|
|
24
|
+
# Idempotent: Sidekiq's Chain#add replaces an existing entry, so calling
|
|
25
|
+
# this twice (e.g. auto-registration plus a stray manual call) won't
|
|
26
|
+
# duplicate the middleware. `sidekiq` defaults to the ::Sidekiq constant
|
|
27
|
+
# and is injectable for testing.
|
|
28
|
+
def self.install!(sidekiq = ::Sidekiq)
|
|
29
|
+
sidekiq.configure_client do |config|
|
|
30
|
+
config.client_middleware { |chain| chain.add(Client) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sidekiq.configure_server do |config|
|
|
34
|
+
config.client_middleware { |chain| chain.add(Client) }
|
|
35
|
+
config.server_middleware { |chain| chain.add(Server) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
20
38
|
end
|
|
21
39
|
end
|
data/lib/logsy/version.rb
CHANGED
data/lib/logsy.rb
CHANGED
|
@@ -32,6 +32,35 @@ module Logsy
|
|
|
32
32
|
Store.tags[key.to_sym] = value&.to_s
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# Set several tags at once. Keyword sugar over repeated `Logsy[]=`,
|
|
36
|
+
# so the same String coercion applies. Returns the given tags.
|
|
37
|
+
#
|
|
38
|
+
# Logsy.tag(user_id: user.id, order_id: order.id)
|
|
39
|
+
def tag(**tags)
|
|
40
|
+
tags.each { |key, value| self[key] = value }
|
|
41
|
+
tags
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Set tags for the duration of the block, then restore the previous
|
|
45
|
+
# values (deleting keys that were unset before). Use to scope context
|
|
46
|
+
# to a unit of work without leaking it to whatever runs next on the
|
|
47
|
+
# same thread/fiber — handy in service objects, rake tasks, or threads
|
|
48
|
+
# the Rails executor / Sidekiq middleware don't reset for you.
|
|
49
|
+
#
|
|
50
|
+
# Logsy.with(order_id: order.id) do
|
|
51
|
+
# process(order) # every log line here carries order_id
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# Returns the block's value.
|
|
55
|
+
def with(**tags)
|
|
56
|
+
keys = tags.keys.map(&:to_sym)
|
|
57
|
+
saved = keys.to_h { |key| [key, Store.tags[key]] }
|
|
58
|
+
tag(**tags)
|
|
59
|
+
yield
|
|
60
|
+
ensure
|
|
61
|
+
saved.each { |key, value| value.nil? ? Store.tags.delete(key) : Store.tags[key] = value }
|
|
62
|
+
end
|
|
63
|
+
|
|
35
64
|
# All tags currently set, as a hash. Used by the formatter on every
|
|
36
65
|
# log line; consumers can call it for debugging.
|
|
37
66
|
def tags
|