xref: /aosp_15_r20/external/pigweed/pw_web/log-viewer/src/log-source.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 {
16  Field,
17  LogEntry,
18  LogSourceEvent,
19  SourceData,
20} from './shared/interfaces';
21
22export abstract class LogSource {
23  private eventListeners: {
24    eventType: string;
25    listener: (event: LogSourceEvent) => void;
26  }[];
27
28  protected sourceId: string;
29
30  protected sourceName: string;
31
32  constructor(sourceName: string) {
33    this.eventListeners = [];
34    this.sourceId = crypto.randomUUID();
35    this.sourceName = sourceName;
36  }
37
38  abstract start(): void;
39
40  abstract stop(): void;
41
42  addEventListener(
43    eventType: string,
44    listener: (event: LogSourceEvent) => void,
45  ): void {
46    this.eventListeners.push({ eventType, listener });
47  }
48
49  removeEventListener(
50    eventType: string,
51    listener: (event: LogSourceEvent) => void,
52  ): void {
53    this.eventListeners = this.eventListeners.filter(
54      (eventListener) =>
55        eventListener.eventType !== eventType ||
56        eventListener.listener !== listener,
57    );
58  }
59
60  emitEvent(event: LogSourceEvent): void {
61    this.eventListeners.forEach((eventListener) => {
62      if (eventListener.eventType === event.type) {
63        eventListener.listener(event);
64      }
65    });
66  }
67
68  publishLogEntry(logEntry: LogEntry): void {
69    // Validate the log entry
70    const validationResult = this.validateLogEntry(logEntry);
71    if (validationResult !== null) {
72      console.error('Validation error:', validationResult);
73      return;
74    }
75
76    const sourceData: SourceData = { id: this.sourceId, name: this.sourceName };
77    logEntry.sourceData = sourceData;
78
79    // Add the name of the log source as a field in the log entry
80    const logSourceField: Field = { key: 'log_source', value: this.sourceName };
81    logEntry.fields.splice(1, 0, logSourceField);
82
83    this.emitEvent({ type: 'log-entry', data: logEntry });
84  }
85
86  validateLogEntry(logEntry: LogEntry): string | null {
87    try {
88      if (!logEntry.timestamp) {
89        return 'Log entry has no valid timestamp';
90      }
91      if (!Array.isArray(logEntry.fields)) {
92        return 'Log entry fields must be an array';
93      }
94      if (logEntry.fields.length === 0) {
95        return 'Log entry fields must not be empty';
96      }
97
98      // Handle backwards compatibility
99      if (logEntry.severity) {
100        logEntry.level = logEntry.severity;
101        delete logEntry.severity;
102      }
103
104      for (const field of logEntry.fields) {
105        if (!field.key || typeof field.key !== 'string') {
106          return 'Invalid field key';
107        }
108
109        // Handle backwards compatibility
110        if (field.key === 'severity') {
111          field.key = 'level';
112        }
113
114        if (
115          field.value === undefined ||
116          (typeof field.value !== 'string' &&
117            typeof field.value !== 'boolean' &&
118            typeof field.value !== 'number' &&
119            typeof field.value !== 'object')
120        ) {
121          return 'Invalid field value';
122        }
123      }
124
125      if (logEntry.level !== undefined && typeof logEntry.level !== 'string') {
126        return 'Invalid level value';
127      }
128
129      return null;
130    } catch (error) {
131      if (error instanceof Error) {
132        console.error('Validation error:', error.message);
133      }
134      return 'An unexpected error occurred during validation';
135    }
136  }
137}
138