import {useEffect, useRef, useState} from "react"; import {create} from "zustand"; import formatDuration from "../../utils/formatDuration.ts"; import {type LogFilterPredicate, type LogRecord, useLogs} from "./useLogs.ts"; import Filters from "./Filters.tsx"; import {type Cell, useCell} from "../../utils/cellStore.ts"; import styles from "./Logging.module.css"; /** * Zustand store definition for managing user preferences related to logging. * * Includes flags for toggling relative timestamps and automatic scroll behavior. */ type LoggingSettings = { /** Whether to display log timestamps as relative (e.g., "2m 15s ago") instead of absolute. */ showRelativeTime: boolean; /** Updates the `showRelativeTime` setting. */ setShowRelativeTime: (showRelativeTime: boolean) => void; /** Whether the log view should automatically scroll to the newest entry. */ scrollToBottom: boolean; /** Updates the `scrollToBottom` setting. */ setScrollToBottom: (scrollToBottom: boolean) => void; }; /** * Global Zustand store for logging UI preferences. */ const useLoggingSettings = create((set) => ({ showRelativeTime: false, setShowRelativeTime: (showRelativeTime: boolean) => set({ showRelativeTime }), scrollToBottom: true, setScrollToBottom: (scrollToBottom: boolean) => set({ scrollToBottom }), })); /** * Renders a single log message entry with colored level indicators and timestamp formatting. * * This component automatically re-renders when the underlying log record (`recordCell`) * changes. It also triggers the `onUpdate` callback whenever the record updates (e.g., for auto-scrolling). * * @param recordCell - A reactive `Cell` containing a single `LogRecord`. * @param onUpdate - Optional callback triggered when the log entry updates. * @returns A JSX element displaying a formatted log message. */ function LogMessage({ recordCell, onUpdate, }: { recordCell: Cell, onUpdate?: () => void, }) { const { showRelativeTime, setShowRelativeTime } = useLoggingSettings(); const record = useCell(recordCell); /** * Normalizes the log level number to a multiple of 10, * for which there are CSS styles. (e.g., INFO = 20, ERROR = 40). */ const normalizedLevelNo = (() => { // By default, the highest level is 50 (CRITICAL). Custom levels can be higher, but we don't have more critical color. if (record.levelno >= 50) return 50; return Math.round(record.levelno / 10) * 10; })(); /** Simplifies the logger name by showing only the last path segment. */ const normalizedName = record.name.split(".").pop() || record.name; // Notify parent component (e.g. for scroll updates) when this record changes. useEffect(() => { if (onUpdate) onUpdate(); }, [record, onUpdate]); return
{record.levelname} setShowRelativeTime(!showRelativeTime)} >{showRelativeTime ? formatDuration(record.relativeCreated) : new Date(record.created * 1000).toLocaleTimeString() }
{normalizedName} {record.message}
; } /** * Displays a scrollable list of log messages. * * Handles: * - Auto-scrolling when new messages arrive. * - Allowing users to scroll manually and disable auto-scroll. * - A floating "Scroll to bottom" button when not at the bottom. * * @param recordCells - Array of reactive log records to display. * @returns A scrollable log list component. */ function LogMessages({ recordCells }: { recordCells: Cell[] }) { const scrollableRef = useRef(null); const lastElementRef = useRef(null) const { scrollToBottom, setScrollToBottom } = useLoggingSettings(); // Disable auto-scroll if the user manually scrolls. useEffect(() => { if (!scrollableRef.current) return; const currentScrollableRef = scrollableRef.current; const handleScroll = () => setScrollToBottom(false); currentScrollableRef.addEventListener("wheel", handleScroll); currentScrollableRef.addEventListener("touchmove", handleScroll); return () => { currentScrollableRef.removeEventListener("wheel", handleScroll); currentScrollableRef.removeEventListener("touchmove", handleScroll); } }, [scrollableRef, setScrollToBottom]); /** * Scrolls the last log message into view if auto-scroll is enabled, * or if forced (e.g., user clicks "Scroll to bottom"). * * @param force - If true, forces scrolling even if `scrollToBottom` is false. */ function scrollLastElementIntoView(force = false) { if ((!scrollToBottom && !force) || !lastElementRef.current) return; lastElementRef.current.scrollIntoView({ behavior: "smooth" }); } return
    {recordCells.map((recordCell, i) => (
  1. ))}
{!scrollToBottom && }
; } /** * Top-level logging panel component. * * Combines: * - The `Filters` component for adjusting log visibility. * - The `LogMessages` component for displaying filtered logs. * - Zustand-managed UI settings (auto-scroll, timestamp display). * * This component uses the `useLogs` hook to fetch and filter logs based on * active predicates, and re-renders automatically as new logs arrive. * * @returns The complete logging UI as a React element. */ export default function Logging() { const [filterPredicates, setFilterPredicates] = useState(new Map()); const { filteredLogs, distinctNames } = useLogs(filterPredicates) return

Logs

; }