Log Fields

Each line LogStruct writes is one JSON object. This page lists its keys: the ones every line has, the ones LogStruct adds from the logger's context, and the short names of each log type's fields. It ends with an example line for each built-in log type and the formats of the values.

Keys on Every Line

KeyValue
srcWhere the line came from: a LogStruct::Source value such as rails, app, job or mailer, or the src of a Hash you log. Lines without one get app.
evtWhat happened: a LogStruct::Event value such as request, log or error, or the evt of a Hash you log. Lines without one get log.
lvlThe level the line was logged at (see Levels).
tsWhen the line was logged (see Timestamps).
msgThe message, on lines that have one: a string you log, the message field of the log types that have one (see Short Names), or the msg of a Hash you log.
progThe name of the logger that wrote the line: Rails for Rails.logger and the framework loggers LogStruct replaces, Sidekiq-Server or Sidekiq-Client for Sidekiq's own lines and GoodJob for GoodJob's. Lines written without a logger name have no prog.

LogStruct's own log types write src, evt, lvl and ts first, then their fields. A Hash you log keeps its own keys first.

Keys from the Logger's Context

LogStruct adds these keys after the line's own fields. It filters and scrubs them like the rest of the line.

KeyValue
req_idThe request's ID (Rails' request.request_id), on every line logged while Rails handles a request. Blocked host lines don't have it: Rails checks the host before it sets the request ID. A line with its own req_id keeps it.
tagsThe logger's tags, as an array: logger.tagged("billing") and config.log_tags. The config/environments/production.rb that Rails generates sets config.log_tags = [:request_id], so the lines logged during a request in production have the request ID in tags too. A line with its own tags keeps them.
ctxNamed tags (logger.tagged(account_id: 42)) and, while a Sidekiq job runs, the job's jid and class. When a line has its own ctx Hash, its keys win and the other keys are added to it.
Rails.logger.tagged("billing") do
  Rails.logger.tagged(account_id: 42) do
    Rails.logger.info("Charged card")
  end
end
{
  "src": "app",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "Charged card",
  "prog": "Rails",
  "tags": [
    "billing"
  ],
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17",
  "ctx": {
    "account_id": 42
  }
}

Logged Hashes

A Hash you log is written with its keys at the top level of the line. It can set its own src and evt. The timestamp and level come from the logger: a ts or lvl key in the Hash is dropped and handled as a LogStruct error (error_handling_modes.logstruct_errors: raised in local_environments, logged elsewhere).

Rails.logger.info(msg: "Invoice sent", invoice_id: 812, email: "Linda.Martinez@example.com")
{
  "msg": "Invoice sent",
  "invoice_id": 812,
  "email": {
    "_filtered": {
      "_class": "String",
      "_hash": "a058d5090035d2e1"
    }
  },
  "src": "app",
  "evt": "log",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "lvl": "info",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

additional_data

LogStruct's log types take an additional_data Hash for fields the type doesn't have. Its keys are written at the top level of the line, after the type's own fields, not under an additional_data key.

Rails.logger.info(
  LogStruct::Log::Plain.new(
    message: "Imported invoices",
    additional_data: {account_id: 42, rows: 120}
  )
)
{
  "src": "app",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "Imported invoices",
  "account_id": 42,
  "rows": 120,
  "prog": "Rails"
}
  • A key with the same name as one of the type's fields replaces that field: an additional_data of {msg: "..."} replaces the message.
  • src, evt, lvl and ts are reserved. LogStruct drops them and handles the ArgumentError as a LogStruct error (raised in local_environments, logged elsewhere).

Short Names

LogStruct's log types name their fields with LogStruct::LogField, which maps each name to a short JSON key: MailerClass is written as mailer and Database as db. The table lists every field. Log::ActiveJob::* stands for every ActiveJob log type, and some fields aren't written by any built-in log type. At the top of a line, Rails' partial filter_parameters matches don't apply to these keys: Rails 8's default :_key would otherwise filter cron_key.

KeyLogFieldWritten by
acceptAcceptLog::Request
actionActionLog::Request
active_countActiveConnectionsLog::SQL
adapterAdapterLog::ActiveModelSerializers, Log::SQL
addrAddressNo built-in log type
ahoy_eventAhoyEventLog::Ahoy
allow_ip_hostsAllowIpHostsNo built-in log type
allowed_hostsAllowedHostsNo built-in log type
argumentsArgumentsLog::ActiveJob::*, Log::GoodJob::*
attachmentsAttachmentCountLog::ActionMailer::*
attemptAttemptLog::ActiveJob::Start
backtraceBacktraceLog::ActionMailer::Error, Log::ActiveJob::Error, Log::Error, Log::GoodJob::Error, Log::Puma::Error, Log::Sidekiq::Error
bind_paramsBindParamsLog::SQL
blocked_hostBlockedHostLog::Security::BlockedHost
blocked_hostsBlockedHostsLog::Security::BlockedHost
cause_classCauseClassLog::Error
cause_messageCauseMessageLog::Error
checksumChecksumLog::ActiveStorage::Upload
client_ipClientIpLog::Security::IPSpoof
content_typeContentTypeLog::Request
controllerControllerLog::Request
cron_keyCronKeyNo built-in log type
ctxContextLog::Sidekiq::Log
dataDataNo built-in log type
dbDatabaseLog::Request
db_nameDatabaseNameLog::SQL
download_optsDownloadOptionsLog::Shrine::Download
duration_msDurationMsLog::ActiveJob::Finish, Log::ActiveModelSerializers, Log::ActiveStorage::Download, Log::ActiveStorage::Upload, Log::CarrierWave::Upload, Log::GoodJob::Error, Log::Request, Log::SQL, Log::Shrine::Metadata, Log::Shrine::Upload
enqueue_callerEnqueueCallerNo built-in log type
environmentEnvironmentLog::Puma::Start
error_classErrorClassLog::ActionMailer::Error, Log::ActiveJob::Discard, Log::ActiveJob::Error, Log::ActiveJob::Retry, Log::ActiveJob::RetryStopped, Log::Error, Log::GoodJob::Error, Log::Puma::Error, Log::Sidekiq::Error
error_contextErrorContextLog::Puma::Error, Log::Sidekiq::Error
error_messageErrorMessageLog::Error, Log::GoodJob::Error
evtEventEvery log type
exception_executionsExceptionExecutionsLog::GoodJob::Error
execution_timeExecutionTimeNo built-in log type
executionsExecutionsLog::ActiveJob::*, Log::GoodJob::*
existExistLog::ActiveStorage::Exist
extExtensionLog::CarrierWave::*
fileFileLog::Dotenv::Load
file_idFileIdLog::ActiveStorage::*, Log::CarrierWave::*
filenameFilenameLog::CarrierWave::Download, Log::CarrierWave::Upload
finished_atFinishedAtLog::ActiveJob::Finish
formatFormatLog::Request
fromFromLog::ActionMailer::*
hostHostLog::Request
job_classJobClassLog::ActiveJob::*, Log::GoodJob::*
job_idJobIdLog::ActiveJob::*, Log::GoodJob::*
listening_addressesListeningAddressesLog::Puma::Start
locationLocationLog::Shrine::Delete, Log::Shrine::Download, Log::Shrine::Exist, Log::Shrine::Upload
lvlLevelEvery log type
mailerMailerClassLog::ActionMailer::*
mailer_actionMailerActionLog::ActionMailer::*
max_threadsMaxThreadsLog::Puma::Start
metadataMetadataLog::CarrierWave::Upload
methodHttpMethodLog::Error, Log::Puma::Error, Log::Request, Log::Security::*
mime_typeMimeTypeLog::ActiveStorage::Metadata, Log::CarrierWave::Download, Log::CarrierWave::Upload, Log::Shrine::Upload
min_threadsMinThreadsLog::Puma::Start
modeModeLog::Puma::Start
modelModelLog::CarrierWave::*
mount_pointMountPointLog::CarrierWave::*
msgMessageLog::ActionMailer::Error, Log::ActiveJob::Discard, Log::ActiveJob::Error, Log::ActiveJob::Retry, Log::ActiveJob::RetryStopped, Log::ActiveModelSerializers, Log::Ahoy, Log::Error, Log::GoodJob::Log, Log::Plain, Log::Puma::Error, Log::SQL, Log::Security::*, Log::Sidekiq::*
msg_idMessageIdLog::ActionMailer::*
nameNameLog::SQL
opOperationNo built-in log type
op_typeOperationTypeLog::SQL
optsOptionsLog::Shrine::Upload
paramsParamsLog::Request
pathPathLog::Error, Log::Puma::Error, Log::Request, Log::Security::*
pidProcessIdLog::GoodJob::*, Log::Puma::Shutdown, Log::Puma::Start, Log::Sidekiq::Log
pool_sizeConnectionPoolSizeLog::SQL
prefixPrefixLog::ActiveStorage::Delete
priorityPriorityLog::GoodJob::Log
propertiesPropertiesLog::Ahoy
provider_job_idProviderJobIdLog::ActiveJob::*
puma_codenamePumaCodenameLog::Puma::Start
puma_versionPumaVersionLog::Puma::Start
queue_nameQueueNameLog::ActiveJob::*, Log::GoodJob::*, Log::Sidekiq::Error
rangeRangeLog::ActiveStorage::Download
refererRefererLog::Error, Log::Request, Log::Security::*
req_idRequestIdAdded from the request (see above)
resource_classResourceClassLog::ActiveModelSerializers
resultResultNo built-in log type
retriesRetriesNo built-in log type
retry_countRetryCountLog::Sidekiq::Error
row_countRowCountLog::SQL
ruby_versionRubyVersionLog::Puma::Start
run_timeRunTimeNo built-in log type
scheduled_atScheduledAtLog::ActiveJob::Schedule, Log::GoodJob::Log
serializerSerializerLog::ActiveModelSerializers
sizeSizeLog::CarrierWave::Download, Log::CarrierWave::Upload, Log::Shrine::Upload
snapshotSnapshotLog::Dotenv::Save
source_ipSourceIpLog::Error, Log::Request, Log::Security::*
sqlSqlLog::SQL
srcSourceEvery log type
started_atStartedAtLog::ActiveJob::Start
statusStatusLog::Error, Log::Request
storageStorageLog::ActiveStorage::*, Log::CarrierWave::*, Log::Shrine::*
store_pathStorePathLog::CarrierWave::*
subjectSubjectLog::ActionMailer::*
table_namesTableNamesLog::SQL
tidThreadIdLog::GoodJob::*, Log::Sidekiq::Log
toToLog::ActionMailer::*
tsTimestampEvery log type
upload_optsUploadOptionsLog::Shrine::Upload
uploaderUploaderLog::CarrierWave::*, Log::Shrine::Upload
urlUrlNo built-in log type
user_agentUserAgentLog::Error, Log::Request, Log::Security::*
varsVarsLog::Dotenv::Restore, Log::Dotenv::Update
versionVersionLog::CarrierWave::*
viewViewLog::Request
wait_msWaitMsLog::ActiveJob::Retry
wait_timeWaitTimeNo built-in log type
x_forwarded_forXForwardedForLog::Security::BlockedHost, Log::Security::IPSpoof

Example Lines

One line for each built-in log type, as LogStruct's formatter writes it from the values its integration sets. The values are made up. Each line is shown over several lines here; LogStruct writes it on one. The fields of each type are listed in Built-In Log Classes.

Log::ActionMailer

Log::ActionMailer::Delivered src: mailer, evt: delivered
{
  "src": "mailer",
  "evt": "delivered",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "to": [
    "[EMAIL:a058d5090035d2e1]"
  ],
  "from": "[EMAIL:a6778e226026bac8]",
  "subject": "Your October invoice",
  "msg_id": "[EMAIL:ff61c70b949f216c]",
  "mailer": "InvoiceMailer",
  "mailer_action": "monthly",
  "attachments": 1,
  "account_id": 42,
  "user_id": 1873,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActionMailer::Delivery src: mailer, evt: delivery
{
  "src": "mailer",
  "evt": "delivery",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "to": [
    "[EMAIL:a058d5090035d2e1]"
  ],
  "from": "[EMAIL:a6778e226026bac8]",
  "subject": "Your October invoice",
  "mailer": "InvoiceMailer",
  "mailer_action": "monthly",
  "attachments": 1,
  "account_id": 42,
  "user_id": 1873,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActionMailer::Error src: mailer, evt: error
{
  "src": "mailer",
  "evt": "error",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "to": [
    "[EMAIL:a058d5090035d2e1]"
  ],
  "from": "[EMAIL:a6778e226026bac8]",
  "subject": "Your October invoice",
  "mailer": "InvoiceMailer",
  "mailer_action": "monthly",
  "attachments": 1,
  "error_class": "Net::SMTPAuthenticationError",
  "msg": "Net::SMTPAuthenticationError: Email delivery failed. Recipients: [EMAIL:a6778e226026bac8]. Error message: 535 5.7.8 Authentication failed",
  "backtrace": [
    "app/mailers/invoice_mailer.rb:12:in 'InvoiceMailer#monthly'"
  ],
  "account_id": 42,
  "user_id": 1873,
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "ActionMailer::MailDeliveryJob"
  }
}

Log::ActiveJob

Log::ActiveJob::Discard src: job, evt: discard
{
  "src": "job",
  "evt": "discard",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 1,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "error_class": "ActiveJob::DeserializationError",
  "msg": "Error while trying to deserialize arguments: Couldn't find Customer",
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}
Log::ActiveJob::Enqueue src: job, evt: enqueue
{
  "src": "job",
  "evt": "enqueue",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 0,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActiveJob::Error src: job, evt: error
{
  "src": "job",
  "evt": "error",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 1,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "error_class": "Net::ReadTimeout",
  "msg": "Net::ReadTimeout with #<TCPSocket:(closed)>",
  "backtrace": [
    "app/services/invoice_export.rb:42:in 'InvoiceExport#upload'",
    "app/services/invoice_export.rb:18:in 'InvoiceExport#call'",
    "app/jobs/invoice_export_job.rb:9:in 'InvoiceExportJob#perform'"
  ],
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}
Log::ActiveJob::Finish src: job, evt: finish
{
  "src": "job",
  "evt": "finish",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.935+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 1,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "duration_ms": 812.43,
  "finished_at": "2026-10-17T09:30:15.935+00:00",
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}
Log::ActiveJob::Retry src: job, evt: retry
{
  "src": "job",
  "evt": "retry",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 1,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "error_class": "Net::ReadTimeout",
  "msg": "Net::ReadTimeout with #<TCPSocket:(closed)>",
  "wait_ms": 3000,
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}
Log::ActiveJob::RetryStopped src: job, evt: retry_stopped
{
  "src": "job",
  "evt": "retry_stopped",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 5,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "error_class": "Net::ReadTimeout",
  "msg": "Net::ReadTimeout with #<TCPSocket:(closed)>",
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}
Log::ActiveJob::Schedule src: job, evt: schedule
{
  "src": "job",
  "evt": "schedule",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 0,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "scheduled_at": "2026-10-17T10:30:15.123+00:00",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActiveJob::Start src: job, evt: start
{
  "src": "job",
  "evt": "start",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "job_id": "c2b8e0f4-1d3a-4b6c-9e7f-5a1d2c3b4e5f",
  "job_class": "InvoiceExportJob",
  "queue_name": "default",
  "arguments": [
    "gid://my-app/Customer/1873",
    {
      "month": "2026-09",
      "email": {
        "_filtered": {
          "_class": "String",
          "_hash": "a058d5090035d2e1"
        }
      }
    }
  ],
  "executions": 1,
  "provider_job_id": "b4a9c63e5f1d2a7e9c0f3b18",
  "started_at": "2026-10-17T09:30:15.123+00:00",
  "attempt": 1,
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceExportJob"
  }
}

Log::ActiveModelSerializers

Log::ActiveModelSerializers src: rails, evt: generate
{
  "src": "rails",
  "evt": "generate",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "ams.render",
  "serializer": "InvoiceSerializer",
  "adapter": "ActiveModelSerializers::Adapter::Attributes",
  "resource_class": "Invoice",
  "duration_ms": 1.241,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::ActiveStorage

Log::ActiveStorage::Delete src: storage, evt: delete
{
  "src": "storage",
  "evt": "delete",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "prog": "Rails"
}
Log::ActiveStorage::Download src: storage, evt: download
{
  "src": "storage",
  "evt": "download",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "duration_ms": 96.12,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActiveStorage::Exist src: storage, evt: exist
{
  "src": "storage",
  "evt": "exist",
  "lvl": "debug",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "exist": true,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActiveStorage::Metadata src: storage, evt: metadata
{
  "src": "storage",
  "evt": "metadata",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "mime_type": "application/pdf",
  "prog": "Rails"
}
Log::ActiveStorage::Upload src: storage, evt: upload
{
  "src": "storage",
  "evt": "upload",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "duration_ms": 184.3,
  "checksum": "3Ys4l0pZ8wQ1mH7cE2uXrA==",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::ActiveStorage::Url src: storage, evt: url
{
  "src": "storage",
  "evt": "url",
  "lvl": "debug",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "S3",
  "file_id": "m3k9x2q7w1c8v4b6n0z5h2j7",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Ahoy

Log::Ahoy src: app, evt: log
{
  "src": "app",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "ahoy.track",
  "ahoy_event": "Viewed invoice",
  "properties": {
    "invoice_id": 812
  },
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::CarrierWave

Log::CarrierWave::Delete src: carrierwave, evt: delete
{
  "src": "carrierwave",
  "evt": "delete",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "file",
  "file_id": "avatar.png",
  "uploader": "AvatarUploader",
  "model": "User",
  "mount_point": "avatar",
  "version": "",
  "store_path": "uploads/user/avatar/1873/avatar.png",
  "ext": "png",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::CarrierWave::Download src: carrierwave, evt: download
{
  "src": "carrierwave",
  "evt": "download",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "file",
  "file_id": "avatar.png",
  "uploader": "AvatarUploader",
  "model": "User",
  "mount_point": "avatar",
  "version": "",
  "store_path": "uploads/user/avatar/1873/avatar.png",
  "ext": "png",
  "filename": "avatar.png",
  "mime_type": "image/png",
  "size": 48213,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::CarrierWave::Upload src: carrierwave, evt: upload
{
  "src": "carrierwave",
  "evt": "upload",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "file",
  "file_id": "avatar.png",
  "uploader": "AvatarUploader",
  "model": "User",
  "mount_point": "avatar",
  "version": "",
  "store_path": "uploads/user/avatar/1873/avatar.png",
  "ext": "png",
  "filename": "avatar.png",
  "mime_type": "image/png",
  "size": 48213,
  "duration_ms": 12.7,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Dotenv

Log::Dotenv::Load src: dotenv, evt: load
{
  "src": "dotenv",
  "evt": "load",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "file": ".env",
  "prog": "Rails"
}
Log::Dotenv::Restore src: dotenv, evt: restore
{
  "src": "dotenv",
  "evt": "restore",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "vars": [
    "DATABASE_URL",
    "REDIS_URL"
  ],
  "prog": "Rails"
}
Log::Dotenv::Save src: dotenv, evt: save
{
  "src": "dotenv",
  "evt": "save",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "snapshot": true,
  "prog": "Rails"
}
Log::Dotenv::Update src: dotenv, evt: update
{
  "src": "dotenv",
  "evt": "update",
  "lvl": "debug",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "vars": [
    "DATABASE_URL",
    "REDIS_URL"
  ],
  "prog": "Rails"
}

Log::Error

Log::Error src: app, evt: error
{
  "src": "app",
  "evt": "error",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "error_class": "JSON::ParserError",
  "msg": "Import failed",
  "error_message": "unexpected end of input",
  "backtrace": [
    "app/services/invoice_import.rb:27:in 'InvoiceImport#parse'",
    "app/controllers/imports_controller.rb:11:in 'ImportsController#create'"
  ],
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::GoodJob

Log::GoodJob::Error src: job, evt: error
{
  "src": "job",
  "evt": "error",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "error_class": "ActiveRecord::ConnectionNotEstablished",
  "error_message": "connection to server at \"10.0.3.12\", port 5432 failed: Connection refused",
  "pid": 4821,
  "tid": "2ix4",
  "backtrace": [
    "good_job/notifier.rb:225:in 'GoodJob::Notifier#with_connection'"
  ],
  "prog": "GoodJob"
}
Log::GoodJob::Log src: job, evt: log
{
  "src": "job",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "Notifier subscribed with LISTEN",
  "pid": 4821,
  "tid": "2ix4",
  "prog": "GoodJob"
}

Log::Plain

Log::Plain src: app, evt: log
{
  "src": "app",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "Exported 120 invoices",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Puma

Log::Puma::Error src: puma, evt: error
{
  "src": "puma",
  "evt": "error",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "error_class": "NoMethodError",
  "msg": "undefined method 'call' for nil",
  "backtrace": [
    "config/initializers/health_check.rb:8:in 'HealthCheck#call'"
  ],
  "error_context": "Rack app",
  "method": "GET",
  "path": "/up",
  "prog": "Rails"
}
Log::Puma::Shutdown src: puma, evt: shutdown
{
  "src": "puma",
  "evt": "shutdown",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "pid": 1,
  "prog": "Rails"
}
Log::Puma::Start src: puma, evt: start
{
  "src": "puma",
  "evt": "start",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "mode": "single",
  "puma_version": "6.6.1",
  "puma_codename": "Return to Forever",
  "ruby_version": "ruby 3.4.5 (2025-07-16 revision 20cda200d3) +PRISM [x86_64-linux]",
  "min_threads": 3,
  "max_threads": 3,
  "environment": "production",
  "pid": 1,
  "listening_addresses": [
    "http://0.0.0.0:3000"
  ],
  "prog": "Rails"
}

Log::Request

Log::Request src: rails, evt: request
{
  "src": "rails",
  "evt": "request",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "path": "/users/sign_in",
  "method": "POST",
  "source_ip": "198.51.100.23",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
  "referer": "https://app.example.com/users/sign_in",
  "format": "html",
  "controller": "Users::SessionsController",
  "action": "create",
  "status": 302,
  "duration_ms": 48.21,
  "view": 0,
  "db": 3.62,
  "params": {
    "authenticity_token": "[FILTERED]",
    "user": {
      "email": "[FILTERED]",
      "password": "[FILTERED]",
      "remember_me": "1"
    },
    "commit": "Log in"
  },
  "host": "app.example.com",
  "content_type": "application/x-www-form-urlencoded",
  "accept": "text/html,application/xhtml+xml",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Security

Log::Security::BlockedHost src: security, evt: blocked_host
{
  "src": "security",
  "evt": "blocked_host",
  "lvl": "warn",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "path": "/",
  "method": "GET",
  "source_ip": "203.0.113.7",
  "user_agent": "curl/8.7.1",
  "msg": "Blocked host detected: admin.example.net",
  "blocked_host": "admin.example.net",
  "blocked_hosts": [
    "admin.example.net"
  ],
  "prog": "Rails"
}
Log::Security::CSRFViolation src: security, evt: csrf_violation
{
  "src": "security",
  "evt": "csrf_violation",
  "lvl": "error",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "path": "/invoices/812",
  "method": "PATCH",
  "source_ip": "198.51.100.23",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
  "referer": "https://app.example.com/invoices/812/edit",
  "msg": "Can't verify CSRF token authenticity.",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::Security::IPSpoof src: security, evt: ip_spoof
{
  "src": "security",
  "evt": "ip_spoof",
  "lvl": "warn",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "path": "/login",
  "method": "GET",
  "source_ip": "10.0.1.15",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
  "msg": "IP spoofing attack?! HTTP_CLIENT_IP=\"203.0.113.9\" HTTP_X_FORWARDED_FOR=\"198.51.100.4\"",
  "client_ip": "203.0.113.9",
  "x_forwarded_for": "198.51.100.4",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Shrine

Log::Shrine::Delete src: shrine, evt: delete
{
  "src": "shrine",
  "evt": "delete",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "store",
  "location": "invoice/812/original-3f9a1c.pdf",
  "prog": "Rails"
}
Log::Shrine::Download src: shrine, evt: download
{
  "src": "shrine",
  "evt": "download",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "store",
  "location": "invoice/812/original-3f9a1c.pdf",
  "download_opts": {},
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::Shrine::Exist src: shrine, evt: exist
{
  "src": "shrine",
  "evt": "exist",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "store",
  "location": "invoice/812/original-3f9a1c.pdf",
  "prog": "Rails"
}
Log::Shrine::Metadata src: shrine, evt: metadata
{
  "src": "shrine",
  "evt": "metadata",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "cache",
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}
Log::Shrine::Upload src: shrine, evt: upload
{
  "src": "shrine",
  "evt": "upload",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "storage": "store",
  "location": "invoice/812/original-3f9a1c.pdf",
  "upload_opts": {},
  "opts": {},
  "uploader": "DocumentUploader",
  "duration_ms": 143.8,
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Log::Sidekiq

Log::Sidekiq::Error src: sidekiq, evt: error
{
  "src": "sidekiq",
  "evt": "error",
  "lvl": "warn",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "error_class": "Timeout::Error",
  "msg": "execution expired",
  "backtrace": [
    "app/workers/invoice_sync_worker.rb:15:in 'InvoiceSyncWorker#perform'"
  ],
  "error_context": "Job raised exception",
  "queue_name": "default",
  "retry_count": 2,
  "prog": "Rails",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceSyncWorker"
  }
}
Log::Sidekiq::Log src: sidekiq, evt: log
{
  "src": "sidekiq",
  "evt": "log",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "start",
  "ctx": {
    "jid": "b4a9c63e5f1d2a7e9c0f3b18",
    "class": "InvoiceSyncWorker"
  },
  "pid": 4821,
  "tid": "1v8b",
  "prog": "Sidekiq-Server"
}

Log::SQL

Log::SQL src: app, evt: database
{
  "src": "app",
  "evt": "database",
  "lvl": "info",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "msg": "Invoice Load executed",
  "sql": "SELECT \"invoices\".* FROM \"invoices\" WHERE \"invoices\".\"account_id\" = $1",
  "name": "Invoice Load",
  "duration_ms": 142.37,
  "row_count": 120,
  "adapter": "PostgreSQLAdapter",
  "db_name": "app_production",
  "pool_size": 5,
  "active_count": 2,
  "op_type": "SELECT",
  "table_names": [
    "invoices"
  ],
  "prog": "Rails",
  "req_id": "6f1c9a2e-7b4d-4e8a-9c61-2d5b8f0e4a17"
}

Value Formats

Timestamps

ts is an ISO 8601 time with milliseconds and the UTC offset of the process's time zone: 2026-10-17T09:30:15.123+00:00 on a server whose time zone is UTC, 2026-10-17T22:30:15.123+13:00 on one in New Zealand. Rails' config.time_zone doesn't change it. Every other time in a line is written the same way: the times of the built-in log types, such as ActiveJob's scheduled_at, and the times in a Hash you log.

Levels

lvl is debug, info, warn, error, fatal or unknown. It comes from the logger method that wrote the line (logger.warn writes warn), not from the log type's own level. Any other severity is written as unknown.

Filtered Values

A value whose key is filtered is replaced by a summary under _filtered. Which keys are filtered is described in Filtering Sensitive Data.

Rails.logger.info(
  msg: "Account updated",
  email: "linda.martinez@example.com",
  password: "correct horse battery staple",
  api_tokens: ["tok_8c1f2a", "tok_93d0b7"],
  credentials: {client_id: "my-app", client_secret: "abc123"},
  note: "Call Linda on 555-201-7788"
)
{
  "msg": "Account updated",
  "email": {
    "_filtered": {
      "_class": "String",
      "_hash": "a058d5090035d2e1"
    }
  },
  "password": {
    "_filtered": {
      "_class": "String"
    }
  },
  "api_tokens": {
    "_filtered": {
      "_class": "Array",
      "_count": 2
    }
  },
  "credentials": {
    "_filtered": {
      "_class": "Hash",
      "_keys_count": 2,
      "_keys": [
        "client_id",
        "client_secret"
      ]
    }
  },
  "note": "Call Linda on [PHONE]",
  "src": "app",
  "evt": "log",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "lvl": "info",
  "prog": "Rails"
}
  • A String: {"_class": "String"}, without its length. For keys in filter_keys_with_hashes (email and email_address by default) the summary also has a _hash of the value: 16 hex characters of an HMAC-SHA256 keyed with filters.hash_salt, or by default with a key derived from the app's secret_key_base. An email address has one hash however it's capitalized.
  • A Hash: _class, _keys_count and its first 10 keys in _keys. An empty Hash is {"_class": "Hash", "_empty": true}.
  • An Array: _class and _count. An empty Array is {"_class": "Array", "_empty": true}.
  • Any other value: its class, such as {"_class": "Integer"}.
  • A value that Rails' filter_parameters already replaced, such as the params on request lines, is kept as Rails wrote it: "[FILTERED]".
  • Values nested more than 20 levels deep, and values that contain themselves, are written as a summary without _keys.

Scrubbed strings keep their text, with the sensitive part replaced: [EMAIL:<hash>] (the same hash as _hash above), [PHONE], [SSN], [CREDIT_CARD], [PASSWORD] for a password in a URL, and [FILTERED] for the credentials after Bearer or Basic and sensitive query params. [IP] and [MAC] appear only when you turn those scrubbers on.

Truncation

config.limits sets how much of each value LogStruct writes:

  • max_hash_keys (default 100): a Hash writes its first 100 keys, then _more_keys with the number left out. The line's own keys count too.
  • max_array_items (default 10): an Array writes its first 10 items, then a last item "... and N more items".
  • max_backtrace_lines (default 20): for an Array that looks like a backtrace (3 of its first 5 items start with file:line), such as an error line's backtrace, the first 20 lines, then "... and N more lines".
  • max_string_bytes (default 32768, 32 KB): a longer string is cut to that many bytes after it's scrubbed, and ends with "... (cut from N bytes)".
# Small limits, to show the markers (the defaults are 100 keys, 10 items and 32 KB).
# A line's own keys count too: this one has 4.
LogStruct.configure do |config|
  config.limits.max_hash_keys = 4
  config.limits.max_array_items = 3
  config.limits.max_string_bytes = 24
end

Rails.logger.info(
  msg: "Import finished",
  columns: {first_name: "First name", last_name: "Last name", city: "City", country: "Country", zip: "Postcode"},
  row_ids: [101, 102, 103, 104, 105, 106, 107, 108],
  summary: "Rows 104 and 107 had no postcode"
)
{
  "msg": "Import finished",
  "columns": {
    "first_name": "First name",
    "last_name": "Last name",
    "city": "City",
    "country": "Country",
    "_more_keys": 1
  },
  "row_ids": [
    101,
    102,
    103,
    "... and 5 more items"
  ],
  "summary": "Rows 104 and 107 had no ... (cut from 32 bytes)",
  "src": "app",
  "evt": "log",
  "ts": "2026-10-17T09:30:15.123+00:00",
  "lvl": "info",
  "prog": "Rails"
}
The settings on this page are described in Configuration.