Skip to content

feat: add error reporters and JSON log formatter#121

Merged
mhenrixon merged 4 commits intomainfrom
feature/error-reporter-and-json-logging
Apr 11, 2026
Merged

feat: add error reporters and JSON log formatter#121
mhenrixon merged 4 commits intomainfrom
feature/error-reporter-and-json-logging

Conversation

@mhenrixon
Copy link
Copy Markdown
Owner

@mhenrixon mhenrixon commented Apr 11, 2026

Summary

  • Error Reporter (Pgbus::ErrorReporter): Configurable array of callable error reporters (config.error_reporters) that receive (exception, context_hash) at every critical rescue site. Inspired by Sidekiq's error_handlers pattern. Lets users route exceptions to APM services (Appsignal, Sentry, Honeybadger, etc.) instead of only logging them.
  • JSON Log Formatter (Pgbus::LogFormatter::JSON): Structured JSON log output with separate fields for ts, pid, tid, lvl, msg, component, and optional ctx. Extracts [Pgbus] / [Pgbus::Web] prefixes into a dedicated component field so msg stays clean. Also adds Pgbus::LogFormatter::Text for consistent text output.
  • Log Context (Pgbus::LogFormatter.with_context): Thread-local context hash that appears in JSON output under ctx, similar to Sidekiq::Context.

Error Reporter Configuration

Pgbus.configure do |c|
  c.error_reporters << ->(ex, ctx) {
    Appsignal.set_error(ex) { |t| t.set_tags(ctx) }
  }
end

JSON Logging Configuration

Pgbus.configure do |c|
  c.log_format = :json
end

Wired Into

Error reporters are called at all critical rescue sites:

  • ActiveJob executor (job failures)
  • Worker (fetch + process errors)
  • Dispatcher (maintenance task failures)
  • Supervisor (fork failures)
  • Circuit breaker (trip failures)
  • Outbox poller (publish failures)
  • Failed event recorder (recording failures)

Non-critical paths (dashboard queries, stat recording, debug-level catches) remain log-only.

Test plan

  • bundle exec rspec — 1632 examples, 0 failures (excluding pre-existing system test failures)
  • bundle exec rubocop — no offenses
  • Verify JSON formatter output in a Rails app with config.log_format = :json
  • Verify error reporter integration with Appsignal/Sentry in staging

Summary by CodeRabbit

  • New Features

    • Configurable log format (text or JSON) with improved, thread-aware log context.
    • Centralized, fault‑tolerant error reporting with pluggable handlers and structured context.
    • Core runtime components now send errors to the centralized reporter for more consistent observability.
  • Tests

    • Added specs covering configuration, log formatters, and error reporter behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71d7a887-886e-4557-b614-e30351717e81

📥 Commits

Reviewing files that changed from the base of the PR and between c180994 and cf28317.

📒 Files selected for processing (1)
  • lib/pgbus/active_job/executor.rb

📝 Walkthrough

Walkthrough

Centralizes error reporting and adds structured log formatting: introduces Pgbus::ErrorReporter and Pgbus::LogFormatter, exposes configuration for log_format and error_reporters, and replaces many direct Pgbus.logger.error calls with ErrorReporter.report(...). New specs validate these behaviors.

Changes

Cohort / File(s) Summary
Core infra
lib/pgbus/error_reporter.rb, lib/pgbus/log_formatter.rb, lib/pgbus/configuration.rb
Added Pgbus::ErrorReporter (fault-tolerant, pluggable handlers with arity-aware dispatch and swallow-on-failure), added Pgbus::LogFormatter (Text/JSON formatters, thread-local context, tid), and new log_format / error_reporters configuration with validation and formatter switching.
Runtime integrations
lib/pgbus/active_job/executor.rb, lib/pgbus/circuit_breaker.rb, lib/pgbus/failed_event_recorder.rb, lib/pgbus/outbox/poller.rb, lib/pgbus/process/dispatcher.rb, lib/pgbus/process/supervisor.rb, lib/pgbus/process/worker.rb
Replaced multiple Pgbus.logger.error rescue paths with ErrorReporter.report(exception, context) including structured context keys (action, queue, msg_id, entry_id, batch_size, etc.). Functional control flow unchanged.
Tests
spec/pgbus/configuration_error_reporters_spec.rb, spec/pgbus/configuration_log_format_spec.rb, spec/pgbus/error_reporter_spec.rb, spec/pgbus/log_formatter_spec.rb
Added specs for error_reporters behavior, log_format setter and formatter swap, ErrorReporter.report dispatch/isolation and arity handling, and LogFormatter Text/JSON output and thread-local context behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Worker as Worker/Component
  participant ErrRpt as Pgbus::ErrorReporter
  participant Logger as Config.logger
  participant Ext as ExternalReporter

  Worker->>ErrRpt: ErrorReporter.report(error, ctx)
  ErrRpt->>Logger: log_error(exception, ctx)
  ErrRpt->>Ext: call_handler(reporter, exception, ctx, config)
  Alt reporter raises
    Ext-->>ErrRpt: raises
    ErrRpt->>Logger: logger.error("reporter failed", ...)
  End
  ErrRpt-->>Worker: returns (swallows exceptions)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I nibble at stack traces, tidy each clue,
I bundle the context — queue, id, and you,
JSON or text, I hop through the log,
Call handlers gentle, catch every frog,
🐇 Carrots for errors, neat and true.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add error reporters and JSON log formatter' directly and accurately summarizes the two main features introduced: error reporters for exception handling and JSON log formatting capabilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/error-reporter-and-json-logging

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/pgbus/circuit_breaker.rb`:
- Line 95: The rescue path in circuit_breaker.rb calls ErrorReporter.report
(line with ErrorReporter.report(e, { action: "circuit_breaker_trip", queue:
queue_name })) which can itself raise and break fault tolerance; wrap that call
in its own begin/rescue that catches StandardError (or Exception as your policy)
and logs the failure via a local logger/STDERR without re-raising, or update
ErrorReporter.report to defensively rescue and swallow/report its internal
errors (e.g., add an internal begin/rescue around callback/logger usage inside
ErrorReporter.report); ensure the circuit breaker rescue path never propagates
exceptions from the reporter.

In `@lib/pgbus/log_formatter.rb`:
- Around line 46-48: Extract the duplicated tid logic into a single shared
helper and call it from both formatters: create a private module method (e.g.,
in a module TidHelper or the parent LogFormatter class) that implements the
current Thread.current[:pgbus_tid] ||= (Thread.current.object_id ^
::Process.pid).to_s(36) logic, remove the duplicate tid definitions from the
Text and JSON formatter classes, and have Text#tid and JSON#tid delegate to the
shared helper (or include the module) so the same unique identifier logic is
centralized and reused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0737f647-4691-4e9a-8b5c-c3aa51de7846

📥 Commits

Reviewing files that changed from the base of the PR and between 3c39a54 and 14cf3ee.

📒 Files selected for processing (14)
  • lib/pgbus/active_job/executor.rb
  • lib/pgbus/circuit_breaker.rb
  • lib/pgbus/configuration.rb
  • lib/pgbus/error_reporter.rb
  • lib/pgbus/failed_event_recorder.rb
  • lib/pgbus/log_formatter.rb
  • lib/pgbus/outbox/poller.rb
  • lib/pgbus/process/dispatcher.rb
  • lib/pgbus/process/supervisor.rb
  • lib/pgbus/process/worker.rb
  • spec/pgbus/configuration_error_reporters_spec.rb
  • spec/pgbus/configuration_log_format_spec.rb
  • spec/pgbus/error_reporter_spec.rb
  • spec/pgbus/log_formatter_spec.rb

Comment thread lib/pgbus/circuit_breaker.rb
Comment thread lib/pgbus/log_formatter.rb Outdated
Add Pgbus::ErrorReporter module and config.error_reporters array so users
can route caught exceptions to APM services (Appsignal, Sentry, etc.)
instead of only logging them.

Configuration:
  Pgbus.configure do |c|
    c.error_reporters << ->(ex, ctx) { Appsignal.set_error(ex) }
  end

Wired into all critical rescue sites: executor job failures, worker
fetch/process errors, dispatcher maintenance, circuit breaker trips,
supervisor fork failures, outbox publish errors, and failed event
recording. Non-critical paths (dashboard queries, stat recording,
debug-level defensive catches) intentionally left as log-only.

Inspired by Sidekiq's error_handlers pattern — each reporter receives
(exception, context_hash) or optionally (exception, context_hash, config).
Add Pgbus::LogFormatter module with Text and JSON formatters, inspired
by Sidekiq::Logger::Formatters. The JSON formatter outputs structured
log lines with separate fields for timestamp, pid, tid, severity,
message, component, and optional thread-local context.

Configuration:
  Pgbus.configure do |c|
    c.log_format = :json  # switches logger formatter to JSON
  end

The JSON formatter extracts [Pgbus] and [Pgbus::Web] prefixes from
messages into a "component" field, keeping the msg field clean. Thread-
local context (via LogFormatter.with_context) appears under "ctx".

Also adds LogFormatter::Text for consistent text output with pid/tid.
@mhenrixon mhenrixon enabled auto-merge (squash) April 11, 2026 11:39
@mhenrixon mhenrixon self-assigned this Apr 11, 2026
@mhenrixon mhenrixon added the enhancement New feature or request label Apr 11, 2026
- Wrap ErrorReporter.report in outer rescue Exception to guarantee it
  never raises — callers sit inside rescue blocks where propagation
  would break fault-tolerance invariants
- Extract duplicated tid helper to LogFormatter module-level method
  shared by both Text and JSON formatters
- Add test proving report swallows logger failures
@mhenrixon mhenrixon force-pushed the feature/error-reporter-and-json-logging branch from 14cf3ee to c180994 Compare April 11, 2026 11:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/pgbus/active_job/executor.rb`:
- Around line 149-151: The context hash `ctx` in Executor#... currently contains
queue, job_class, msg_id, and read_ct but needs an explicit action key for
consistency; update the `ctx = { ... }` construction (the `ctx` variable created
near `queue_name`, `payload&.dig("job_class")`, `message.msg_id`,
`message.read_ct`) to include an `action` entry (e.g. action: "execute_job")
before calling `ErrorReporter.report(error, ctx)` so downstream
routing/filtering matches other reporter call sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 58e63167-6a10-4352-a5d1-9a6d82a2155e

📥 Commits

Reviewing files that changed from the base of the PR and between 14cf3ee and c180994.

📒 Files selected for processing (14)
  • lib/pgbus/active_job/executor.rb
  • lib/pgbus/circuit_breaker.rb
  • lib/pgbus/configuration.rb
  • lib/pgbus/error_reporter.rb
  • lib/pgbus/failed_event_recorder.rb
  • lib/pgbus/log_formatter.rb
  • lib/pgbus/outbox/poller.rb
  • lib/pgbus/process/dispatcher.rb
  • lib/pgbus/process/supervisor.rb
  • lib/pgbus/process/worker.rb
  • spec/pgbus/configuration_error_reporters_spec.rb
  • spec/pgbus/configuration_log_format_spec.rb
  • spec/pgbus/error_reporter_spec.rb
  • spec/pgbus/log_formatter_spec.rb

Comment thread lib/pgbus/active_job/executor.rb Outdated
All other ErrorReporter.report call sites include an action: key.
@mhenrixon mhenrixon merged commit 6c8f847 into main Apr 11, 2026
9 checks passed
@mhenrixon mhenrixon deleted the feature/error-reporter-and-json-logging branch April 11, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant