docs: create-and-check-documentation
This commit is contained in:
committed by
Gerla, J. (Justin)
parent
f87c7fed03
commit
10a2c0c3cd
@@ -3,6 +3,19 @@ import {useCallback, useEffect, useRef, useState} from "react";
|
||||
import {applyPriorityPredicates, type PriorityFilterPredicate} from "../../utils/priorityFiltering.ts";
|
||||
import {cell, type Cell} from "../../utils/cellStore.ts";
|
||||
|
||||
/**
|
||||
* Represents a single log record emitted by the backend logging system.
|
||||
*
|
||||
* @property name - The name of the logger or source (e.g., `"agent.core"`).
|
||||
* @property message - The message content of the log record.
|
||||
* @property levelname - The human-readable severity level (e.g., `"INFO"`, `"ERROR"`).
|
||||
* @property levelno - The numeric severity value corresponding to `levelname`.
|
||||
* @property created - The UNIX timestamp (in seconds) when this record was created.
|
||||
* @property relativeCreated - The time (in milliseconds) since the logging system started.
|
||||
* @property reference - (Optional) A reference identifier linking related log messages.
|
||||
* @property firstCreated - Timestamp of the first log in this reference group.
|
||||
* @property firstRelativeCreated - Relative timestamp of the first log in this reference group.
|
||||
*/
|
||||
export type LogRecord = {
|
||||
name: string;
|
||||
message: string;
|
||||
@@ -15,29 +28,68 @@ export type LogRecord = {
|
||||
firstRelativeCreated: number;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type LogFilterPredicate = PriorityFilterPredicate<LogRecord> & { value: any };
|
||||
/**
|
||||
* A log filter predicate with priority support, used to determine whether
|
||||
* a log record should be displayed.
|
||||
*
|
||||
* This extends a general `PriorityFilterPredicate` and includes an optional
|
||||
* `value` field for UI metadata (e.g., selected log level or agent).
|
||||
*
|
||||
* @template T - The type of record being filtered (here, `LogRecord`).
|
||||
*/
|
||||
export type LogFilterPredicate = PriorityFilterPredicate<LogRecord> & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any };
|
||||
|
||||
/**
|
||||
* React hook that manages the lifecycle of log records, including:
|
||||
* - Receiving live log messages via Server-Sent Events (SSE),
|
||||
* - Applying priority-based filtering rules,
|
||||
* - Managing distinct logger names and reference-linked messages.
|
||||
*
|
||||
* Returns both the filtered logs (as reactive `Cell<LogRecord>` objects)
|
||||
* and a set of distinct logger names for use in UI components (e.g., Filters).
|
||||
*
|
||||
* @param filterPredicates - A `Map` of log filter predicates, keyed by ID or type.
|
||||
* @returns An object containing:
|
||||
* - `filteredLogs`: The currently visible (filtered) log messages.
|
||||
* - `distinctNames`: A set of all distinct logger names encountered.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { filteredLogs, distinctNames } = useLogs(activeFilters);
|
||||
* ```
|
||||
*/
|
||||
export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
/** Distinct logger names encountered across all logs. */
|
||||
const [distinctNames, setDistinctNames] = useState<Set<string>>(new Set());
|
||||
/** Filtered logs that pass all active predicates, stored as reactive cells. */
|
||||
const [filtered, setFiltered] = useState<Cell<LogRecord>[]>([]);
|
||||
|
||||
/** Persistent reference to the active EventSource connection. */
|
||||
const sseRef = useRef<EventSource | null>(null);
|
||||
/** Keeps a stable reference to the current filter map (avoids re-renders). */
|
||||
const filtersRef = useRef(filterPredicates);
|
||||
/** Stores all received logs (the unfiltered full history). */
|
||||
const logsRef = useRef<LogRecord[]>([]);
|
||||
|
||||
/** Map to store the first message for each reference, instance can be updated to change contents. */
|
||||
const firstByRefRef = useRef<Map<string, Cell<LogRecord>>>(new Map());
|
||||
|
||||
/**
|
||||
* Apply the filter predicates to a log record.
|
||||
* Apply all active filter predicates to a log record.
|
||||
* @param log The log record to apply the filters to.
|
||||
* @returns `true` if the record passes.
|
||||
* @returns `true` if the record passes all filters; otherwise `false`.
|
||||
*/
|
||||
const applyFilters = useCallback((log: LogRecord) =>
|
||||
applyPriorityPredicates(log, [...filtersRef.current.values()]), []);
|
||||
|
||||
/** Recomputes the entire filtered list. Use when filter predicates change. */
|
||||
/**
|
||||
* Fully recomputes the filtered log list based on the current
|
||||
* filter predicates and historical logs.
|
||||
*
|
||||
* Should be invoked whenever the filter map changes.
|
||||
*/
|
||||
const recomputeFiltered = useCallback(() => {
|
||||
const newFiltered: Cell<LogRecord>[] = [];
|
||||
firstByRefRef.current = new Map();
|
||||
@@ -49,6 +101,7 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
firstRelativeCreated: message.relativeCreated,
|
||||
});
|
||||
|
||||
// Handle reference grouping: update the first message in the group.
|
||||
if (message.reference) {
|
||||
const first = firstByRefRef.current.get(message.reference);
|
||||
if (first) {
|
||||
@@ -59,14 +112,14 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
firstRelativeCreated: prev.firstRelativeCreated ?? prev.relativeCreated,
|
||||
}));
|
||||
|
||||
// Don't add it to the list again
|
||||
continue;
|
||||
continue; // Don't add it to the list again (it's a duplicate).
|
||||
} else {
|
||||
// Add the first message with this reference to the registry
|
||||
firstByRefRef.current.set(message.reference, messageCell);
|
||||
}
|
||||
}
|
||||
|
||||
// Include only if it passes current filters.
|
||||
if (applyFilters(message)) {
|
||||
newFiltered.push(messageCell);
|
||||
}
|
||||
@@ -75,20 +128,23 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
setFiltered(newFiltered);
|
||||
}, [applyFilters, setFiltered]);
|
||||
|
||||
// Reapply filters to all logs, only when filters change
|
||||
// Re-filter all logs whenever filter predicates change.
|
||||
useEffect(() => {
|
||||
filtersRef.current = filterPredicates;
|
||||
recomputeFiltered();
|
||||
}, [filterPredicates, recomputeFiltered]);
|
||||
|
||||
/**
|
||||
* Handle a new log message. Updates the filtered list and to the full history.
|
||||
* @param message The new log message.
|
||||
* Handles a newly received log record.
|
||||
* Updates the full log history, distinct names set, and filtered log list.
|
||||
*
|
||||
* @param message - The new log record to process.
|
||||
*/
|
||||
const handleNewMessage = useCallback((message: LogRecord) => {
|
||||
// Add to the full history for re-filtering on filter changes
|
||||
// Store in complete history for future refiltering.
|
||||
logsRef.current.push(message);
|
||||
|
||||
// Track distinct logger names.
|
||||
setDistinctNames((prev) => {
|
||||
if (prev.has(message.name)) return prev;
|
||||
const newSet = new Set(prev);
|
||||
@@ -96,12 +152,14 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// Wrap in a reactive cell for UI binding.
|
||||
const messageCell = cell<LogRecord>({
|
||||
...message,
|
||||
firstCreated: message.created,
|
||||
firstRelativeCreated: message.relativeCreated,
|
||||
});
|
||||
|
||||
// Handle reference-linked updates.
|
||||
if (message.reference) {
|
||||
const first = firstByRefRef.current.get(message.reference);
|
||||
if (first) {
|
||||
@@ -112,20 +170,28 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
|
||||
firstRelativeCreated: prev.firstRelativeCreated ?? prev.relativeCreated,
|
||||
}));
|
||||
|
||||
// Don't add it to the list again
|
||||
return;
|
||||
return; // Do not duplicate reference group entries.
|
||||
} else {
|
||||
// Add the first message with this reference to the registry
|
||||
firstByRefRef.current.set(message.reference, messageCell);
|
||||
}
|
||||
}
|
||||
|
||||
// Only append if message passes filters.
|
||||
if (applyFilters(message)) {
|
||||
setFiltered((curr) => [...curr, messageCell]);
|
||||
}
|
||||
}, [applyFilters, setFiltered]);
|
||||
|
||||
/**
|
||||
* Initializes the SSE (Server-Sent Events) stream for real-time logs.
|
||||
*
|
||||
* Subscribes to messages from the backend logging endpoint and
|
||||
* dispatches each message to `handleNewMessage`.
|
||||
*
|
||||
* Cleans up the EventSource connection when the component unmounts.
|
||||
*/
|
||||
useEffect(() => {
|
||||
// Only create one SSE connection for the lifetime of the hook.
|
||||
if (sseRef.current) return;
|
||||
|
||||
const es = new EventSource("http://localhost:8000/logs/stream");
|
||||
|
||||
Reference in New Issue
Block a user