lumberjack_sidekiq 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: af05f3d7358c3375820ed3e3d4b09ea6830de363c6ce38600236a873002d297d
4
+ data.tar.gz: 69e09c53f370c63009e5612add2d3f471ac1e3bb134dbd94e35a2580f36a3bf8
5
+ SHA512:
6
+ metadata.gz: 4ae4f2b993a862c4eca32d4634412fdd9c6178d06698d67fd29038de73ca623528053aad65a04e6407196bb2cc120b65aa23620cceb1a070d14547bbc97b33cb
7
+ data.tar.gz: 92392ec7a0b70eaac5cd20b8bc71c864dd517c49ee91d0dbaf3890dae58f42c2bef06668b883a1add2f98e221bc7282c0b868f0e72d04649168b057eb1f4b531
@@ -0,0 +1,12 @@
1
+ # Dependabot update strategy
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: bundler
5
+ directory: "/"
6
+ schedule:
7
+ interval: weekly
8
+ allow:
9
+ # Automatically keep all runtime dependencies updated
10
+ - dependency-name: "*"
11
+ dependency-type: "production"
12
+ versioning-strategy: lockfile-only
@@ -0,0 +1,47 @@
1
+ name: Continuous Integration
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ - actions-*
8
+ tags:
9
+ - v*
10
+ pull_request:
11
+ branches-ignore:
12
+ - actions-*
13
+ workflow_dispatch:
14
+
15
+ env:
16
+ BUNDLE_CLEAN: "true"
17
+ BUNDLE_PATH: vendor/bundle
18
+ BUNDLE_JOBS: 3
19
+ BUNDLE_RETRY: 3
20
+
21
+ jobs:
22
+ build:
23
+ name: ${{ matrix.ruby }} build
24
+ runs-on: ubuntu-latest
25
+ strategy:
26
+ matrix:
27
+ include:
28
+ - ruby: "ruby"
29
+ standardrb: true
30
+ - ruby: "3.0"
31
+ appraisal: "sidekiq_8"
32
+ - ruby: "2.7"
33
+ appraisal: "sidekiq_7"
34
+ steps:
35
+ - uses: actions/checkout@v2
36
+ - name: Set up Ruby
37
+ uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby}}
40
+ - name: Install gems
41
+ run: |
42
+ bundle install
43
+ - name: Run Tests
44
+ run: bundle exec rake
45
+ - name: standardrb
46
+ if: matrix.standardrb
47
+ run: bundle exec standardrb
data/.standard.yml ADDED
@@ -0,0 +1,8 @@
1
+ ruby_version: 2.7
2
+
3
+ format: progress
4
+
5
+ ignore:
6
+ - 'spec/**/*':
7
+ - Lint/UselessAssignment
8
+ - Lint/Void
data/CHANGE_LOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 1.0.0
8
+
9
+ ### Added
10
+
11
+ - Initial release
data/MIT_LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2025 Brian Durand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Lumberjack Sidekiq
2
+
3
+ [![Continuous Integration](https://github.com/bdurand/lumberjack_sidekiq/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/lumberjack_sidekiq/actions/workflows/continuous_integration.yml)
4
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
+ [![Gem Version](https://badge.fury.io/rb/lumberjack_sidekiq.svg)](https://badge.fury.io/rb/lumberjack_sidekiq)
6
+
7
+ This gem provides an enhanced logging setup for [Sidekiq](https://github.com/mperham/sidekiq) using the [lumberjack](https://github.com/bdurand/lumberjack) structured logging framework. It replaces Sidekiq's default job logging behavior with one that provides rich structured logging with automatic tagging, timing information, and context propagation.
8
+
9
+ **Key Features:**
10
+
11
+ - **Structured Job Logging**: Automatically adds structured tags for job metadata (class, job ID, queue, duration, etc.)
12
+ - **Context Propagation**: Pass log tags from client to server to maintain request context across job execution
13
+ - **Flexible Configuration**: Control logging behavior per job with options for log levels, argument filtering, and custom tags
14
+ - **Performance Tracking**: Automatic timing of job execution and queue wait times
15
+
16
+ ## Usage
17
+
18
+ ### Job Logger
19
+
20
+ The `Lumberjack::Sidekiq::JobLogger` provides structured logging for Sidekiq jobs with automatic tagging and timing information.
21
+
22
+ To use it, configure Sidekiq to use the Lumberjack job logger:
23
+
24
+ ```ruby
25
+ require 'lumberjack_sidekiq'
26
+
27
+ # Firat you'll need a Lumberjack logger instance
28
+ logger = Lumberjack::Logger.new(STDOUT)
29
+
30
+ # Configure Sidekiq to use Lumberjack
31
+ Sidekiq.configure_server do |config|
32
+ config.logger = logger
33
+ config[:job_logger] = Lumberjack::Sidekiq::JobLogger
34
+ end
35
+ ```
36
+
37
+ The job logger automatically adds structured tags to your log entries:
38
+
39
+ - `class` - The worker class name
40
+ - `jid` - The job ID
41
+ - `bid` - The batch ID (if using Sidekiq batch)
42
+ - `queue` - The queue name
43
+ - `duration` - Job execution time in seconds
44
+ - `enqueued_ms` - Time the job was queued before execution
45
+ - `retry_count` - Number of retries (if > 0)
46
+ - `tags` - Any custom Sidekiq tags
47
+
48
+ You can add an optional prefix to all tags:
49
+
50
+ ```ruby
51
+ Sidekiq.configure_server do |config|
52
+ config[:log_tag_prefix] = "sidekiq."
53
+ end
54
+ ```
55
+
56
+ ### Tag Passthrough Middleware
57
+
58
+ The `Lumberjack::Sidekiq::TagPassthroughMiddleware` allows you to pass log tags from the client (where jobs are enqueued) to the server (where jobs are executed). This is useful for maintaining context like user IDs or request IDs across the job execution.
59
+
60
+ Configure the middleware on the client side:
61
+
62
+ ```ruby
63
+ Sidekiq.configure_client do |config|
64
+ config.client_middleware do |chain|
65
+ # Pass through :user_id and :request_id tags to the job logger
66
+ chain.add(Lumberjack::Sidekiq::TagPassthroughMiddleware, :user_id, :request_id)
67
+ end
68
+ end
69
+ ```
70
+
71
+ Now when you enqueue a job with those tags in the current logging context, they will be propagated to the logs when the job runs.
72
+
73
+ ```ruby
74
+ logger.tag(user_id: 123, request_id: "abc-def") do
75
+ MyWorker.perform_async(params)
76
+ end
77
+ ```
78
+
79
+ ### Adding Additional Metadata
80
+
81
+ You can add additional metadata to your job logs by adding your own server middleware. Job logging sets up a tag context so any tags you add in your middleware will be included in the job log when it finishes.
82
+
83
+ Tags added before the `yield` in your middleware will be included in all logs for the job processing. Tags added after the `yield` will only be included in the final final job lifecycle event log.
84
+
85
+ ```ruby
86
+ class MyLogTaggingMiddleware
87
+ include Sidekiq::ServerMiddleware
88
+
89
+ def call(worker, job, queue)
90
+ # Add tag_1 to all logs for this job.
91
+ Sidekiq.logger.tag(tag_1: job["value_1"]) if Sidekiq.logger.is_a?(Lumberjack::Logger)
92
+
93
+ yield
94
+
95
+ # Add tag_2 only to the final job log only.
96
+ Sidekiq.logger.tag(tag_2: job["value_2"]) if Sidekiq.logger.is_a?(Lumberjack::Logger)
97
+ end
98
+ end
99
+
100
+ Sidekiq.configure_server do |config|
101
+ config.server_middleware do |chain|
102
+ chain.add MyLogTaggingMiddleware
103
+ end
104
+ end
105
+ ```
106
+
107
+ ### Job-Level Logging Options
108
+
109
+ You can control logging behavior on a per-job basis by setting logging options:
110
+
111
+ ```ruby
112
+ class MyWorker
113
+ include Sidekiq::Worker
114
+
115
+ sidekiq_options logging: {
116
+ level: "warn", # Set log level for this job
117
+ skip: false, # Skip logging lifecycle events for this job
118
+ skip_start: true, # Skip the "Start job" lifecycle log message
119
+ args: ["param1"], # Only log specific arguments by name; can specify false to omit all args
120
+ tags: {custom: "value"} # Add custom tags to job logs
121
+ }
122
+
123
+ def perform(param1, param2)
124
+ # Your job logic here
125
+ end
126
+ end
127
+ ```
128
+
129
+ ### Configuration Options
130
+
131
+ You can globally disable logging job start events by setting `:skip_start_job_logging` to `true` in the Sidekiq configuration.
132
+
133
+ ```ruby
134
+ Sidekiq.configure_server do |config|
135
+ config[:skip_start_job_logging] = true
136
+ end
137
+ ```
138
+
139
+ You can add a prefix to all automatically generated log tags by setting `:log_tag_prefix`.
140
+
141
+ ```ruby
142
+ Sidekiq.configure_server do |config|
143
+ config[:log_tag_prefix] = "sidekiq."
144
+ end
145
+ ```
146
+
147
+ You can disable logging the enqueued time by setting `:skip_enqueued_time_logging` to `true`.
148
+
149
+ ```ruby
150
+ Sidekiq.configure_server do |config|
151
+ config[:skip_enqueued_time_logging] = true
152
+ end
153
+ ```
154
+
155
+ You can disable logging any job arguments by setting `:skip_logging_job_arguments` to `true`.
156
+
157
+ ```ruby
158
+ Sidekiq.configure_server do |config|
159
+ config[:skip_logging_job_arguments] = true
160
+ end
161
+ ```
162
+
163
+ You can customize the message format by implementing your own `Lumberjack::Sidekiq::MessageFormatter` and setting it in the configuration. You can use this if you existing log processing pipeline is expecting specific message formats.
164
+
165
+ ```ruby
166
+ Sidekiq.configure_server do |config|
167
+ config[:job_logger_message_formatter] = MyCustomMessageFormatter.new(config)
168
+ end
169
+ ```
170
+
171
+ ## Installation
172
+
173
+ Add this line to your application's Gemfile:
174
+
175
+ ```ruby
176
+ gem "lumberjack_sidekiq"
177
+ ```
178
+
179
+ And then execute:
180
+ ```bash
181
+ $ bundle install
182
+ ```
183
+
184
+ Or install it yourself as:
185
+ ```bash
186
+ $ gem install lumberjack_sidekiq
187
+ ```
188
+
189
+ ## Contributing
190
+
191
+ Open a pull request on GitHub.
192
+
193
+ Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
194
+
195
+ ## License
196
+
197
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a replacement for Sidekiq's built in JobLogger. Like the built in JobLogger, it
4
+ # will log job lifecycle events (start, end, failure) with timing information and job metadata.
5
+ # It the standard metadata for jobs:
6
+ # - Job class name
7
+ # - Job ID
8
+ # - Duration of job execution
9
+ # - Tags from the current Sidekiq context
10
+ #
11
+ # It will also include additional metadata:
12
+ # - Queue name
13
+ # - Retry count
14
+ # - Enqueued time in milliseconds (if available)
15
+ #
16
+ # Log messages will also include more information to be human readable include the jog arguments:
17
+ #
18
+ # Finished Sidekiq job MyWorker.perform("foo", 12)`
19
+ #
20
+ # You can specify at the worker level if you want to suppress arguments with the `logging => args` option:
21
+ #
22
+ # sidekiq_options logging: {args: [:arg1]} # only `arg1` will appear in the logs
23
+ #
24
+ # @example
25
+ # Sidekiq.configure_server do |config|
26
+ # config.logger = Lumberjack::Sidekiq::JobLogger.new(config)
27
+ # end
28
+ class Lumberjack::Sidekiq::JobLogger
29
+ def initialize(config)
30
+ @config = config
31
+ @logger = @config.logger
32
+ @prefix = @config[:log_tag_prefix] || ""
33
+ @message_formatter = @config[:job_logger_message_formatter] || Lumberjack::Sidekiq::MessageFormatter.new(@config)
34
+ end
35
+
36
+ def call(job, _queue)
37
+ enqueued_time = enqueued_time_ms(job) unless skip_enqueued_time_logging?
38
+ begin
39
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
40
+ log_start_job(job) unless skip_start_job_logging?(job)
41
+
42
+ yield
43
+
44
+ log_end_job(job, start, enqueued_time) unless skip_logging?(job)
45
+ rescue Exception => err # rubocop:disable Lint/RescueException
46
+ log_failed_job(job, err, start, enqueued_time) unless skip_logging?(job)
47
+
48
+ raise
49
+ end
50
+ end
51
+
52
+ # If true don't log the start of the job.
53
+ def skip_start_job_logging?(job)
54
+ return true if @config[:skip_start_job_logging]
55
+ return true if skip_logging?(job)
56
+
57
+ logging_options = job["logging"]
58
+ return false unless logging_options.is_a?(Hash)
59
+
60
+ !!logging_options["skip_start"]
61
+ end
62
+
63
+ def skip_logging?(job)
64
+ logging_options = job["logging"]
65
+ return false unless logging_options.is_a?(Hash)
66
+
67
+ !!logging_options["skip"]
68
+ end
69
+
70
+ def skip_enqueued_time_logging?
71
+ @config[:skip_enqueued_time_logging] || false
72
+ end
73
+
74
+ def prepare(job, &block)
75
+ return yield unless @logger.is_a?(Lumberjack::Logger)
76
+
77
+ tags = {
78
+ "#{@prefix}class" => worker_class(job),
79
+ "#{@prefix}jid" => job["jid"]
80
+ }
81
+ tags["#{@prefix}bid"] = job["bid"] if job.include?("bid")
82
+ tags["#{@prefix}tags"] = job["tags"] if job.include?("tags")
83
+
84
+ persisted_tags = passthrough_tags(job)
85
+ tags.merge!(persisted_tags) if persisted_tags.is_a?(Hash)
86
+
87
+ @logger.tag(tags) do
88
+ level = job.dig("logging", "level") || job["log_level"]
89
+ if level
90
+ @logger.silence(level, &block)
91
+ else
92
+ yield
93
+ end
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def log_start_job(job)
100
+ message = @message_formatter.start_job(job)
101
+ if @logger.is_a?(Lumberjack::Logger)
102
+ tags = job_tags(job)
103
+ @logger.info(message, tags)
104
+ else
105
+ @logger.info(message)
106
+ end
107
+ end
108
+
109
+ def log_end_job(job, start, enqueued_time)
110
+ message = @message_formatter.end_job(job, elapsed_time(start))
111
+ if @logger.is_a?(Lumberjack::Logger)
112
+ tags = job_tags(job)
113
+ tags["#{@prefix}duration"] = elapsed_time(start)
114
+ tags["#{@prefix}enqueued_ms"] = enqueued_time if enqueued_time
115
+ @logger.info(message, tags)
116
+ else
117
+ @logger.info(message)
118
+ end
119
+ end
120
+
121
+ def log_failed_job(job, err, start, enqueued_time)
122
+ message = @message_formatter.failed_job(job, err, elapsed_time(start))
123
+ if @logger.is_a?(Lumberjack::Logger)
124
+ tags = job_tags(job)
125
+ tags["#{@prefix}duration"] = elapsed_time(start)
126
+ tags["#{@prefix}enqueued_ms"] = enqueued_time if enqueued_time
127
+ @logger.error(message, tags)
128
+ else
129
+ @logger.error(message)
130
+ end
131
+ end
132
+
133
+ def elapsed_time(start)
134
+ (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(6)
135
+ end
136
+
137
+ def enqueued_time_ms(job)
138
+ enqueued_at = job["enqueued_at"]
139
+ return nil unless enqueued_at.is_a?(Numeric)
140
+
141
+ # Older versions of Sidekiq stored the time as the number of seconds in a float.
142
+ # As of Sidekiq 8 it is stored as an integer in milliseconds.
143
+ enqueued_at = (enqueued_at * 1000).round if enqueued_at.is_a?(Float)
144
+ enqueued_ms = ((Time.now.to_f * 1000) - enqueued_at).round
145
+ enqueued_ms = 0 if enqueued_ms < 0
146
+ enqueued_ms
147
+ end
148
+
149
+ def job_tags(job)
150
+ tags = {}
151
+
152
+ retry_count = job["retry_count"]
153
+ tags["#{@prefix}retry_count"] = retry_count if retry_count && retry_count > 0
154
+
155
+ tags["#{@prefix}queue"] = job["queue"] if job["queue"]
156
+
157
+ ::Sidekiq::Context.current&.each do |tag, value|
158
+ tags["#{@prefix}#{tag}"] = value
159
+ end
160
+
161
+ tags
162
+ end
163
+
164
+ def worker_class(job)
165
+ job["display_class"] || job["wrapped"] || job["class"]
166
+ end
167
+
168
+ def passthrough_tags(job)
169
+ job.dig("logging", "tags")
170
+ end
171
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack::Sidekiq
4
+ # This class formats log messages for Sidekiq jobs. Out of the box it will log messages like these:
5
+ #
6
+ # - Start Sidekiq job MyWorker.perform("foo", 12)
7
+ # - Finished Sidekiq job MyWorker.perform("foo", 12) in 123.4ms
8
+ # - Failed Sidekiq job MyWorker.perform("foo", 12) due to RuntimeError in 123.4ms
9
+ #
10
+ # You can control the arguments that are logged by setting the `logging.args` option in your worker:
11
+ #
12
+ # sidekiq_options logging: {args: [:arg1]} # only `arg1` will appear in the logs
13
+ # sidekiq_options logging: {args: false} # no arguments will appear in the logs
14
+ #
15
+ # Argument logging can be disabled globally by setting the `skip_logging_job_arguments` option in your
16
+ # Sidekiq configuration.
17
+ #
18
+ # You can override this class or provide your own implementation that implements the `start_job`,
19
+ # `end_job`, and `failed_job` methods and set it in your Sidekiq configuration:
20
+ #
21
+ # Sidekiq.configure_server do |config|
22
+ # config.job_logger_message_formatter = MyCustomFormatter.new(config)
23
+ # end
24
+ class MessageFormatter
25
+ # @param config [::Sidekiq::Config] The Sidekiq configuration.
26
+ def initialize(config)
27
+ @config = config
28
+ end
29
+
30
+ # Formats the start job message.
31
+ #
32
+ # @param job [Hash] The job data.
33
+ # @return [String] The formatted start job message.
34
+ def start_job(job)
35
+ "Start Sidekiq job #{job_info(job)}"
36
+ end
37
+
38
+ # Formats the end job message.
39
+ #
40
+ # @param job [Hash] The job data.
41
+ # @param elapsed_time [Float] The elapsed time in seconds.
42
+ # @return [String] The formatted end job message.
43
+ def end_job(job, elapsed_time)
44
+ "Finished Sidekiq job #{job_info(job)} in #{(elapsed_time * 1000).round(1)}ms"
45
+ end
46
+
47
+ # Formats the failed job message.
48
+ #
49
+ # @param job [Hash] The job data.
50
+ # @param error [Exception] The exception that was raised.
51
+ # @param elapsed_time [Float] The elapsed time in seconds.
52
+ # @return [String] The formatted failed job message.
53
+ def failed_job(job, error, elapsed_time)
54
+ "Failed Sidekiq job #{job_info(job)} due to #{error.class.name} in #{(elapsed_time * 1000).round(1)}ms"
55
+ end
56
+
57
+ # Helper method to get the method called on the job worker and format the arguments.
58
+ #
59
+ # @param job [Hash] The job data.
60
+ # @return [String] The formatted job information.
61
+ # @note If `skip_logging_job_arguments?` is true, it will only return the worker class name.
62
+ def job_info(job)
63
+ return worker_class(job) if skip_logging_job_arguments?
64
+
65
+ display_args = job_display_args(job)
66
+ "#{worker_class(job)}.perform(#{display_args.join(", ")})"
67
+ end
68
+
69
+ # Helper method to get the job arguments for logging. The return value is an array
70
+ # of strings representing the inspect of each argument (i.e. `["foo", 12]` will be
71
+ # returned as `['"foo"'', '12']`).
72
+ #
73
+ # Arguments can be filtered by the `logging.args` option in the worker sidekiq options.
74
+ #
75
+ # @param job [Hash] The job data.
76
+ # @return [Array<String>] The formatted job arguments.
77
+ def job_display_args(job)
78
+ logger_options = job["logging"] || {}
79
+ args_filter = logger_options["args"]
80
+ args = job["args"]
81
+ return [] if args.nil?
82
+ return args.collect(&:inspect) if args_filter == true || args_filter.nil?
83
+
84
+ if args_filter == false
85
+ ["..."]
86
+ else
87
+ args_filter = Array(args_filter)
88
+ filtered_args(job, args, args_filter)
89
+ end
90
+ end
91
+
92
+ # Returns true of job arguments should never be logged.
93
+ #
94
+ # @return [Boolean] True if job arguments should not be logged.
95
+ def skip_logging_job_arguments?
96
+ @config[:skip_logging_job_arguments] || false
97
+ end
98
+
99
+ # Helper method to get the job worker class name. If the job has a `display_class` or `wrapped` key,
100
+ # it will return that value for logging purposes.
101
+ # #
102
+ # @param job [Hash] The job data.
103
+ # @return [String] The worker class name.
104
+ def worker_class(job)
105
+ job["display_class"] || job["wrapped"] || job["class"]
106
+ end
107
+
108
+ private
109
+
110
+ def filtered_args(job, args, args_filter)
111
+ class_name = job["wrapped"] || job["class"]
112
+ klass = Object.const_get(class_name) if class_name && Object.const_defined?(class_name)
113
+ return ["..."] unless klass.is_a?(Class)
114
+ return ["..."] unless klass.instance_methods.include?(:perform)
115
+
116
+ perform_args = klass.instance_method(:perform).parameters
117
+ args.each_with_index.map do |arg, index|
118
+ arg_name = perform_args[index][1] if perform_args[index]
119
+ if args_filter.include?(arg_name.to_s)
120
+ arg.inspect
121
+ else
122
+ "-"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ # Sidekiq client middleware that can pass through log tags from the current Lumberjack
6
+ # logger to job logger when the job is executed on the Sidekiq server. This can be
7
+ # useful to maintain context in logs when a job is executed.
8
+ #
9
+ # @example
10
+ # Sidekiq.configure_client do |config|
11
+ # config.client_middleware do |chain|
12
+ # # Pass through :user_id and :request_id tags to the job logger.
13
+ # chain.add(Lumberjack::Sidekiq::TagPassthroughMiddleware, :user_id, :request_id)
14
+ # end
15
+ # end
16
+ class Lumberjack::Sidekiq::TagPassthroughMiddleware
17
+ include ::Sidekiq::ClientMiddleware
18
+
19
+ JSON_SAFE_TYPES = [String, Integer, Float, TrueClass, FalseClass].freeze
20
+
21
+ # @param pass_through_tags [Array<String, Symbol>] Log tags to pass through to the job logger when the job is executed.
22
+ def initialize(*pass_through_tags)
23
+ @pass_through_tags = pass_through_tags.flatten.map(&:to_s)
24
+ end
25
+
26
+ def call(job_class_or_string, job, queue, redis_pool)
27
+ return yield unless Sidekiq.logger.is_a?(Lumberjack::Logger)
28
+
29
+ job["logging"] ||= {}
30
+ tags = job["logging"]["tags"] || {}
31
+
32
+ @pass_through_tags.each do |tag|
33
+ value = json_value(Sidekiq.logger.tag_value(tag))
34
+ tags[tag] = value unless value.nil?
35
+ end
36
+
37
+ job["logging"]["tags"] = tags unless tags.empty?
38
+
39
+ yield
40
+ end
41
+
42
+ private
43
+
44
+ def json_value(value)
45
+ return nil if value.nil?
46
+ return value if JSON_SAFE_TYPES.include?(value.class)
47
+
48
+ begin
49
+ JSON.parse(JSON.generate(value))
50
+ rescue JSON::JSONError
51
+ nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lumberjack"
4
+ require "sidekiq"
5
+
6
+ module Lumberjack::Sidekiq
7
+ end
8
+
9
+ require_relative "sidekiq/job_logger"
10
+ require_relative "sidekiq/message_formatter"
11
+ require_relative "sidekiq/tag_passthrough_middleware"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lumberjack/sidekiq"
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "lumberjack_sidekiq"
3
+ spec.version = File.read(File.join(__dir__, "VERSION")).strip
4
+ spec.authors = ["Brian Durand"]
5
+ spec.email = ["bbdurand@gmail.com"]
6
+
7
+ spec.summary = "Structured logging for Sidekiq jobs using the Lumberjack framework with automatic tagging, timing, and context propagation."
8
+ spec.homepage = "https://github.com/bdurand/lumberjack_sidekiq"
9
+ spec.license = "MIT"
10
+
11
+ # Specify which files should be added to the gem when it is released.
12
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
13
+ ignore_files = %w[
14
+ .gitignore
15
+ .travis.yml
16
+ Appraisals
17
+ Gemfile
18
+ Gemfile.lock
19
+ Rakefile
20
+ gemfiles/
21
+ spec/
22
+ ]
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
25
+ end
26
+
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.required_ruby_version = ">= 2.7"
30
+
31
+ spec.add_dependency "lumberjack", ">=1.3"
32
+ spec.add_dependency "sidekiq", ">=7.0"
33
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lumberjack_sidekiq
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Durand
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lumberjack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sidekiq
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ description:
42
+ email:
43
+ - bbdurand@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".github/dependabot.yml"
49
+ - ".github/workflows/continuous_integration.yml"
50
+ - ".standard.yml"
51
+ - CHANGE_LOG.md
52
+ - MIT_LICENSE.txt
53
+ - README.md
54
+ - VERSION
55
+ - lib/lumberjack/sidekiq.rb
56
+ - lib/lumberjack/sidekiq/job_logger.rb
57
+ - lib/lumberjack/sidekiq/message_formatter.rb
58
+ - lib/lumberjack/sidekiq/tag_passthrough_middleware.rb
59
+ - lib/lumberjack_sidekiq.rb
60
+ - lumberjack_sidekiq.gemspec
61
+ homepage: https://github.com/bdurand/lumberjack_sidekiq
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '2.7'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.4.10
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Structured logging for Sidekiq jobs using the Lumberjack framework with automatic
84
+ tagging, timing, and context propagation.
85
+ test_files: []