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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d9d026e66871c418c7315bbec120182f0aa3a2e4caa247e9fa533a4a6b99361
4
- data.tar.gz: caf2ad615f13cd2f75954e43dfcab5d61e93ec40b11c004bf9723bd8c4736721
3
+ metadata.gz: be9d1c01334a5549c684e6e0807eb2f0b1b00f7d481b0862a0d0d005de31b81f
4
+ data.tar.gz: 993aadd7127a75825114c9cadb6db4ef211fcd1e582320661b73ba483653aed9
5
5
  SHA512:
6
- metadata.gz: 9bed718fb7a7618cd420220579e7edd7ce92b44d46f538a0ef2459644014fc207febe0c09cc51a3b72624d083930dc78a37e00752bab2ef6fd34f4291c6677f0
7
- data.tar.gz: 2e2d8bd7a250600dedbf8e704e87227cd5ff590c8f9fee39276a1a909b8b1973d51db5f09e6a415ccd375a5222b9bef87039958e80a9c44b3718d4c5724c7d05
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
 
@@ -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
- # Inserts the request-id capturing middleware right after
5
- # ActionDispatch::RequestId, before Rails::Rack::Logger — early enough
6
- # that the "Started GET ..." line is already tagged.
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`, and resets the store after the job runs (even on
8
- # error) so tags don't leak between jobs sharing a worker thread.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logsy
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/logsy.rb CHANGED
@@ -5,6 +5,7 @@ require 'logsy/store'
5
5
  require 'logsy/configuration'
6
6
  require 'logsy/json_formatter'
7
7
  require 'logsy/controller_hooks'
8
+ require 'logsy/job_hooks'
8
9
  require 'logsy/rack_middleware'
9
10
  require 'logsy/railtie' if defined?(Rails::Railtie)
10
11
 
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.2.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