xref: /aosp_15_r20/external/pigweed/pw_web/webconsole/components/log.tsx (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2022 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker//
3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker// the License at
6*61c4878aSAndroid Build Coastguard Worker//
7*61c4878aSAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker//
9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker// the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Workerimport {useEffect, useRef, useState} from "react";
16*61c4878aSAndroid Build Coastguard Workerimport {pw_tokenizer, Device} from "pigweedjs";
17*61c4878aSAndroid Build Coastguard Workerimport {AutoSizer, Table, Column} from 'react-virtualized';
18*61c4878aSAndroid Build Coastguard Workerimport {listenToDefaultLogService} from "../common/logService";
19*61c4878aSAndroid Build Coastguard Workerimport 'react-virtualized/styles.css';
20*61c4878aSAndroid Build Coastguard Workerimport styles from "../styles/log.module.css";
21*61c4878aSAndroid Build Coastguard Worker
22*61c4878aSAndroid Build Coastguard Workertype Detokenizer = pw_tokenizer.Detokenizer;
23*61c4878aSAndroid Build Coastguard Worker
24*61c4878aSAndroid Build Coastguard Workerinterface LogProps {
25*61c4878aSAndroid Build Coastguard Worker  device: Device | undefined,
26*61c4878aSAndroid Build Coastguard Worker  tokenDB: string | undefined
27*61c4878aSAndroid Build Coastguard Worker}
28*61c4878aSAndroid Build Coastguard Worker
29*61c4878aSAndroid Build Coastguard Workerinterface LogEntry {
30*61c4878aSAndroid Build Coastguard Worker  msg: string,
31*61c4878aSAndroid Build Coastguard Worker  timestamp: number,
32*61c4878aSAndroid Build Coastguard Worker  humanTime: string,
33*61c4878aSAndroid Build Coastguard Worker  module: string,
34*61c4878aSAndroid Build Coastguard Worker  file: string
35*61c4878aSAndroid Build Coastguard Worker}
36*61c4878aSAndroid Build Coastguard Worker
37*61c4878aSAndroid Build Coastguard Workerfunction parseLogMsg(msg: string): LogEntry {
38*61c4878aSAndroid Build Coastguard Worker  const pairs = msg.split("■").slice(1).map(pair => pair.split("♦"));
39*61c4878aSAndroid Build Coastguard Worker
40*61c4878aSAndroid Build Coastguard Worker  // Not a valid message, print as-is.
41*61c4878aSAndroid Build Coastguard Worker  if (pairs.length === 0) {
42*61c4878aSAndroid Build Coastguard Worker    return {
43*61c4878aSAndroid Build Coastguard Worker      msg,
44*61c4878aSAndroid Build Coastguard Worker      module: "",
45*61c4878aSAndroid Build Coastguard Worker      file: "",
46*61c4878aSAndroid Build Coastguard Worker      timestamp: Date.now(),
47*61c4878aSAndroid Build Coastguard Worker      humanTime: new Date(Date.now()).toLocaleTimeString("en-US")
48*61c4878aSAndroid Build Coastguard Worker    }
49*61c4878aSAndroid Build Coastguard Worker  }
50*61c4878aSAndroid Build Coastguard Worker
51*61c4878aSAndroid Build Coastguard Worker  let map: any = {};
52*61c4878aSAndroid Build Coastguard Worker  pairs.forEach(pair => map[pair[0]] = pair[1])
53*61c4878aSAndroid Build Coastguard Worker  return {
54*61c4878aSAndroid Build Coastguard Worker    msg: map.msg,
55*61c4878aSAndroid Build Coastguard Worker    module: map.module,
56*61c4878aSAndroid Build Coastguard Worker    file: map.file,
57*61c4878aSAndroid Build Coastguard Worker    timestamp: Date.now(),
58*61c4878aSAndroid Build Coastguard Worker    humanTime: new Date(Date.now()).toLocaleTimeString("en-US")
59*61c4878aSAndroid Build Coastguard Worker  }
60*61c4878aSAndroid Build Coastguard Worker}
61*61c4878aSAndroid Build Coastguard Worker
62*61c4878aSAndroid Build Coastguard Workerconst keyToDisplayName: {[key: string]: string} = {
63*61c4878aSAndroid Build Coastguard Worker  "msg": "Message",
64*61c4878aSAndroid Build Coastguard Worker  "humanTime": "Time",
65*61c4878aSAndroid Build Coastguard Worker  "module": "Module",
66*61c4878aSAndroid Build Coastguard Worker  "file": "File"
67*61c4878aSAndroid Build Coastguard Worker}
68*61c4878aSAndroid Build Coastguard Worker
69*61c4878aSAndroid Build Coastguard Workerexport default function Log({device, tokenDB}: LogProps) {
70*61c4878aSAndroid Build Coastguard Worker  const [logs, setLogs] = useState<LogEntry[]>([]);
71*61c4878aSAndroid Build Coastguard Worker  const [detokenizer, setDetokenizer] = useState<Detokenizer | null>(null);
72*61c4878aSAndroid Build Coastguard Worker  const logTable = useRef<Table | null>(null);
73*61c4878aSAndroid Build Coastguard Worker  const _headerRenderer = ({dataKey, sortBy, sortDirection}: any) => {
74*61c4878aSAndroid Build Coastguard Worker    return (
75*61c4878aSAndroid Build Coastguard Worker      <div>
76*61c4878aSAndroid Build Coastguard Worker        {keyToDisplayName[dataKey]}
77*61c4878aSAndroid Build Coastguard Worker      </div>
78*61c4878aSAndroid Build Coastguard Worker    );
79*61c4878aSAndroid Build Coastguard Worker  }
80*61c4878aSAndroid Build Coastguard Worker
81*61c4878aSAndroid Build Coastguard Worker  const processFrame = (frame: Uint8Array) => {
82*61c4878aSAndroid Build Coastguard Worker    if (detokenizer) {
83*61c4878aSAndroid Build Coastguard Worker      const detokenized = detokenizer.detokenizeUint8Array(frame);
84*61c4878aSAndroid Build Coastguard Worker      setLogs(oldLogs => [...oldLogs, parseLogMsg(detokenized)]);
85*61c4878aSAndroid Build Coastguard Worker    }
86*61c4878aSAndroid Build Coastguard Worker    else {
87*61c4878aSAndroid Build Coastguard Worker      const decoded = new TextDecoder().decode(frame);
88*61c4878aSAndroid Build Coastguard Worker      setLogs(oldLogs => [...oldLogs, parseLogMsg(decoded)]);
89*61c4878aSAndroid Build Coastguard Worker    }
90*61c4878aSAndroid Build Coastguard Worker    setTimeout(() => {
91*61c4878aSAndroid Build Coastguard Worker      logTable.current!.scrollToRow(logs.length - 1);
92*61c4878aSAndroid Build Coastguard Worker    }, 100);
93*61c4878aSAndroid Build Coastguard Worker  }
94*61c4878aSAndroid Build Coastguard Worker
95*61c4878aSAndroid Build Coastguard Worker  useEffect(() => {
96*61c4878aSAndroid Build Coastguard Worker    if (device) {
97*61c4878aSAndroid Build Coastguard Worker      let cleanupFn: () => void;
98*61c4878aSAndroid Build Coastguard Worker      listenToDefaultLogService(device, processFrame).then((unsub) => cleanupFn = unsub);
99*61c4878aSAndroid Build Coastguard Worker      return () => {
100*61c4878aSAndroid Build Coastguard Worker        if (cleanupFn) cleanupFn();
101*61c4878aSAndroid Build Coastguard Worker      }
102*61c4878aSAndroid Build Coastguard Worker    }
103*61c4878aSAndroid Build Coastguard Worker  }, [device, detokenizer]);
104*61c4878aSAndroid Build Coastguard Worker
105*61c4878aSAndroid Build Coastguard Worker  useEffect(() => {
106*61c4878aSAndroid Build Coastguard Worker    if (tokenDB && tokenDB.length > 0) {
107*61c4878aSAndroid Build Coastguard Worker      const detokenizer = new pw_tokenizer.Detokenizer(tokenDB);
108*61c4878aSAndroid Build Coastguard Worker      setDetokenizer(detokenizer);
109*61c4878aSAndroid Build Coastguard Worker    }
110*61c4878aSAndroid Build Coastguard Worker  }, [tokenDB])
111*61c4878aSAndroid Build Coastguard Worker
112*61c4878aSAndroid Build Coastguard Worker  return (
113*61c4878aSAndroid Build Coastguard Worker    <>
114*61c4878aSAndroid Build Coastguard Worker      {/* @ts-ignore */}
115*61c4878aSAndroid Build Coastguard Worker      <AutoSizer>
116*61c4878aSAndroid Build Coastguard Worker        {({height, width}) => (
117*61c4878aSAndroid Build Coastguard Worker          <>
118*61c4878aSAndroid Build Coastguard Worker            {/* @ts-ignore */}
119*61c4878aSAndroid Build Coastguard Worker            <Table
120*61c4878aSAndroid Build Coastguard Worker              className={styles.logsContainer}
121*61c4878aSAndroid Build Coastguard Worker              headerHeight={40}
122*61c4878aSAndroid Build Coastguard Worker              height={height}
123*61c4878aSAndroid Build Coastguard Worker              rowCount={logs.length}
124*61c4878aSAndroid Build Coastguard Worker              rowGetter={({index}) => logs[index]}
125*61c4878aSAndroid Build Coastguard Worker              rowHeight={30}
126*61c4878aSAndroid Build Coastguard Worker              ref={logTable}
127*61c4878aSAndroid Build Coastguard Worker              width={width}
128*61c4878aSAndroid Build Coastguard Worker            >
129*61c4878aSAndroid Build Coastguard Worker              {/* @ts-ignore */}
130*61c4878aSAndroid Build Coastguard Worker              <Column
131*61c4878aSAndroid Build Coastguard Worker                dataKey="humanTime"
132*61c4878aSAndroid Build Coastguard Worker                width={190}
133*61c4878aSAndroid Build Coastguard Worker                headerRenderer={_headerRenderer}
134*61c4878aSAndroid Build Coastguard Worker              />
135*61c4878aSAndroid Build Coastguard Worker              {/* @ts-ignore */}
136*61c4878aSAndroid Build Coastguard Worker              <Column
137*61c4878aSAndroid Build Coastguard Worker                dataKey="msg"
138*61c4878aSAndroid Build Coastguard Worker                flexGrow={1}
139*61c4878aSAndroid Build Coastguard Worker                width={290}
140*61c4878aSAndroid Build Coastguard Worker                headerRenderer={_headerRenderer}
141*61c4878aSAndroid Build Coastguard Worker              />
142*61c4878aSAndroid Build Coastguard Worker              {/* @ts-ignore */}
143*61c4878aSAndroid Build Coastguard Worker              <Column
144*61c4878aSAndroid Build Coastguard Worker                dataKey="module"
145*61c4878aSAndroid Build Coastguard Worker                width={190}
146*61c4878aSAndroid Build Coastguard Worker                headerRenderer={_headerRenderer}
147*61c4878aSAndroid Build Coastguard Worker              />
148*61c4878aSAndroid Build Coastguard Worker              {/* @ts-ignore */}
149*61c4878aSAndroid Build Coastguard Worker              <Column
150*61c4878aSAndroid Build Coastguard Worker                dataKey="file"
151*61c4878aSAndroid Build Coastguard Worker                flexGrow={1}
152*61c4878aSAndroid Build Coastguard Worker                width={190}
153*61c4878aSAndroid Build Coastguard Worker                headerRenderer={_headerRenderer}
154*61c4878aSAndroid Build Coastguard Worker              />
155*61c4878aSAndroid Build Coastguard Worker            </Table>
156*61c4878aSAndroid Build Coastguard Worker          </>
157*61c4878aSAndroid Build Coastguard Worker        )}
158*61c4878aSAndroid Build Coastguard Worker      </AutoSizer>
159*61c4878aSAndroid Build Coastguard Worker    </>
160*61c4878aSAndroid Build Coastguard Worker  )
161*61c4878aSAndroid Build Coastguard Worker}
162