Separately format time, json and message of debug stream log.
This commit is contained in:
17
components/Code.tsx
Normal file
17
components/Code.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { styled } from "../stitches.config";
|
||||||
|
|
||||||
|
const Code = styled("pre", {
|
||||||
|
m: 0,
|
||||||
|
wordBreak: "break-all",
|
||||||
|
fontFamily: '$monospace',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
variants: {
|
||||||
|
fluid: {
|
||||||
|
true: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Code;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import { Select } from ".";
|
import { Select } from ".";
|
||||||
import state from "../state";
|
import state, { ILog } from "../state";
|
||||||
|
import { extractJSON } from "../utils/json";
|
||||||
import LogBox from "./LogBox";
|
import LogBox from "./LogBox";
|
||||||
import Text from "./Text";
|
|
||||||
|
|
||||||
const DebugStream = () => {
|
const DebugStream = () => {
|
||||||
const snap = useSnapshot(state);
|
const snap = useSnapshot(state);
|
||||||
@@ -16,7 +16,6 @@ const DebugStream = () => {
|
|||||||
|
|
||||||
const renderNav = () => (
|
const renderNav = () => (
|
||||||
<>
|
<>
|
||||||
<Text css={{ mx: "$2", fontSize: "inherit" }}>Account: </Text>
|
|
||||||
<Select
|
<Select
|
||||||
instanceId="debugStreamAccount"
|
instanceId="debugStreamAccount"
|
||||||
placeholder="Select account"
|
placeholder="Select account"
|
||||||
@@ -24,11 +23,31 @@ const DebugStream = () => {
|
|||||||
hideSelectedOptions
|
hideSelectedOptions
|
||||||
value={selectedAccount}
|
value={selectedAccount}
|
||||||
onChange={acc => setSelectedAccount(acc as any)}
|
onChange={acc => setSelectedAccount(acc as any)}
|
||||||
css={{ width: "30%" }}
|
css={{ width: "100%" }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const prepareLog = useCallback((str: any): ILog => {
|
||||||
|
if (typeof str !== "string") throw Error("Unrecognized debug log stream!");
|
||||||
|
|
||||||
|
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))\ ?([\s\S]*)/m);
|
||||||
|
const [_, time, msg] = match || [];
|
||||||
|
|
||||||
|
const jsonData = extractJSON(msg);
|
||||||
|
const timestamp = time ? new Date(time) : undefined;
|
||||||
|
const message = !jsonData
|
||||||
|
? msg
|
||||||
|
: msg.slice(0, jsonData.start) + msg.slice(jsonData.end + 1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "log",
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
jsonData: jsonData?.result,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const account = selectedAccount?.value;
|
const account = selectedAccount?.value;
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@@ -52,10 +71,7 @@ const DebugStream = () => {
|
|||||||
};
|
};
|
||||||
const onMessage = (event: any) => {
|
const onMessage = (event: any) => {
|
||||||
if (!event.data) return;
|
if (!event.data) return;
|
||||||
state.debugLogs.push({
|
state.debugLogs.push(prepareLog(event.data));
|
||||||
type: "log",
|
|
||||||
message: event.data,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.addEventListener("open", onOpen);
|
socket.addEventListener("open", onOpen);
|
||||||
@@ -67,10 +83,11 @@ const DebugStream = () => {
|
|||||||
socket.removeEventListener("open", onOpen);
|
socket.removeEventListener("open", onOpen);
|
||||||
socket.removeEventListener("close", onError);
|
socket.removeEventListener("close", onError);
|
||||||
socket.removeEventListener("message", onMessage);
|
socket.removeEventListener("message", onMessage);
|
||||||
|
socket.removeEventListener("error", onError);
|
||||||
|
|
||||||
socket.close();
|
socket.close();
|
||||||
};
|
};
|
||||||
}, [selectedAccount]);
|
}, [prepareLog, selectedAccount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogBox
|
<LogBox
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ import useStayScrolled from "react-stay-scrolled";
|
|||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
|
|
||||||
import Container from "./Container";
|
import Container from "./Container";
|
||||||
import Box from "./Box";
|
|
||||||
import Flex from "./Flex";
|
|
||||||
import LogText from "./LogText";
|
import LogText from "./LogText";
|
||||||
import { ILog } from "../state";
|
import { ILog } from "../state";
|
||||||
import Text from "./Text";
|
import { Code, Link, Heading, Button, Text, Flex, Box } from ".";
|
||||||
import Button from "./Button";
|
|
||||||
import Heading from "./Heading";
|
|
||||||
import Link from "./Link";
|
|
||||||
|
|
||||||
interface ILogBox {
|
interface ILogBox {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,14 +16,7 @@ interface ILogBox {
|
|||||||
enhanced?: boolean;
|
enhanced?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogBox: React.FC<ILogBox> = ({
|
const LogBox: React.FC<ILogBox> = ({ title, clearLog, logs, children, renderNav, enhanced }) => {
|
||||||
title,
|
|
||||||
clearLog,
|
|
||||||
logs,
|
|
||||||
children,
|
|
||||||
renderNav,
|
|
||||||
enhanced,
|
|
||||||
}) => {
|
|
||||||
const logRef = useRef<HTMLPreElement>(null);
|
const logRef = useRef<HTMLPreElement>(null);
|
||||||
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef);
|
||||||
|
|
||||||
@@ -55,6 +43,7 @@ const LogBox: React.FC<ILogBox> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
|
fluid
|
||||||
css={{
|
css={{
|
||||||
height: "48px",
|
height: "48px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -78,7 +67,15 @@ const LogBox: React.FC<ILogBox> = ({
|
|||||||
>
|
>
|
||||||
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
<Notepad size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
||||||
</Heading>
|
</Heading>
|
||||||
{renderNav?.()}
|
<Flex
|
||||||
|
row
|
||||||
|
align="center"
|
||||||
|
css={{
|
||||||
|
width: "50%", // TODO make it max without breaking layout!
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderNav?.()}
|
||||||
|
</Flex>
|
||||||
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||||
{clearLog && (
|
{clearLog && (
|
||||||
<Button ghost size="xs" onClick={clearLog}>
|
<Button ghost size="xs" onClick={clearLog}>
|
||||||
@@ -117,16 +114,18 @@ const LogBox: React.FC<ILogBox> = ({
|
|||||||
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
backgroundColor: enhanced ? "$backgroundAlt" : undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
p: enhanced ? "$2 $1" : undefined,
|
p: enhanced ? "$1" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogText variant={log.type}>
|
<LogText variant={log.type}>
|
||||||
|
{log.timestamp && <Text muted>{log.timestamp.toLocaleTimeString()} </Text>}
|
||||||
{log.message}{" "}
|
{log.message}{" "}
|
||||||
{log.link && (
|
{log.link && (
|
||||||
<NextLink href={log.link} shallow passHref>
|
<NextLink href={log.link} shallow passHref>
|
||||||
<Link as="a">{log.linkText}</Link>
|
<Link as="a">{log.linkText}</Link>
|
||||||
</NextLink>
|
</NextLink>
|
||||||
)}
|
)}
|
||||||
|
{log.jsonData && <Code>{JSON.stringify(log.jsonData, null, 2)}</Code>}
|
||||||
</LogText>
|
</LogText>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export * from "./Tabs";
|
|||||||
export * from "./AlertDialog";
|
export * from "./AlertDialog";
|
||||||
export { default as Box } from "./Box";
|
export { default as Box } from "./Box";
|
||||||
export { default as Button } from "./Button";
|
export { default as Button } from "./Button";
|
||||||
|
export { default as Code } from "./Code";
|
||||||
export { default as ButtonGroup } from "./ButtonGroup";
|
export { default as ButtonGroup } from "./ButtonGroup";
|
||||||
export { default as DeployFooter } from "./DeployFooter";
|
export { default as DeployFooter } from "./DeployFooter";
|
||||||
export * from "./Dialog";
|
export * from "./Dialog";
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export interface IAccount {
|
|||||||
export interface ILog {
|
export interface ILog {
|
||||||
type: "error" | "warning" | "log" | "success";
|
type: "error" | "warning" | "log" | "success";
|
||||||
message: string;
|
message: string;
|
||||||
|
jsonData?: any,
|
||||||
|
timestamp?: Date;
|
||||||
link?: string;
|
link?: string;
|
||||||
linkText?: string;
|
linkText?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
21
utils/json.ts
Normal file
21
utils/json.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export const extractJSON = (str?: string) => {
|
||||||
|
if (!str) return
|
||||||
|
let firstOpen = 0, firstClose = 0, candidate = '';
|
||||||
|
firstOpen = str.indexOf('{', firstOpen + 1);
|
||||||
|
do {
|
||||||
|
firstClose = str.lastIndexOf('}');
|
||||||
|
if (firstClose <= firstOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
candidate = str.substring(firstOpen, firstClose + 1);
|
||||||
|
try {
|
||||||
|
let result = JSON.parse(candidate);
|
||||||
|
return { result, start: firstOpen < 0 ? 0 : firstOpen, end: firstClose }
|
||||||
|
}
|
||||||
|
catch (e) { }
|
||||||
|
firstClose = str.substring(0, firstClose).lastIndexOf('}');
|
||||||
|
} while (firstClose > firstOpen);
|
||||||
|
firstOpen = str.indexOf('{', firstOpen + 1);
|
||||||
|
} while (firstOpen != -1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user