logsy 0.2.0 → 0.3.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 +8 -0
- data/README.md +51 -1
- data/lib/logsy/configuration.rb +13 -2
- data/lib/logsy/job_hooks.rb +70 -0
- data/lib/logsy/railtie.rb +16 -3
- data/lib/logsy/sidekiq_middleware/server.rb +5 -2
- data/lib/logsy/version.rb +1 -1
- data/lib/logsy.rb +1 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be9d1c01334a5549c684e6e0807eb2f0b1b00f7d481b0862a0d0d005de31b81f
|
|
4
|
+
data.tar.gz: 993aadd7127a75825114c9cadb6db4ef211fcd1e582320661b73ba483653aed9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d48f08e5b3c2f13c14ac9f8d3983c46bbb7361785343912e9840b8652cef8f6d24e3e98726dcf97fa00b1473aa776f9c0c4418add83b5888d60d398f0d081a5
|
|
7
|
+
data.tar.gz: e420eec6c13c97386d41bfbd32d91f54511c137808ce486ce00518a00df09226e6055526e8957baad210903ad71011dfc0f99fd013393cdac747b2f5558b1471
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-06-21
|
|
4
|
+
|
|
5
|
+
- **`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`.
|
|
6
|
+
- **Auto-wiring**: the Railtie now auto-includes `Logsy::JobHooks` into `ActiveJob::Base` (via `ActiveSupport.on_load(:active_job)`), so jobs emit summaries with zero per-app setup. Opt out with `config.auto_include_job_hooks = false` and include it manually. (Non-Rails ActiveJob users include it themselves.)
|
|
7
|
+
- `SidekiqMiddleware::Server`: now also sets `Logsy[:job_class]` (from `job['wrapped']`, the ActiveJob class name, falling back to `job['class']` for plain Sidekiq workers), so every job log line — including ActiveJob's own "Performing ..." — is tagged with the class, not just the `job_id`/jid.
|
|
8
|
+
- `Configuration`: new `job_summary_event_name` (default `"job"`), mirroring `request_summary_event_name`; new `auto_include_job_hooks` (default `true`).
|
|
9
|
+
- **Default `ignored_caller_paths`** now also skips `app/middleware` / `app/middlewares` frames — Rack middlewares sit in every request's stack and would otherwise be picked as the "app frame" for lines emitted below them. (Previously each app had to add this itself.)
|
|
10
|
+
|
|
3
11
|
## [0.2.0] - 2026-06-07
|
|
4
12
|
|
|
5
13
|
- **`Logsy::RackMiddleware` + Railtie**: request_id is now captured in Rack middleware (auto-inserted after `ActionDispatch::RequestId`) instead of a controller `before_action`. Every log line of a request now carries `request_id` — including "Started GET ..." and "Processing by ..." which a controller callback fires too late to tag. Falls back to `X-Request-Id`, then a generated UUID, so the tag is always present.
|
data/README.md
CHANGED
|
@@ -82,10 +82,33 @@ The bundled middleware:
|
|
|
82
82
|
- **Client side**: when a job is enqueued, copies the configured tags from `Logsy[]` into the job payload
|
|
83
83
|
- **Server side**: when the job runs, sets those tags back in `Logsy[]` AND sets `Logsy[:job_id] = job['jid']` automatically
|
|
84
84
|
|
|
85
|
-
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).
|
|
85
|
+
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
86
|
|
|
87
87
|
For other job systems (Resque, GoodJob, custom), write your own middleware using the same pattern — Logsy's `[]=` / `[]` / `tags` / `reset` API is generic.
|
|
88
88
|
|
|
89
|
+
### 6. Job wide event — automatic
|
|
90
|
+
|
|
91
|
+
`Logsy::JobHooks` is the background-job counterpart of `ControllerHooks`. In a Rails app there's **nothing to do**: the Railtie auto-includes it into `ActiveJob::Base`, so every job emits one wide event with `event: "job"` when it finishes, carrying `job_class`, `queue`, `active_job_id`, `executions` (attempt count), `duration_ms`, `status` (`"ok"`/`"error"`), any `error` class/message — plus every tag set during the run (`job_id`/jid, propagated `request_id`, and anything you wrote via `Logsy[:key] = value`). It's emitted even when the job raises (then re-raised). Job arguments are deliberately omitted — ActiveJob already logs them at enqueue time, correlatable by `active_job_id`.
|
|
92
|
+
|
|
93
|
+
Override `logsy_job_summary_extras` in a job to add fields:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
class SomeJob < ApplicationJob
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def logsy_job_summary_extras
|
|
100
|
+
{ tenant: account_id }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
To opt out of the auto-include and wire it yourself:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Logsy.configure { |c| c.auto_include_job_hooks = false }
|
|
109
|
+
# then `include Logsy::JobHooks` in the jobs you want
|
|
110
|
+
```
|
|
111
|
+
|
|
89
112
|
## What it looks like in your logs
|
|
90
113
|
|
|
91
114
|
A regular log line:
|
|
@@ -123,6 +146,25 @@ The wide event at end of request:
|
|
|
123
146
|
}
|
|
124
147
|
```
|
|
125
148
|
|
|
149
|
+
The wide event at end of a job (with `Logsy::JobHooks`):
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"ts":"2026-05-02T10:00:00.250Z",
|
|
154
|
+
"level":"INFO",
|
|
155
|
+
"event":"job",
|
|
156
|
+
"job_class":"SpgReverseTransactionJob",
|
|
157
|
+
"queue":"default",
|
|
158
|
+
"active_job_id":"09fe3546-211b-45cf-9434-fb2689c580c5",
|
|
159
|
+
"executions":1,
|
|
160
|
+
"duration_ms":842.10,
|
|
161
|
+
"status":"ok",
|
|
162
|
+
"job_id":"402797cb0cfc9324cc10cc7f",
|
|
163
|
+
"transaction_id":"019c0440-a797-7479-8d3a-4260ba896681",
|
|
164
|
+
"rrn":"123456789012"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
126
168
|
Search your log store by `message_id` to find every request from that message. Pivot from a request's `request_id` to see every breadcrumb log line emitted while it ran.
|
|
127
169
|
|
|
128
170
|
Notes on the fields:
|
|
@@ -184,6 +226,14 @@ Logsy.configure do |c|
|
|
|
184
226
|
|
|
185
227
|
# The "event" name on the request summary. Default: "request"
|
|
186
228
|
c.request_summary_event_name = 'http_request'
|
|
229
|
+
|
|
230
|
+
# The "event" name on the job summary (Logsy::JobHooks). Default: "job"
|
|
231
|
+
c.job_summary_event_name = 'background_job'
|
|
232
|
+
|
|
233
|
+
# Auto-include Logsy::JobHooks into ActiveJob::Base (the Railtie does this
|
|
234
|
+
# so jobs emit summaries with no setup). Set false to wire it yourself.
|
|
235
|
+
# Default: true
|
|
236
|
+
c.auto_include_job_hooks = true
|
|
187
237
|
end
|
|
188
238
|
```
|
|
189
239
|
|
data/lib/logsy/configuration.rb
CHANGED
|
@@ -8,7 +8,12 @@ module Logsy
|
|
|
8
8
|
%r{/lograge/},
|
|
9
9
|
%r{/sprockets/},
|
|
10
10
|
%r{/quiet_assets/},
|
|
11
|
-
%r{/lib/logsy/}
|
|
11
|
+
%r{/lib/logsy/},
|
|
12
|
+
# Rack middlewares sit in every request's call stack, so without this
|
|
13
|
+
# they'd be picked as the "app frame" for lines emitted below them
|
|
14
|
+
# (e.g. "Started GET ..." would be attributed to a health_check
|
|
15
|
+
# middleware). Matches app/middleware and app/middlewares.
|
|
16
|
+
%r{/app/middlewares?/}
|
|
12
17
|
].freeze
|
|
13
18
|
|
|
14
19
|
# Tag keys that should be carried across job boundaries (e.g. when a
|
|
@@ -17,13 +22,19 @@ module Logsy
|
|
|
17
22
|
# specific (Logsy ships one for Sidekiq).
|
|
18
23
|
attr_accessor :job_propagated_keys, :ignored_caller_paths,
|
|
19
24
|
:include_caller_location, :request_summary_event_name,
|
|
20
|
-
:caller_location_max_depth
|
|
25
|
+
:job_summary_event_name, :caller_location_max_depth,
|
|
26
|
+
:auto_include_job_hooks
|
|
21
27
|
|
|
22
28
|
def initialize
|
|
23
29
|
@job_propagated_keys = [:request_id]
|
|
24
30
|
@ignored_caller_paths = DEFAULT_IGNORED_CALLER_PATHS.dup
|
|
25
31
|
@include_caller_location = true
|
|
26
32
|
@request_summary_event_name = 'request'
|
|
33
|
+
@job_summary_event_name = 'job'
|
|
34
|
+
# When true (the default), the Railtie auto-includes Logsy::JobHooks
|
|
35
|
+
# into ActiveJob::Base, so every job emits a summary wide event with
|
|
36
|
+
# no per-app setup. Set to false to opt out and include it manually.
|
|
37
|
+
@auto_include_job_hooks = true
|
|
27
38
|
# How many stack frames to inspect before giving up on attributing
|
|
28
39
|
# the log line to app code. App frames sit within a few dozen frames
|
|
29
40
|
# of the logger; framework-only lines (no app frame at all) would
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
5
|
+
module Logsy
|
|
6
|
+
# Include into your ApplicationJob (or specific jobs) to emit a single
|
|
7
|
+
# "wide event" log line when the job finishes — carrying the job class,
|
|
8
|
+
# queue, ActiveJob id, attempt count, duration_ms, status/error, and
|
|
9
|
+
# every Logsy tag set during the run (job_id/jid, propagated request_id,
|
|
10
|
+
# and anything the job wrote via `Logsy[:key] = value`, e.g. an RRN).
|
|
11
|
+
#
|
|
12
|
+
# This is the background-job counterpart of Logsy::ControllerHooks.
|
|
13
|
+
# Per-job context capture (job_id, job_class, propagated tags) lives in
|
|
14
|
+
# Logsy::SidekiqMiddleware::Server — not here — so even ActiveJob's own
|
|
15
|
+
# "Performing ..." line is already tagged before this hook runs.
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
#
|
|
19
|
+
# class ApplicationJob < ActiveJob::Base
|
|
20
|
+
# include Logsy::JobHooks
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# Override `logsy_job_summary_extras` to add custom fields to the event:
|
|
24
|
+
#
|
|
25
|
+
# def logsy_job_summary_extras
|
|
26
|
+
# { tenant: account_id }
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# Job arguments are intentionally omitted — ActiveJob already logs them
|
|
30
|
+
# once at enqueue time ("Enqueued ... with arguments: ..."), correlatable
|
|
31
|
+
# by active_job_id, so repeating them here would only duplicate (and risk
|
|
32
|
+
# leaking) payload data.
|
|
33
|
+
module JobHooks
|
|
34
|
+
extend ActiveSupport::Concern
|
|
35
|
+
|
|
36
|
+
included do
|
|
37
|
+
around_perform :_logsy_emit_job_summary
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def _logsy_emit_job_summary
|
|
43
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
44
|
+
error = nil
|
|
45
|
+
yield
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
error = e
|
|
48
|
+
raise
|
|
49
|
+
ensure
|
|
50
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
51
|
+
payload = {
|
|
52
|
+
event: Logsy.configuration.job_summary_event_name,
|
|
53
|
+
job_class: self.class.name,
|
|
54
|
+
queue: queue_name,
|
|
55
|
+
active_job_id: job_id,
|
|
56
|
+
executions:,
|
|
57
|
+
duration_ms:,
|
|
58
|
+
status: error ? 'error' : 'ok',
|
|
59
|
+
error: error && "#{error.class}: #{error.message}"
|
|
60
|
+
}.merge(logsy_job_summary_extras).compact
|
|
61
|
+
|
|
62
|
+
Rails.logger.info(payload)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Override in subclasses to add fields to the job summary wide event.
|
|
66
|
+
def logsy_job_summary_extras
|
|
67
|
+
{}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/logsy/railtie.rb
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Logsy
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Wires Logsy into Rails with zero per-app setup:
|
|
5
|
+
#
|
|
6
|
+
# - Inserts the request-id capturing middleware right after
|
|
7
|
+
# ActionDispatch::RequestId, before Rails::Rack::Logger — early enough
|
|
8
|
+
# that the "Started GET ..." line is already tagged.
|
|
9
|
+
# - Includes Logsy::JobHooks into ActiveJob::Base so every job emits a
|
|
10
|
+
# summary wide event (disable via config.auto_include_job_hooks = false).
|
|
7
11
|
class Railtie < ::Rails::Railtie
|
|
8
12
|
initializer 'logsy.insert_rack_middleware' do |app|
|
|
9
13
|
app.middleware.insert_after(ActionDispatch::RequestId, Logsy::RackMiddleware)
|
|
10
14
|
end
|
|
15
|
+
|
|
16
|
+
initializer 'logsy.include_job_hooks' do
|
|
17
|
+
# Fires when ActiveJob::Base loads (eager load in production, first
|
|
18
|
+
# reference in development) — after config/initializers have run, so
|
|
19
|
+
# an app's auto_include_job_hooks = false is respected.
|
|
20
|
+
ActiveSupport.on_load(:active_job) do
|
|
21
|
+
include(Logsy::JobHooks) if Logsy.configuration.auto_include_job_hooks
|
|
22
|
+
end
|
|
23
|
+
end
|
|
11
24
|
end
|
|
12
25
|
end
|
|
@@ -4,14 +4,17 @@ module Logsy
|
|
|
4
4
|
module SidekiqMiddleware
|
|
5
5
|
# Server-side middleware: reads propagated tags back from the job
|
|
6
6
|
# payload, sets them on the per-job Logsy store, sets job_id from
|
|
7
|
-
# Sidekiq's `jid
|
|
8
|
-
#
|
|
7
|
+
# Sidekiq's `jid` and job_class from the worker (the wrapped ActiveJob
|
|
8
|
+
# class name when run through ActiveJob), and resets the store after the
|
|
9
|
+
# job runs (even on error) so tags don't leak between jobs sharing a
|
|
10
|
+
# worker thread.
|
|
9
11
|
#
|
|
10
12
|
# Anything the job code writes via `Logsy[:foo] = bar` while running
|
|
11
13
|
# will appear on every log line emitted during the job.
|
|
12
14
|
class Server
|
|
13
15
|
def call(_worker_instance, job, _queue)
|
|
14
16
|
Logsy[:job_id] = job['jid']
|
|
17
|
+
Logsy[:job_class] = job['wrapped'] || job['class']
|
|
15
18
|
Logsy.configuration.job_propagated_keys.each do |key|
|
|
16
19
|
value = job[key.to_s]
|
|
17
20
|
Logsy[key] = value if value
|
data/lib/logsy/version.rb
CHANGED
data/lib/logsy.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: logsy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ruby_is_love
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '7.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activejob
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: rake
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,6 +108,7 @@ files:
|
|
|
94
108
|
- lib/logsy.rb
|
|
95
109
|
- lib/logsy/configuration.rb
|
|
96
110
|
- lib/logsy/controller_hooks.rb
|
|
111
|
+
- lib/logsy/job_hooks.rb
|
|
97
112
|
- lib/logsy/json_formatter.rb
|
|
98
113
|
- lib/logsy/rack_middleware.rb
|
|
99
114
|
- lib/logsy/railtie.rb
|