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