Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

LogsViewer

Follow compute logs with ANSI colors, level filters, search, and a retention cap.

LogsViewer is a log pane that stays usable while output is still arriving. Rows are virtualized, so a long session scrolls at the same speed as a short one, and the viewer follows the tail until you scroll away from it.

Compute logs

Streaming
480 of 480 lines
Filter log levels
15:02:22.526infocompute-0database system is ready to accept connections
15:02:37.526debugcompute-0checkpoint starting: time
15:02:52.526infocompute-0connection received: host=10.0.14.22 port=54344
15:03:07.526infocompute-0connection authorized: user=app_owner database=neondb SSL enabled
15:03:22.526debugcompute-0parse <unnamed>: select id, email from users where id = $1
15:03:37.526warncompute-0temporary file: path "base/pgsql_tmp/pgsql_tmp8321.0", size 4194304
15:03:52.526errorcompute-0duplicate key value violates unique constraint "users_email_key"
15:04:07.526errorcompute-0detail: Key (email)=(ada@example.com) already exists.
15:04:22.526infocompute-0autovacuum: processing database "neondb"
15:04:37.526warncompute-0compute is approaching the autoscaling ceiling (7.4 of 8 CU)
15:04:52.526infocompute-0checkpoint complete: wrote 214 buffers (1.3%)
15:05:07.526debugcompute-0statement duration: 2.481 ms
15:05:22.526infocompute-0database system is ready to accept connections
15:05:37.526debugcompute-0checkpoint starting: time
15:05:52.526infocompute-0connection received: host=10.0.14.22 port=54344
15:06:07.526infocompute-0connection authorized: user=app_owner database=neondb SSL enabled
15:06:22.526debugcompute-0parse <unnamed>: select id, email from users where id = $1
15:06:37.526warncompute-0temporary file: path "base/pgsql_tmp/pgsql_tmp8321.0", size 4194304
15:06:52.526errorcompute-0duplicate key value violates unique constraint "users_email_key"
15:07:07.526errorcompute-0detail: Key (email)=(ada@example.com) already exists.
15:07:22.526infocompute-0autovacuum: processing database "neondb"
15:07:37.526warncompute-0compute is approaching the autoscaling ceiling (7.4 of 8 CU)
15:07:52.526infocompute-0checkpoint complete: wrote 214 buffers (1.3%)
15:08:07.526debugcompute-0statement duration: 2.481 ms
15:08:22.526infocompute-0database system is ready to accept connections
15:08:37.526debugcompute-0checkpoint starting: time
15:08:52.526infocompute-0connection received: host=10.0.14.22 port=54344
15:09:07.526infocompute-0connection authorized: user=app_owner database=neondb SSL enabled
15:09:22.526debugcompute-0parse <unnamed>: select id, email from users where id = $1
15:09:37.526warncompute-0temporary file: path "base/pgsql_tmp/pgsql_tmp8321.0", size 4194304

Behavior

  • The viewer follows new output while you sit at the bottom. Scrolling up detaches it and a pill reports how many lines arrived while you were reading; scrolling back to the bottom reattaches it.
  • Only the rows in view are rendered, plus a small overscan, so 50,000 lines cost the same as 50.
  • maxLines caps retention (5,000 by default) and the header states when the cap is trimming output.
  • ANSI escape codes render as color and weight through anser. Text is parsed into spans, never injected as HTML.
  • Level filters, the time range, and search all compose. Search matches the timestamp, source, and the log text with escape codes stripped, so colored text stays findable.
  • The time range (5 minutes to 24 hours) measures back from the newest line, not the wall clock, so a paused stream keeps showing the same window instead of draining as time passes. The control only appears when lines carry at.
  • Scroll areas use the house scrollbar: thin, rounded, and transparent-tracked, on both axes. While the viewer is following, the scrollbar hides, since the thumb would report a position the reader does not control and would slide on every new line. It returns the moment you scroll and take over.
  • Long lines scroll sideways inside the pane; the page never gains horizontal overflow.

Install

npx shadcn@latest add https://ui.neon.com/r/logs-viewer.json

Usage

import { LogsViewer } from "@/components/neon-ui/logs-viewer";

<LogsViewer lines={lines} isStreaming={isStreaming} title="Compute logs" />;

Wiring to Neon

useLogsStream reads a server-sent events endpoint that emits one JSON line per message:

const { lines, isStreaming, error } = useLogsStream({
  endpoint: "/api/logs/stream?branch=br-main&compute=compute-0",
});

The endpoint belongs to the application. Neon credentials stay on the server, which also decides retention and who may read a compute’s output.

Line shape

interface LogLine {
  id: string;
  message: string;
  level?: "debug" | "info" | "warn" | "error";
  timestamp?: string;
  at?: string;
  source?: string;
}

timestamp is display-ready, so the application controls precision and time zone. Set at to the ISO timestamp to enable time-range filtering. message may contain ANSI escape codes.

Props

PropType
linesLogLine[]

Log lines, oldest first.

TypeLogLine[]
title?string

Heading and accessible name for the log region.

Typestring
Default"Logs"
maxLines?number

Retention cap. Older lines are dropped.

Typenumber
Default5000
rowHeight?number

Row height in pixels, used for virtualization math.

Typenumber
Default20
visibleRows?number

Height of the pane, counted in rows.

Typenumber
Default18
follow?boolean

Controlled follow-tail state.

Typeboolean
defaultFollow?boolean

Initial follow state for uncontrolled usage.

Typeboolean
Defaulttrue
onFollowChange?(follow: boolean) => void

Fires when following starts or stops.

Type(follow: boolean) => void
query?string

Controlled search text.

Typestring
levels?LogLevel[]

Controlled set of enabled levels.

TypeLogLevel[]
range?"all" | "5m" | "15m" | "1h" | "24h"

Controlled time range, measured back from the newest line.

Type"all" | "5m" | "15m" | "1h" | "24h"
defaultRange?"all" | "5m" | "15m" | "1h" | "24h"

Initial range for uncontrolled usage.

Type"all" | "5m" | "15m" | "1h" | "24h"
Default"all"
onRangeChange?(range: TimeRange) => void

Fires when the time range changes.

Type(range: TimeRange) => void
isStreaming?boolean

Marks the stream as live in the header.

Typeboolean
Defaultfalse
isLoading?boolean

Renders the loading state.

Typeboolean
Defaultfalse
error?Error | string | null

Renders an error state instead of the pane.

TypeError | string | null

Accessibility

The pane is a log region with an accessible name, and it is focusable so keyboard users can scroll it. Search has a real label and a clear button. Level filters are pressed buttons inside a fieldset, and the time range uses the shared select, so its menu is keyboard driven and styled with the rest of the system. Level is carried by text as well as color. The follow pill states its purpose and the pending line count. The streaming dot and the pill entrance both stop under reduced motion.