xref: /aosp_15_r20/external/pigweed/ts/logging_source_rpc.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1// Copyright 2023 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15import { Detokenizer } from '../pw_tokenizer/ts';
16import { LogSource } from '../pw_web/log-viewer/src/log-source';
17import { Device } from './device';
18import { LogEntry } from './logging';
19
20export class PigweedRPCLogSource extends LogSource {
21  private detokenizer: Detokenizer | undefined;
22  private logs: LogEntry[] = [];
23  private call: any;
24  constructor(device: Device, tokenDB: string | undefined) {
25    super();
26    if (tokenDB && tokenDB.length > 0) {
27      this.detokenizer = new Detokenizer(tokenDB);
28    }
29    this.call = device.rpcs.pw.log.Logs.Listen((msg: any) => {
30      msg
31        .getEntriesList()
32        .forEach((entry: any) => this.processFrame(entry.getMessage()));
33    });
34  }
35
36  destroy() {
37    this.call.cancel();
38  }
39
40  private processFrame(frame: Uint8Array) {
41    let entry: LogEntry;
42    if (this.detokenizer) {
43      const detokenized = this.detokenizer.detokenizeUint8Array(frame);
44      entry = this.parseLogMsg(detokenized);
45    } else {
46      const decoded = new TextDecoder().decode(frame);
47      entry = this.parseLogMsg(decoded);
48    }
49    this.logs = [...this.logs, entry];
50    this.publishLogEntry(entry);
51  }
52
53  private parseLogMsg(msg: string): LogEntry {
54    const pairs = msg
55      .split('■')
56      .slice(1)
57      .map((pair) => pair.split('♦'));
58
59    // Not a valid message, print as-is.
60    const timestamp = new Date();
61    if (pairs.length === 0) {
62      return {
63        fields: [
64          { key: 'timestamp', value: timestamp.toISOString() },
65          { key: 'message', value: msg },
66        ],
67        timestamp: timestamp,
68      };
69    }
70
71    const map: any = {};
72    pairs.forEach((pair) => (map[pair[0]] = pair[1]));
73    return {
74      fields: [
75        { key: 'timestamp', value: timestamp },
76        { key: 'message', value: map.msg },
77        { key: 'module', value: map.module },
78        { key: 'file', value: map.file },
79      ],
80      timestamp: timestamp,
81    };
82  }
83}
84