Skip to content

Observability Logging

Updated: 2026-07-30

Barbelic keeps lightweight operational logs for slow-data and import diagnosis. Logs must explain where time was spent without exposing raw workout payloads, uploaded file contents, OAuth tokens, or provider raw payloads.

Correlation Fields

Use these fields consistently:

  • operationId / operation_id: one generated operation identifier.
  • operationType: broad category such as rpc or wodup_import.
  • operationName: concrete operation such as get_home_dashboard or wodup-start-import.
  • durationMs / duration_ms: elapsed time in milliseconds.
  • responseBytes: approximate JSON response size on the frontend.
  • errorCode / error_code: stable error code when available.
  • batchId / batch_id: Wodup import batch id.
  • job_id: stats refresh job id.

Persistent Application Error Events

Authenticated browser, React render, returned server/RPC, network, and local draft persistence failures are sent through record_client_error_event(jsonb) into the private client_error_events table.

The application installs these capture boundaries:

  • browser error and unhandledrejection events;
  • failed non-RPC fetch responses and network rejections (RPC failures are recorded once at the repository boundary — reads through callScreenRpc, writes through callMutationRpc, each with the RPC's real name);
  • React error boundaries;
  • screen RPC and strict response-adapter failures;
  • mutation RPC failures at callMutationRpc (#664 Phase 2) and draft persistence failures;
  • every user-facing error message via the reportedUserError gate (#664 Phase 1): copy is returned only after the unreported error is captured, with PUBLIC_ERROR_MESSAGES codes excluded as expected user errors, and pre-RPC client invariant violations classified as client_contract_error via clientContractError.

A report-once marker (owner-scoped, including the cause chain of wrapped errors) guarantees that overlapping boundaries store one event per error object.

The browser keeps at most 50 unsent events in sessionStorage["barbelic-pending-error-events-v1"] and retries after an authenticated session or restored connectivity is available, with bounded exponential backoff after transient failures. Pending entries are partitioned by authenticated user; an account switch cannot submit the previous account's entry under the new auth.uid(). Invalid or permanently rejected client envelopes are discarded from transport state so they cannot block later diagnostics. Normal duplicate events are suppressed for 30 seconds and browser ingestion is capped at 20 events per minute. The database independently caps authenticated ingestion at 60 events per minute and 1,000 per 24 hours.

The server binds every row to auth.uid() and verifies the transport's expected_user_id. Normal users cannot directly read, insert, update, or delete the table; service-role diagnostics can read it. Anonymous ingestion is intentionally disabled. Replaying the same client event ID with different content is rejected using a server-computed payload hash.

Free-form exception messages and URL/resource paths are never part of the server ingest contract. Stored rows contain only bounded classifications, error codes, contract field paths, operation keys, numeric status/timing data, and asset-basename/line/column stack frames. Tokens, authorization data, emails, titles, file names, query strings, request/response bodies, workout payloads, provider identifiers, and raw user-agent data are excluded.

User-facing error surfaces render only application-owned copy. Raw provider, RPC, response-contract, and import error strings stay out of normal and admin workflow screens; operational details are inspected in private server logs.

No TTL, purge RPC, cron job, or automatic deletion policy is installed during development. Stored rows remain until an explicit manual retention decision is made. The bounded browser pending queue is transport state, not the server log.

Frontend Debug Rings

Screen RPC calls write both compatibility RPC logs and the general operation log.

The in-memory rings are updated synchronously. sessionStorage is hydrated once per ring and whole-ring persistence is coalesced onto an idle callback (or a timer fallback), so RPC completion does not repeatedly parse and stringify both history buffers before the screen can render.

Storage:

  • globalThis.__liftGuildRpcDebug
  • sessionStorage["barbelic-rpc-debug-log"]
  • globalThis.__liftGuildOperationDebug
  • sessionStorage["barbelic-operation-debug-log"]

Console output is opt-in:

  • ?debug-rpc=1 or ?lg-rpc-debug=1
  • ?debug-ops=1 or ?lg-operation-debug=1
  • localStorage["barbelic-rpc-debug"] = "1"
  • localStorage["barbelic-operation-debug"] = "1"

Required screen RPC fields:

  • event: screen_rpc:started, screen_rpc:completed, or screen_rpc:failed
  • operationId
  • operationType: "rpc"
  • operationName
  • rpcName
  • durationMs
  • responseBytes
  • paramKeys
  • errorCode and safe error object on failure

Wodup import frontend events:

  • wodup_import_upload:started|completed|failed
  • wodup_import_start:started|completed|failed
  • wodup_import_poll:completed|failed

These events may include batchId, file name, file size, import status, and counts. They must not include raw JSONL text or raw provider payloads.

Completed-workout stats dispatch events:

  • stats_refresh_dispatch:started
  • stats_refresh_dispatch:coalesced
  • stats_refresh_dispatch:completed
  • stats_refresh_dispatch:failed

These include the source operation and session id, but never the workout payload. coalesced means another same-user write joined the active bounded drain instead of starting a parallel processor. A failed dispatch is logged as a warning because the canonical write has already committed and the database cron job remains the recovery path.

Edge Function Logs

Vercel authentication handlers emit structured [barbelic server error] entries for configuration, provider exchange, and unexpected request failures. Those entries contain only an event key, timestamp, error name/code, and safe numeric metadata; arbitrary exception messages, provider response text, and credentials are excluded. Their HTTP/native error responses use only application-owned copy, so provider diagnostics do not become error pages.

wodup-start-import, wodup-process-import-jobs, and stats-process-refresh-jobs write structured server logs with:

  • operation_id
  • batch_id
  • user_id
  • status
  • duration_ms on terminal events
  • error_code on failures
  • stage counts such as session_count, session_exercise_count, and set_count

The frontend sends operation_id to the Edge Function in the request body and x-lift-guild-operation-id header, so a failed start call can be matched between browser logs and Supabase function logs.

Wodup import server events:

  • wodup_import:received
  • wodup_import:batch_loaded
  • wodup_import:queued
  • wodup_import:worker_dispatched
  • wodup_import:worker_dispatch_failed
  • wodup_import:worker_received
  • wodup_import:claimed
  • wodup_import:normalized
  • wodup_import:staged
  • wodup_import:importing
  • wodup_import:canonical_completed
  • wodup_import:completed
  • wodup_import:worker_completed
  • wodup_import:worker_job_failed
  • wodup_import:failed

The start request should usually end around wodup_import:queued and wodup_import:worker_dispatched. Longer normalization and canonical import work runs in wodup-process-import-jobs while the UI polls wodup_import_batches.

Stats refresh worker events:

  • stats_refresh:worker_received
  • stats_refresh:worker_completed
  • stats_refresh:worker_failed

DB Stats Job Logs

user_exercise_stats_refresh_jobs remains the write-side lifecycle table. For operational reads, use:

sql
select *
from public.user_exercise_stats_refresh_job_observability
order by updated_at desc
limit 50;

The view exposes:

  • job_id
  • user_id
  • event_type
  • source_ref
  • status
  • operation_id
  • duration_ms
  • error_code
  • error_message
  • processing_started_at
  • finished_at

The view is not granted to normal authenticated app users. It is intended for service/admin diagnostics.