Skip to content

Log GoodJob workers as JSON, and each GoodJob error once - #20

Merged
ndbroadbent merged 10 commits into
mainfrom
nathan/good-job-workers
Oct 17, 2026
Merged

ndbroadbent merged 10 commits into
mainfrom
nathan/good-job-workers

Conversation

@ndbroadbent

@ndbroadbent ndbroadbent commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Fifth of twelve stacked PRs for 0.2.0.

What this fixes

  • good_job start workers wrote plain text. LogStruct now turns itself on in GoodJob's executable, like Puma and Sidekiq, and stops GoodJob adding a second, plain text STDOUT logger beside it.
  • Thread errors weren't logged when the app sets its own handler. GoodJob's engine applies config.good_job.on_thread_error after LogStruct's setup, so it replaced LogStruct's handler. LogStruct now prepends GoodJob._on_thread_error, which every GoodJob component calls: it logs a structured error line, then calls whatever handler the app set.
  • Logging a thread error raised a TypeError. LogStruct's GoodJob logger only took Strings. It now logs LogStruct entries and Hashes as they are.
  • Each thread error was also logged as GoodJob's "GoodJob error: ..." text line with the whole backtrace. GoodJob passes job task, systemd watchdog and notifier errors to on_thread_error, so its own lines for them are off. The one exception is the notifier's connection errors, which GoodJob passes on only once they repeat: its line for those stays, without the backtrace.
  • A failed job was logged twice. GoodJob also passes every failed job's exception to on_thread_error, after the ActiveJob integration has logged it with the job's ID and class. The ActiveJob integration now marks the exceptions it logs (LogStruct::LoggedErrors, a weak map), and the thread error handler skips them.
  • Exceptions that aren't StandardErrors raised a TypeError in the handler. GoodJob passes any Exception that its handled_exceptions setting lets through, for example a job's NotImplementedError. The handlers, the ActiveJob error line and Log.from_exception now take any Exception, and error_class on Log::Error is T.class_of(Exception).
  • GoodJob's own log lines get the job's fields (ID, class, queue) from GoodJob::CurrentThread while a job runs.
  • Removes LogStruct::Integrations::GoodJob::LogSubscriber. It listened for events GoodJob doesn't send (enqueue, start, finish, error, schedule), so it never ran. GoodJob's jobs are logged by the ActiveJob integration, so subscribing to GoodJob's real events would log each job twice.
  • Removes the LogStruct::Log::GoodJob::Enqueue, Start, Finish and Schedule log types, which only the subscriber built. GoodJob lines are log and error lines. The schema, the generated docs, the Terraform provider catalog check and the integration docs (now "GoodJob 3.0 or later") match.

Breaking: code that referred to the removed classes or to the four GoodJob event types. Code that read error_class as a StandardError subclass gets T.class_of(Exception).

Testing

The thread-error test runs the real order (the app sets its handler after LogStruct's setup) and fails on the old code; so do the tests for a failed job's exception (one line), a NotImplementedError and the CurrentThread fields. Also checked with real GoodJob 4.19 in a Rails app booted in production mode: one line each for a thread error, a failed job and a NotImplementedError. Sorbet and the unit suite (294 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

LogStruct turned itself on in Puma, Rails servers and Sidekiq, but not in
`good_job start` workers, which kept writing Rails' plain text logs. Treat
GoodJob's executable as a server process: it sets GoodJob::CLI.within_exe
before it loads the app.

GoodJob's executable also adds a plain text STDOUT logger to its log
subscriber unless GoodJob.logger already writes to STDOUT, which it can't
tell for LogStruct's logger, so every GoodJob line would be written twice.
Turn that off when LogStruct replaces GoodJob's logger.
GoodJob applies config.good_job (logger, on_thread_error) in an
after_initialize block. An app that sets on_thread_error there, as
GoodJob's README does to report thread errors to an error tracker,
replaced LogStruct's handler, so thread errors were reported but no
longer logged. LogStruct now wraps the handler again after
initialization.
GoodJob calls on_thread_error for every error its job and timer tasks
raise, and its own log subscriber then logs the same error as
"GoodJob error: ..." text with the whole backtrace in the message (over
10KB). LogStruct's thread error handler already logs each one as a
structured line, so GoodJob's finished_job_task and finished_timer_task
lines are turned off when that handler is in place.
The thread error handler passes a LogStruct::Log::GoodJob::Error entry to
GoodJob.logger, and LogStruct's GoodJob logger wrapped every message in
a Log::GoodJob::Log, whose message must be a String. Every thread error
raised a TypeError (outside production) or logged a Sorbet error instead
of the thread error. The logger now logs LogStruct entries and Hashes as
they are, and the thread error line has level error.

The tests stubbed the logger, so they never ran this path.
GoodJob::LogSubscriber also logs the notifier's and systemd's thread
errors as text with the whole backtrace. The notifier only calls
on_thread_error after repeated connection errors, so these lines are
still logged, with the error class and message only.
LogStruct logged thread errors by replacing GoodJob.on_thread_error with
a handler that called the app's. In a Rails app, GoodJob's engine
applies config.good_job.on_thread_error in an after_initialize block
that runs after LogStruct's, so it replaced LogStruct's handler and
thread errors were reported but not logged. The test stubbed
after_initialize, so it never ran the real order.

LogStruct now prepends GoodJob._on_thread_error, which every GoodJob
component calls with its thread errors: it logs the structured line,
then calls whatever handler the app set, whenever it was set.
…doesn't send

It handled enqueue, start, finish, error and schedule events, but GoodJob's events are enqueue_job,
perform_job and the scheduler, notifier and cron events, so none of its methods ever ran. GoodJob's
jobs are already logged by the ActiveJob integration, so subscribing to GoodJob's real events would
log every job twice. The docs now say what the integration logs: GoodJob's own messages and its
thread errors.
…dErrors

GoodJob passes every unhandled job exception to on_thread_error, after the
ActiveJob integration has already logged it with the job's ID and class, so
each failed job was logged as two job error lines. The ActiveJob integration
now records the exceptions it logs (LogStruct::LoggedErrors, which holds them
weakly), and the thread error line skips them. The app's handler still gets
every error.

GoodJob's notifier passes every error except connection errors to
on_thread_error straight away, and systemd watchdog errors always, so GoodJob's
own lines for those are off too. The notifier's line for connection errors is
kept, since it passes those on only once they repeat. The finished_timer_task
override is gone: GoodJob instruments that event without the .good_job suffix,
so its log subscriber never receives it.

GoodJob passes any Exception that its handled_exceptions setting lets through,
and ActiveJob's perform_now lets any Exception through. Both handlers' signatures
only accepted StandardError, so outside production a NotImplementedError raised
a TypeError and the app's handler was skipped. Log::Error's error_class is now
any Exception class.
The GoodJob logger read the running job from Thread.current[:good_job_execution],
which GoodJob never sets, so its lines never had the job's ID, class or queue.
It now reads GoodJob::CurrentThread.job (GoodJob 4) or .execution (GoodJob 3).
The tests set that thread variable themselves, so they passed.
GoodJob::Enqueue, Start, Finish and Schedule were only built by the removed
GoodJob log subscriber. GoodJob's jobs are logged by the ActiveJob integration,
whose lines have the same src and evt. The Terraform provider's catalog now
lists only error and log for GoodJob, and the docs no longer describe GoodJob
lifecycle events. The docs say the integration needs GoodJob 3.0 or later
(GoodJob._on_thread_error).
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 28 billable files and costs up to $7.00.

  • 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: 4f5668b2-af7d-4eea-b9a2-85ba9abaa652
📥 Commits

Reviewing files that changed from the base of the PR and between f4d9ffd and 6b28d56.

⛔ Files ignored due to path filters: 1
  • sorbet/rbi/shims/good_job.rbi is excluded by !sorbet/**
📒 Files selected for processing: 28
  • INTEGRATIONS.md
  • docs/lib/integration-helpers.ts
  • docs/lib/log-generation/log-generator.ts
  • docs/lib/log-structure-descriptions.ts
  • lib/log_struct.rb
  • lib/log_struct/concerns/configuration.rb
  • lib/log_struct/integrations/active_job/log_subscriber.rb
  • lib/log_struct/integrations/good_job.rb
  • lib/log_struct/integrations/good_job/log_subscriber.rb
  • lib/log_struct/integrations/good_job/logger.rb
  • lib/log_struct/log.rb
  • lib/log_struct/log/error.rb
  • lib/log_struct/log/good_job.rb
  • lib/log_struct/log/good_job/enqueue.rb
  • lib/log_struct/log/good_job/finish.rb
  • lib/log_struct/log/good_job/schedule.rb
  • lib/log_struct/log/good_job/start.rb
  • lib/log_struct/logged_errors.rb
  • schemas/log_sources/error.yml
  • schemas/log_sources/good_job.yml
  • schemas/meta/log-source-schema.json
  • scripts/export_provider_catalog.rb
  • test/log_struct/configuration_test.rb
  • test/log_struct/integrations/active_job/log_subscriber_test.rb
  • test/log_struct/integrations/good_job/log_subscriber_test.rb
  • test/log_struct/integrations/good_job/logger_test.rb
  • test/log_struct/integrations/good_job_test.rb
  • test/log_struct/log/good_job_test.rb
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@ndbroadbent
ndbroadbent changed the base branch from nathan/compatibility-fixes to main October 17, 2026 23:13
@ndbroadbent
ndbroadbent merged commit 025131f into main Oct 17, 2026
1 check passed
@ndbroadbent
ndbroadbent deleted the nathan/good-job-workers branch October 17, 2026 23:13
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