Skip to content

Set up integrations after the app's initializers, and stop Shrine, AMS and Puma writing text lines - #18

Merged
ndbroadbent merged 7 commits into
mainfrom
nathan/integration-fixes
Oct 17, 2026
Merged

ndbroadbent merged 7 commits into
mainfrom
nathan/integration-fixes

Conversation

@ndbroadbent

@ndbroadbent ndbroadbent commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Third of twelve stacked PRs for 0.2.0.

What this fixes

  • Settings in config/initializers/logstruct.rb were ignored. Integrations were set up before the app's initializers ran, so the documented place to configure LogStruct had no effect on integration toggles, and filters other initializers added to filter_parameters were never merged. Setup now runs after config/initializers. enabled and enabled_environments set in an initializer work too (LOGSTRUCT_ENABLED still takes precedence).
  • Shrine's default log subscriber wrote a text line beside each structured one. LogStruct now loads the instrumentation plugin without it, and skips LogStruct's events in it whenever the app loads it. Shrine.logger writes JSON.
  • ActiveModelSerializers' own log subscriber is detached, so each render is logged once.
  • GoodJob's logger was never set. Inside LogStruct::Integrations::GoodJob, GoodJob is that module, so the integration set the logger on itself. It now sets ::GoodJob.logger (the thread error handling is reworked in the GoodJob PR).
  • Sidekiq's logger is replaced as soon as Rails' logger is, so lines Sidekiq logs while the app's initializers run are JSON.
  • Puma's request errors (its "Rack app (...)" lines) are structured error lines with the method and path, without the query string.
  • Removes a debug line ([logstruct] puma lifecycle init) Puma servers wrote to STDERR at boot.

Because setup runs after the app's initializers, LogStruct's loggers and handlers now replace ones an initializer set (a custom Sidekiq logger, lograge settings). Configure those through LogStruct.

Testing

Every regression test fails without its fix. Sorbet and the unit suite (300 tests) pass at this branch's tip.


Stack for 0.2.0, merged in order: #16 → #17 → #18 → #19 → #20 → #21 → #22 → #23 → #24 → #25 → #26 → #27

Apps usually load Shrine's instrumentation plugin in their own
initializer, which subscribes Shrine's default log subscriber: it writes
each upload, download, open, exists, delete and metadata event to
Shrine.logger as a line of text, next to LogStruct's structured line.
LogStruct only removed it when the plugin was already configured, and
that path listened on the wrong notification names ("shrine.upload"
instead of "upload.shrine"), unsubscribed every other listener for those
events and passed ActiveSupport events where Shrine's were expected.

LogStruct now loads the plugin without the default subscriber, subscribes
its own with Shrine.subscribe, and skips those events in Shrine's default
subscriber whenever the app loads it, before or after LogStruct. It also
points Shrine.logger at SemanticLogger, so other plugins' log lines and
Shrine's warnings are written as JSON instead of plain text.
AMS attaches a log subscriber that writes "Rendered UserSerializer with
ActiveModelSerializers::Adapter::Attributes (0.1ms)" for every render,
so each render was logged twice: once as that line of text and once as
LogStruct's structured ams.render line.
The logstruct.setup initializer only had to run before the middleware
stack was built, so it ran before the app's config/initializers. The
documented place to configure LogStruct is config/initializers/
logstruct.rb, so its integration toggles were ignored, and filters that
other initializers added to filter_parameters (such as Rails' default
filter_parameter_logging.rb) were never merged into LogStruct's filters.
It now runs after :load_config_initializers.

`config.enabled` and `config.enabled_environments` set in an
initializer were ignored too, because LogStruct decides whether it's
enabled when it's required, and the logger is replaced as soon as Rails
creates it. The logger is still replaced early, so boot-time logs and
code that keeps a reference to Rails.logger use it, but setup now
replaces it late if an initializer enabled LogStruct, and puts the
original loggers back if an initializer disabled it. As documented,
LOGSTRUCT_ENABLED takes precedence, then `enabled`, then
`enabled_environments`.
Inside LogStruct::Integrations::GoodJob, `GoodJob` is that module, so
the integration set the logger and thread error handler on itself and
GoodJob kept its own logger. It now sets them on ::GoodJob. The thread
error handler calls the handler the app set first (GoodJob's README sets
one to report errors), then logs the error.
Integrations are now set up after config/initializers, so Sidekiq kept
its own text logger while they ran. An initializer that configures
Sidekiq logs with that logger straight away (connecting to Redis,
setting up a fetcher), so those lines were plain text. The Railtie now
replaces Sidekiq's logger as soon as it replaces Rails.logger, and puts
Sidekiq's logger back if an initializer turns LogStruct or the Sidekiq
integration off.
Puma::ErrorLogger writes request errors (an exception the app didn't
handle, a malformed request, an SSL error) as a plain text line on
STDERR: the time, the request line with its query string and client
IP, and the error. LogStruct's Puma integration now logs them as error
lines (src puma, evt error) with the error, method and path. The query
string is left out, since tokens in it can't be filtered inside the
line.
The puma lifecycle initializer wrote "[logstruct] puma lifecycle init"
to STDERR on every rails server boot.
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 19 billable files and costs up to $4.75.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 37 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 53 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 68522ad1-8d4d-4814-93c8-7fee5182013d
📥 Commits

Reviewing files that changed from the base of the PR and between 3bfe669 and e0f6768.

⛔ Files ignored due to path filters: 2
  • sorbet/rbi/shims/good_job.rbi is excluded by !sorbet/**
  • sorbet/rbi/shims/shrine.rbi is excluded by !sorbet/**
📒 Files selected for processing: 19
  • .cspell/project-words.txt
  • lib/log_struct/concerns/configuration.rb
  • lib/log_struct/integrations.rb
  • lib/log_struct/integrations/active_model_serializers.rb
  • lib/log_struct/integrations/good_job.rb
  • lib/log_struct/integrations/puma.rb
  • lib/log_struct/integrations/shrine.rb
  • lib/log_struct/integrations/sidekiq.rb
  • lib/log_struct/railtie.rb
  • lib/log_struct/semantic_logger/setup.rb
  • rails_test_app/templates/config/initializers/logstruct.rb
  • rails_test_app/templates/test/integration/ams_logging_test.rb
  • rails_test_app/templates/test/integration/boot_logs_integration_test.rb
  • rails_test_app/templates/test/integration/filter_parameters_test.rb
  • rails_test_app/templates/test/integration/shrine_logging_test.rb
  • test/log_struct/configuration_test.rb
  • test/log_struct/integrations/good_job_test.rb
  • test/log_struct/integrations/puma_unit_test.rb
  • test/log_struct/integrations/sidekiq_test.rb
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant