Add observability: client-side errors, structured logs, error subscriber (#39) #40
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "39-add-observability"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Changes
app/javascript/application.js: Globalwindow.onerror+unhandledrejectionhandlers that POST to/errors/clientapp/controllers/client_errors_controller.rb: New endpoint — logs client errors as[CLIENT_ERROR]lines, 4KB body limit, JSON validationapp/javascript/controllers/location_controller.js: Fix 2 silent.catch()blocks — now logs to console and shows actual error messageapp/javascript/controllers/add_location_controller.js: Fix 2 silent.catch()blocks — same treatmentconfig/initializers/error_subscriber.rb: Rails error subscriber for structured server-side exception loggingconfig/routes.rb: AddPOST /errors/clientrouteGemfile+Gemfile.lock: Addlogragegemconfig/environments/production.rb: Enable lograge with JSON formatter and request_idspec/requests/client_errors_spec.rb: Request spec for valid and invalid payloadsTest Plan
POST /errors/clientlogs[CLIENT_ERROR]line to STDOUTReview Checklist
Related Notes
project-landscaping-assistant— project this work belongs toobservability-audit-2026-02-25— platform observability baseline that informed the approachCloses #39
PR #40 Review
DOMAIN REVIEW
Tech stack: Rails 8, Stimulus/importmap, RSpec. This is a Ruby/Rails review with JS (vanilla Stimulus, no TypeScript).
ClientErrorsController (
app/controllers/client_errors_controller.rb)skip_forgery_protectionis justified -- the client-side JS sends the CSRF token viaX-CSRF-Tokenheader, and the controller reads raw body. This is a standard pattern for JSON API endpoints in Rails. No issue.Body size limit (4KB) is good -- prevents log-flooding abuse. The
request.body.read(4096)approach is correct.No rate limiting -- This endpoint is unauthenticated (no auth in
ApplicationController) and CSRF-protected only by a meta tag token. An attacker with a valid CSRF token (which is in every page's HTML) could spam this endpoint to flood logs. This is a low-severity concern for an internal landscaping tool, but worth noting. Not a blocker given the app has no authentication at all.data['stack']&.truncate(500)--truncateis an ActiveSupport method on String. Ifdata['stack']is present but not a String (e.g., attacker sends"stack": 123), this will raiseNoMethodError. TheJSON::ParserErrorrescue won't catch it. This would result in a 500 error, which is noisy but not harmful. Minor robustness gap.No sanitization of logged values --
data['message'],data['source'], etc. are interpolated directly into log output. If logs are consumed by a log aggregator (Loki, as mentioned), injected newlines or control characters could corrupt log parsing. Low risk for a structured logging pipeline but worth a note.Error subscriber (
config/initializers/error_subscriber.rb)reportmethod signature matches the Rails 7+ErrorReporter::Subscriberinterface correctly (error, handled:, severity:, context:, source:). Uses keyword args properly.Lograge config (
config/environments/production.rb)request_idcustom option aligns with the existingconfig.log_tags = [ :request_id ]on line 36. Good correlation setup.Client-side JS (
app/javascript/application.js)The
reportClientErrorfunction has.catch(() => {})-- a silent catch on the error-reporting fetch itself. This is actually correct here (you don't want error-reporting failures to themselves trigger error reporting in an infinite loop). Good defensive pattern.window.onerrorreturnsfalse, which is correct -- it allows the error to still propagate to the browser console.CSRF token lookup uses optional chaining (
?.content), and the function returns early if no token is found. This handles SSR/test environments cleanly.Stimulus controller fixes (
location_controller.js,add_location_controller.js)All four
.catch()blocks now log toconsole.errorand display actual error messages to the user. This is the right fix -- errors were previously swallowed with generic "Failed" messages. The user now seese.message || "fallback"which is a good pattern.Minor UX concern: Raw
e.messagevalues from fetch/network errors may not be user-friendly (e.g.,"Failed to fetch","NetworkError when attempting to fetch resource"). These are browser-generated strings and are acceptable for a dev/internal tool.Test coverage (
spec/requests/client_errors_spec.rb)Two tests: valid payload (204 + log assertion) and invalid JSON (422). Both are correct and meaningful. The log assertion uses
expect(Rails.logger).to receive(:error).with(/CLIENT_ERROR.*TypeError/)which verifies the log format contract.Missing test: oversized payload -- The controller reads only 4096 bytes. There is no test verifying behavior when a payload exceeds that limit. What happens:
bodyis truncated,JSON.parselikely raisesJSON::ParserErroron incomplete JSON, returning 422. This is acceptable behavior but documenting it with a test would strengthen the contract.Missing test: non-string stack field -- As noted in point 4, sending
"stack": 123would raiseNoMethodError. A test would expose this.BLOCKERS
None. The new endpoint has test coverage (happy path + error case). No secrets committed. No unvalidated user input reaching SQL or HTML rendering -- data goes to logs only. No DRY violations in auth paths (there is no auth).
NITS
Robustness: Consider adding
.to_sbefore.truncate(500)on the stack field to handle non-string values gracefully:Test gap: An oversized-payload test and a malformed-types test (e.g.,
"stack": 123) would harden the contract, but not blocking.Log injection: If the Loki pipeline parses logs by newline, consider stripping
\nfrom interpolated fields, or switching to structured JSON logging for the[CLIENT_ERROR]line itself (consistent with the lograge JSON formatter already configured).ErrorSubscriber
context.inspect: In production,contextcould contain large objects. Considercontext.slice(:key1, :key2).inspector a size guard. Low risk for now.SOP COMPLIANCE
39-add-observabilitymatches issue #39project-landscaping-assistantandobservability-audit-2026-02-25but no plan slug was provided for this task (noted as "No plan slug" in the review request -- acceptable).envfiles in diffe807db6on this branchPROCESS OBSERVATIONS
VERDICT: APPROVED