Sidekiq
When LogStruct is on and Sidekiq is loaded, Sidekiq's logger is a LogStruct logger, so Sidekiq's own lines and your Sidekiq.logger calls are JSON lines. In the Sidekiq process, every line logged while a job runs has the job's ID and class in ctx, and a failed job is one warn line with the error instead of Sidekiq's three text lines.
What LogStruct changes
- The logger. LogStruct replaces Sidekiq's logger as soon as it replaces Rails' logger, before
config/initializersrun, so the lines Sidekiq logs while an initializer configures it are JSON too. The logger is namedSidekiq-Serverin the Sidekiq process andSidekiq-Clienteverywhere else, and the name is written asprog. - The job context. In the Sidekiq process, LogStruct adds a server middleware at the front of the chain. While a job runs, every line (from
Rails.logger,Sidekiq.loggeror any other SemanticLogger logger) gets the job'sjidandclassinctx, so a query onctx.jidreturns every line of one job run. For an ActiveJob job,classis the job's own class, not Sidekiq's wrapper. - The error handler. In the Sidekiq process, LogStruct replaces Sidekiq's default error handler with one that writes a structured line. Handlers that your app or an error tracker added stay and run as before.
Lines
Sidekiq's job lines
Sidekiq logs start when it starts a job, and done or fail when the job ends. Each is a sidekiq log line with Sidekiq's own job context in ctx; done and fail add elapsed, in seconds.
{
"src": "sidekiq",
"evt": "log",
"lvl": "info",
"ts": "2026-10-17T03:21:00.200+00:00",
"msg": "done",
"ctx": {
"class": "InvoiceJob",
"jid": "b4a577edbccf1d805744efa9",
"elapsed": 0.001
},
"pid": 70624,
"tid": "1fu8",
"prog": "Sidekiq-Server"
}Lines logged during a job
Rails.logger.info("Rendering invoice PDF") inside the job writes:
{
"src": "app",
"evt": "log",
"lvl": "info",
"ts": "2026-10-17T03:21:00.199+00:00",
"msg": "Rendering invoice PDF",
"prog": "Rails",
"ctx": {
"jid": "b4a577edbccf1d805744efa9",
"class": "InvoiceJob"
}
}A Hash logged with its own ctx keeps its keys and gains the job's.
Sidekiq.logger calls
A String passed to Sidekiq.logger is a sidekiq log line with the message in msg, Sidekiq's context in ctx, and the process and thread IDs. A Hash, a LogStruct log struct or an exception is logged as it is, like on Rails.logger: a Hash's keys are the line's fields and are filtered like any other Hash, and its src is app unless the Hash sets one. Sidekiq logs some of its settings as a Hash, so their keys are filtered too. This line came from Sidekiq.logger.info({msg: "Synced invoice", invoice_id: 42, api_token: token}) during a job:
{
"msg": "Synced invoice",
"invoice_id": 42,
"api_token": {
"_filtered": {
"_class": "String"
}
},
"src": "app",
"evt": "log",
"ts": "2026-10-17T03:21:00.200+00:00",
"lvl": "info",
"prog": "Sidekiq-Server",
"ctx": {
"jid": "b4a577edbccf1d805744efa9",
"class": "InvoiceJob"
}
}Failed jobs
When a job raises, Sidekiq calls its error handlers. LogStruct's handler writes one sidekiq error line at warn level, the level Sidekiq's default handler uses. It doesn't include the job's arguments: Sidekiq's handler logged the whole job, arguments included, as a JSON string, which LogStruct can't filter.
{
"src": "sidekiq",
"evt": "error",
"lvl": "warn",
"ts": "2026-10-17T03:21:00.205+00:00",
"error_class": "ArgumentError",
"msg": "Template not found",
"backtrace": [
"app/sidekiq/invoice_job.rb:9:in 'InvoiceJob#perform'"
],
"error_context": "Job raised exception",
"queue_name": "default",
"retry_count": 2,
"prog": "Rails",
"ctx": {
"jid": "2f1c9e0d7a6b5c4d3e2f1a0b",
"class": "InvoiceJob"
}
}- Errors Sidekiq reports outside a job (from its fetcher, for example) are the same line without the job's fields.
error_contextsays what Sidekiq was doing. - For an ActiveJob job, the ActiveJob integration logs the error as a
joberrorline with the job's ID and arguments, and this line is skipped for that error. It's still written for errors raised before ActiveJob ran the job, such as a job class that a deploy removed. - LogStruct replaces only Sidekiq's default handler. If your app already removed it, LogStruct doesn't add its own.
Fields
sidekiq log lines (a String logged with Sidekiq.logger):
| Field | Value |
|---|---|
| msg | The message. |
| ctx | Sidekiq's context: class, jid, and elapsed (seconds) on done and fail. Left out outside a job. |
| pid | The process ID. |
| tid | Sidekiq's thread ID, as Sidekiq writes it in its own logs. |
| prog | Sidekiq-Server or Sidekiq-Client. |
sidekiq error lines:
| Field | Value |
|---|---|
| error_class | The exception's class. |
| msg | The exception's message. |
| backtrace | The backtrace, up to max_backtrace_lines (default 20). |
| error_context | What Sidekiq was doing, e.g. Job raised exception. |
| queue_name | The job's queue. |
| retry_count | The job's retry_count when Sidekiq fetched it: not there on the first run, 0 on the first retry. |
| ctx | The job's jid and class. |
Sidekiq versions
- Sidekiq 7: everything on this page. LogStruct's tests run against Sidekiq 7.
- Sidekiq 6: LogStruct sets the logger on the
Sidekiqmodule and adds the job context. Sidekiq 6 has noSidekiq::Config::ERROR_HANDLER, so its own error handler stays: a failed job is threewarnlines (the job as a JSON string with its arguments, the error, and the backtrace), written through LogStruct's logger. - Sidekiq 8 isn't tested.
Settings
config.integrations.enable_sidekiq (default true). With it off, or with LogStruct off, Sidekiq keeps its own logger and error handler. If an initializer turns either off after LogStruct replaced the logger, LogStruct puts Sidekiq's logger back. See Configuration for the other settings.
LogStruct.configure do |config|
# On by default. false keeps Sidekiq's own logger and error handler.
config.integrations.enable_sidekiq = false
endLimits
- Job arguments aren't in any Sidekiq line.
- The job context is added in the Sidekiq process only. Lines logged where a job is enqueued don't have its
jid. - LogStruct writes no line of its own when Sidekiq schedules a retry; the error line's
retry_countsays which run failed.
ctx included, are filtered and scrubbed as described in Filtering Sensitive Data.