Runtime Observability
Verified Scenario
This page is generated from an executable spec. An automated suite renders a fresh App from the current GoForj templates, applies every step below in order, and runs every verification command. If any step fails, the page does not ship.
Scenario 7 of 7 in the verified path. Plan on about 15 minutes.
This scenario follows the same application behavior through the surfaces operators use to trust a running App.
The goal is not to add new business behavior. The goal is to prove that HTTP requests, events, jobs, schedules, metrics, inspects, Lighthouse, and logs all describe the same runtime story.
What You Will Observe
route:listshows the HTTP surface.- API logs show request and lifecycle behavior.
- Metrics expose bounded labels for routes, jobs, queues, and schedules.
- Inspects capture request, job, scheduler, and CLI execution records.
- Lighthouse provides a first-party operator view over recent runtime state.
flowchart TD request["POST /api/v1/users"] --> route["route:list registered route"] request --> inspectHTTP["HTTP inspect"] request --> event["users.created"] event --> job["reports:generate job"] job --> inspectJob["job inspect"] schedule["reports:daily schedule"] --> inspectSchedule["scheduler inspect"] job --> metrics["metrics with queue and job labels"] schedule --> metrics inspectHTTP --> lighthouse["Lighthouse"] inspectJob --> lighthouse inspectSchedule --> lighthouse metrics --> lighthouse
Prerequisites
Complete these scenarios first:
- JSON API Route
- Cached User Profile
- File Upload to Storage
- Users Created Event
- Reports Generate Job
- Reports Daily Schedule
Enable metrics and Lighthouse when creating or configuring the App.
Golden Path State
Before this scenario, the App has routes, cache, storage, events, jobs, workers, and a schedule.
After this scenario, you should know where to prove each runtime boundary: route list for HTTP, logs for process behavior, metrics for bounded counters and timings, inspects for execution records, and Lighthouse for operator-facing runtime state.
Build and Verify
forj buildgo test ./...grep -Fx LIGHTHOUSE_INSPECT_ENABLED=true .env.localExpected output includes:
LIGHTHOUSE_INSPECT_ENABLED=true
forj route:listExpected output includes:
/api/v1/users/metrics
Trigger the Workflow
List the registered routes:
forj route:listWith the default process-local workerpool driver, start the combined App so API, Jobs, and scheduler runtimes share one process:
forj appThe built binary uses the same combined topology:
./bin/appTo run API, workers, and scheduler as separate processes, first select a shared queue driver as described in Reports Generate Job. Each command is a long-running process, so run one in each of three terminals:
forj apiforj workerforj schedulerCreate a user:
curl -X POST http://localhost:3000/api/v1/users \
-H 'Content-Type: application/json' \
-d '{"name":"Grace Hopper","email":"grace@example.test"}'Expected behavior:
- the API handles
POST /api/v1/users - the service publishes
users.created - the subscriber dispatches
reports:generate - the worker processes
reports:generate - storage receives a report artifact
Then check route output, process logs, metrics endpoints, inspect records, and Lighthouse for the same bounded names: /api/v1/users, users.created, reports:generate, and reports:daily.
Check Metrics
The combined local App exposes one shared metrics endpoint:
curl http://localhost:3000/metricsIn a split topology using a shared queue backend, each direct runtime owns a dedicated process metrics endpoint:
curl http://localhost:10000/metrics
curl http://localhost:10001/metrics
curl http://localhost:10002/metricsThese are the API, scheduler, and worker metrics listeners, respectively. The API still serves GET /metrics on port 3000, while the dedicated port keeps scrape traffic separate from application requests.
Look for bounded labels such as registered route, event topic, queue name, job name, scheduler job name, source, and status. Do not expect user IDs, emails, raw URLs, or storage filenames to appear as labels.
Useful evidence across the different metric families includes:
route="/api/v1/users"
topic="users.created"
job_name="reports:generate"
job_name="reports:daily"
queue="default"
source="jobs"
source="scheduler"Scheduler metrics use the bounded job_name label for the registered schedule name. Metric names can evolve with the metrics package and generated App version, but the label discipline must remain stable: bounded operational names, not user-controlled data.
Check Inspects
Open Lighthouse and inspect recent executions.
Look for records from:
- HTTP request handling
- queued job processing
- scheduler runs
- CLI commands such as
route:list
Each inspect should tell a bounded execution story: source runtime, duration, status, timeline events, and safe payload details where enabled.
Use inspect for the product surface. trace_id may still appear as a correlation field in logs or payloads.
Check Logs
Use logs to confirm lifecycle and failure behavior:
forj appWhen using a shared queue backend and separate processes, inspect the output from forj api, forj worker, and forj scheduler individually.
Good logs should answer:
- which runtime started
- which runtime is shutting down
- whether optional resources degraded
- whether a job or schedule failed
- which bounded runtime identity emitted the line
Logs should not be the only way to discover registered routes, queue depth, or scheduler state. Use route lists, metrics, inspects, and Lighthouse for those surfaces.
Follow the Schedule
Run the combined App with the temporary short interval from Reports Daily Schedule when testing locally. Separate scheduler and worker processes require a shared queue backend.
Expected evidence:
- scheduler logs show
reports:daily - scheduler metrics include the schedule name
- a scheduler inspect is retained by Lighthouse
- workers process one or more
reports:generatejobs - job metrics and job inspects use
reports:generate
This proves the schedule dispatches queued work instead of performing report generation inside scheduler bootstrap.
Operations
Operational notes:
- Use
route:listas the HTTP source of truth. - Use
forj appfor the normal localworkerpooltopology; split runtimes only after selecting a shared queue backend. - Use metrics for bounded counters and timings; do not put user IDs, raw URLs, emails, request IDs, or filenames in labels.
- Use inspect records and Lighthouse to follow request, job, scheduler, and CLI execution stories.
- Use logs to confirm lifecycle and failure behavior, not as the only route or queue inventory.
Troubleshooting
If no route appears, run forj build and then forj route:list.
If no job is processed, confirm the API and worker processes use a shared queue backend. workerpool is process-local; use Redis, SQL-backed queues, or another shared backend when API and worker run separately.
If metrics are empty, confirm metrics were enabled for the surface you are checking:
METRICS_HTTP_ENABLED=true
METRICS_QUEUE_ENABLED=true
METRICS_EVENTS_ENABLED=true
METRICS_SCHEDULER_ENABLED=trueLocal Apps enable inspect capture with LIGHTHOUSE_INSPECT_ENABLED=true in .env.local. If Lighthouse has no inspect records, confirm that setting is still active, Lighthouse is enabled, and the inspect buffer is not saturated.
Common Mistakes
Common mistakes
- Do not use metrics labels for user IDs, emails, raw paths, request IDs, or filenames.
- Do not treat Lighthouse as the only observability surface.
- Do not call inspects traces in user-facing docs.
- Do not rely only on logs to discover registered routes or queue state.
- Do not hide worker or scheduler startup inside constructors.
- Do not expect
workerpoolqueues to cross process boundaries. - Do not treat missing Lighthouse records as proof that work did not happen; confirm inspect configuration and buffer limits.
Next Steps
- Metrics explains metric surfaces.
- Inspects explains execution records.
- Lighthouse explains the operator UI.
- Production Checklist collects production readiness checks.
