Captured: 2026-07-15 · Implemented: w3/m28 · Severity/filter follow-up: w8/m48, 2026-10-02
Render's current List logs API uses one repeatable resource filter for every log-producing resource and explicitly includes Postgres alongside servers, cron jobs, jobs, Key Value, and workflows. Its documented controls include startTime, endTime, direction, resource, instance, level, text, and limit. The 2026-10-02 API-reference recheck confirms that severity filters apply to this shared resource query. Render's logging guide shows a default recent window, time-range selection, search, and per-instance filtering. The managed Postgres guide states that database and slow-query output is available in logs; queries longer than two seconds are logged by default.
The 2026-07-15 live dashboard walk captured a dedicated Postgres Logs tab. The baseline screenshots are render-walk-postgres-logs.png and bex-walk-postgres.png in the walk capture set; at that point bex had no database-log source or tab. After implementation, a fresh Chrome capture against the deterministic local bex fixture is .playwright-mcp/bex-walk-postgres-logs-current.png: it shows the directly linked active Logs tab, Postgres-attributed lines, Last hour, All instances, and search controls. Backend adapter tests and dashboard component/route tests cover the nonvisual behavior; the shared walk cluster was not an implementation dependency.
- REST
GET /v1/logs?resource=dpg-…, GraphQLlogs(resource: "dpg-…"), and MCPlist_logs(resource: ["dpg-…"])all call the same database-scoped core query. The dedicated Postgres REST, GraphQL, and MCP compatibility adapters remain available and delegate typed ids to that same production core. - Supported Postgres filters are RFC3339 start/end time, direction, text search, instance, severity level, and limit. Level values use the same normalized Render aliases and OR/
*wildcard semantics as App queries: ERROR/FATAL/PANIC records matcherror. Service/request concepts (type, host, HTTP method/status/path, build, and pre-deploy) return a named 400 instead of being ignored. Instance discovery retains its authorized-pod fallback; level-value discovery requires the durable label source. GET /v1/logs(and GraphQLlogs, MCPlist_logs) pages follow the chosen direction exactly like service logs: backward (the default) lists the most recent first, forward the oldest first (w8/m116). The dedicated Postgres adapters keep their unpaged list oldest-first after the direction applies the limit. Each line carriesresource=dpg-…,type=postgres, the CNPG pod asinstance, andcontainer=postgreswhen supplied by the source.- The dashboard detail route owns its selected tab in the URL:
/databases/<dpg-id>?tab=logs. Its controls are the shared range ladder (Last hour by default,w5/030), observed CNPG instance, and debounced text search. Loading, empty, filtered empty, unauthorized, unconfigured-source, and upstream-error states are distinct. The first page holds the newest 100 lines of the range; scrolling the pane to its top (or Load older in the truncation notice) followsnextStartTime/nextEndTimeback untilhasMoreis false, and a capped view says so, the same paging as the service Logs tab (w4/m107, extended to datastores inw4/m136). REST, GraphQL, and MCP return the same envelope fordpg-resources, and one adapter test walks the cursor chain on all three.
AuthorizeDatabase(ctx, can_view_logs, dpg-id)completes before a Loki or Kubernetes pod selector is built. The Database CR's tenant label, not its namespace or display name, selects the authorization object.- The durable selector is exact equality on
{namespace="…", database="dpg-…"}.databasecomes from the already authorized Database CR's immutable metadata name, never a free-form caller selector. - The live fallback selects pods by CNPG's exact
cnpg.io/cluster=dpg-…label and reads only thepostgrescontainer. - The operator stamps
app.bex.co/component=databaseinto tenant Database CNPG pod metadata. Alloy requires that marker before ingesting the stream, so platform/auth/control-plane CNPG clusters are excluded. It derives the Lokidatabaselabel fromcnpg.io/cluster; it never uses the mutable display name. - Tests seed two database pod sets and prove one query cannot mix the other, and prove REST, GraphQL, and MCP reject a foreign-workspace database before the history source is called.
With BEX_LOKI_URL, Loki history survives CNPG pod restarts. Without Loki, bex reads the current CNPG pod log buffer: search, range, direction, limit, instance, and parsed CNPG severity still work within the bounded raw tail, but older and restarted-pod history is unavailable. A severity filter runs before the result cap; it does not enlarge the pod read into an unbounded scan. With neither history nor pod-log source the APIs return 503 logs source not configured, and the dashboard explains that the installation has no database-log source. An unreachable configured Loki surfaces its error; bex does not silently switch sources and disguise an outage.
Live subscription (w5/m139). Render's /logs/subscribe takes the same resource as /logs ("the id of a server, cronjob, job, postgres, redis…"), so render logs -r dpg-… --tail streams: bex follows the CNPG pods' postgres container with this page's parsing (instance-manager JSON dropped, records unwrapped with their level) and the same instance/text/level filters, over WebSocket, SSE or NDJSON. The dashboard Logs tab has a Live switch (off by default) that merges the tail into the history beside it. Walked on dev-5 with the pinned CLI: a forced select 1/0 arrived as ERROR: division by zero, with no instance-manager lines.