All projects

Xata Logs Viewer

NewShipped

A log inspection tool for the Xata console, built for large volumes with filtering, virtualization and keyboard navigation.

Xata Logs Viewer

Branch logs started as a plain table. Over roughly six weeks I built it into a real inspection tool: filterable, virtualized, keyboard navigable, and able to remember how each developer prefers to read their logs. Part of the Xata Console.

Inspecting a log entry, filtering with chips and the sidebar, then switching the display timezone.

Key Contributions:

  • Detail panel: Added an inspectable log view with raw JSON and structured field tabs, shown as a split pane on desktop and a sheet on mobile, with previous and next navigation.
  • Filtering: Built a resizable filter panel with active counts per section, then layered chip based search for level, instance and process on top, backed by shared state so the two representations cannot disagree.
  • Body search: Added optional message search with plain text and regex modes, surfaced directly in the search input.
  • Virtualization: Replaced full list rendering with a virtualized list that keeps around thirty rows in the DOM at any volume, with scroll based paging and a manual fallback when the list does not overflow.
  • Preference persistence: Introduced a useLocalStorageState hook that takes a key plus a zod schema, so stale or corrupt values fall back to defaults and types are inferred. All keys live in a central registry.
  • Timezone handling: Added a UTC and local toggle with a GMT offset indicator on every timestamp, backed by a shared date helper.
  • Keyboard and accessibility: Made row actions appear on focus rather than hover only, added arrow key navigation between rows, and named every icon-only control.
  • Package boundaries: Moved the viewer into the app as feature code with a single consumer, while extracting the genuinely reusable filter primitives back into the shared component package.
Logs viewer with the filter sidebar, a virtualized log list and the detail panel for a selected entry
Detail panel with structured fields, alongside the filter sidebar and the virtualized list.
Logs viewer search input with level and instance filter chips and a suggestions dropdown
Chip based search and the filter sidebar share one state, so they always agree.

Technical Impact:

Virtualization was the enabling change. It made log volume a non issue for the browser, which in turn made features like infinite scroll, row selection and scroll into view worth building at all. The localStorage pattern introduced here became the app's general approach to persisted UI state, with schema validation preventing a stale value from breaking a page. The package boundary work is the piece I would point to first: the viewer moved out of the shared library, the generic filter parts moved in, and both ended up where they belonged.

Project Structure:

  • Viewer: virtualized list, row actions, display toggles for single line and compact modes
  • Filters: sidebar panel, chip based search, regex body search, synced filter state
  • Detail panel: JSON and table tabs, per field copy, prev and next navigation
  • Preferences: schema validated localStorage registry for columns, density and timezone