Upgrading to 0.2.0

0.2.0 changes the shape of some lines, filters more, and fixes integrations that logged nothing or logged twice. This page lists what you may need to change. The changelog has every change.

Requirements

  • Ruby 3.3 or newer, and Rails 7.1 or newer.
  • LogStruct depends on railties, actionpack, activesupport, globalid and lograge instead of rails, so it no longer installs every Rails framework.
  • Pin gem "logstruct", "~> 0.2.0". Below 1.0, a minor version can change lines.

Lines that look different

Update the log queries, dashboards and alerts that read these fields.

  • A logged Hash's keys are at the top level, not under payload. A Hash can set its own evt and src:
Rails.logger.info({evt: "signup", user_id: 1, plan: "pro"})
{"evt":"signup","user_id":1,"plan":"pro","src":"app","ts":"2026-10-17T03:14:21.865Z","lvl":"info","prog":"Rails"}
  • Email hashes changed. A hash is now an HMAC-SHA256, 16 characters long, keyed with filters.hash_salt or, without it, a key derived from your app's secret_key_base. Every hash differs from 0.1's, and they change again if secret_key_base does: set hash_salt to keep them. An address has one hash however it's capitalized.
  • Filtered values have no length. A filtered String is {"_filtered":{"_class":"String"}}, without _bytes. Keys in filter_keys_with_hashes (email, email_address) are filtered too, with a _hash:
{"msg":"Invited","email":{"_filtered":{"_class":"String","_hash":"537d1000c07039b3"}},"invite_token":{"_filtered":{"_class":"String"}},"src":"app","evt":"log","ts":"2026-10-17T03:14:21.865Z","lvl":"info","prog":"Rails"}
  • More keys are filtered. Sensitive keys are matched by their words in any case or separator style, so apiKey, X-Api-Key, Set-Cookie, passwords, session, passcode and card_number are filtered by default. Rails' filter_parameters match as Rails matches them: :secret filters clientSecret. See Filtering Sensitive Data.
  • Request lines have source_ip and host. If your lograge_custom_options added them, remove them there.
  • Tags are written: logger.tagged and config.log_tags give a tags array, and named tags go in ctx.
  • Times have milliseconds in every field (scheduled_at, started_at, finished_at, ...), like ts. A finish line's ts is when the job finished.
  • Size limits. Hashes write their first 100 keys, backtraces 20 lines (an error line's backtrace had 5), other Arrays 10 items, and strings are cut at 32 KB. See Limits.
  • Error lines from Sidekiq, Puma and ActiveJob have typed fields instead of fields in additional_data, and the request's path, method, source_ip, user_agent and referer. A message logged with an exception (logger.error("Import failed", e)) has the message in msg and the exception's in error_message.
  • Mail error lines say Email delivery failed, not Email delivery error, will retry. Update alerts that match the old text.
  • Removed fields that were never written: ActiveStorage's url (a signed URL anyone could use), ActiveJob's retries, the blocked host line's allowed_hosts and allow_ip_hosts (it has blocked_hosts instead), Shrine's metadata and exist, and ActiveStorage's filename, mime_type, size and metadata on upload and download lines (metadata lines have the mime_type instead).

Behavior changes

  • Production writes to STDOUT only. LogStruct no longer adds a log/production.log that nothing rotated. Development writes STDOUT and its log file, and test its log file. See Where lines go.
  • Sorbet type errors raise in development and test. error_handling_modes.type_checking_errors defaults to ReportProduction: raised in local_environments, logged and reported in production. They were only logged, so a test suite can find type errors it missed before. Set it to Log to only log them while you fix them.
  • Integrations are set up after your initializers, so the settings in config/initializers/logstruct.rb apply. LogStruct's Lograge settings and Sidekiq logger replace ones another initializer set: configure them through LogStruct (lograge_custom_options, for example).
  • IP spoofing isn't checked on every request any more. Rails raises IpSpoofAttackError where your app reads remote_ip and responds as it would without LogStruct, and LogStruct logs it there.
  • Blocked hosts get your app's own config.host_authorization[:response_app] if you set one.
  • Mail delivery jobs log only the mailer, the mail and the delivery method as their arguments, because mailers are often passed tokens (Devise's reset password token) as plain Strings.
  • A failed Sidekiq job is one structured warn line without its arguments, instead of Sidekiq's three text lines.
  • Mailers keep their logger, so logger.info in a mailer is written.

Code that may need changing

  • LogStruct::Log::Sidekiq is now LogStruct::Log::Sidekiq::Log.
  • LogStruct::Log::Error's error_class takes any Exception class.
  • LogStruct.handle_exception takes log:. If your tests stub it with a lambda, accept log: too.
  • Removed: LogStruct::Integrations::GoodJob::LogSubscriber and Log::GoodJob::Enqueue, Start, Finish and Schedule (GoodJob's jobs are logged by the ActiveJob integration), LogStruct::Integrations::Rack, Log::ActiveStorage::Stream and Event::Stream.
  • Removed: config.integrations.enable_semantic_logger, which did nothing (LogStruct always writes through SemanticLogger). Remove it from your initializer: setting it raises NoMethodError.

New in 0.2.0

  • config.limits, filters.url_query_params, filters.path_matchers and filters.authorization_credentials.
  • ActiveJob retry, retry_stopped and discard lines, and lines for perform_all_later.
  • CarrierWave download and delete lines, and ActiveStorage deletes by prefix.
  • Every line logged during a Sidekiq job has the job's jid and class in ctx.
  • Forked children (Puma and Unicorn workers, Passenger, Pitchfork, Resque) write their lines with no setup.

If you use the Terraform provider, upgrade it with the gem: its catalog of sources and events is built in, and 0.2.0 removes GoodJob's enqueue, start, finish and schedule events and ActiveStorage's stream event.