import React, { useEffect, useState } from "react"; import { ErrorWithLabel } from "../rqbit-web"; import { ErrorComponent } from "./ErrorComponent"; interface LogStreamProps { httpApiBase: string; maxLines?: number; } interface Line { id: number; content: string; } const mergeBuffers = (a1: Uint8Array, a2: Uint8Array): Uint8Array => { const merged = new Uint8Array(a1.length + a2.length); merged.set(a1); merged.set(a2, a1.length); return merged; }; const streamLogs = ( httpApiBase: string, addLine: (text: string) => void, setError: (error: ErrorWithLabel | null) => void ): (() => void) => { const controller = new AbortController(); const signal = controller.signal; let canceled = true; const cancel = () => { console.log("cancelling fetch"); canceled = true; controller.abort(); }; const run = async () => { let response = null; try { response = await fetch(httpApiBase + "/stream_logs", { signal }); } catch (e: any) { if (canceled) { return; } setError({ text: "network error fetching logs", details: { text: e.toString(), }, }); return null; } if (!response.ok) { let text = await response.text(); setError({ text: "error fetching logs", details: { statusText: response.statusText, text, }, }); } if (!response.body) { setError({ text: "error fetching logs: ReadableStream not supported.", }); throw new Error("ReadableStream not supported."); } const reader = response.body.getReader(); let buffer = new Uint8Array(); while (true) { const { done, value } = await reader.read(); if (done) { // Handle stream completion or errors break; } buffer = mergeBuffers(buffer, value); while (true) { const newLineIdx = buffer.indexOf(10); if (newLineIdx === -1) { break; } let lineBytes = buffer.slice(0, newLineIdx); let line = new TextDecoder().decode(lineBytes); addLine(line); buffer = buffer.slice(newLineIdx + 1); } } }; run(); return cancel; }; const SplitByLevelRegexp = new RegExp( /(.*?) +(INFO|WARN|TRACE|ERROR|DEBUG) +(.*)/ ); const LogLine = ({ line }: { line: string }) => { line.split; const getClassNameByLevel = (level: string) => { switch (level) { case "INFO": return "text-success"; case "WARN": return "text-warning"; case "ERROR": return "text-danger"; case "DEBUG": return "text-primary"; default: return "text-secondary"; } }; const getContent = () => { let match = line.match(SplitByLevelRegexp); if (!match) { return line; } const [beforeLevel, level, afterLevel] = match.slice(1); return ( <> {beforeLevel} {level} {afterLevel} > ); }; return (
{getContent()}
); }; export const LogStream: React.FC