// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. import { Field, LogEntry, LogSourceEvent, SourceData, } from './shared/interfaces'; export abstract class LogSource { private eventListeners: { eventType: string; listener: (event: LogSourceEvent) => void; }[]; protected sourceId: string; protected sourceName: string; constructor(sourceName: string) { this.eventListeners = []; this.sourceId = crypto.randomUUID(); this.sourceName = sourceName; } abstract start(): void; abstract stop(): void; addEventListener( eventType: string, listener: (event: LogSourceEvent) => void, ): void { this.eventListeners.push({ eventType, listener }); } removeEventListener( eventType: string, listener: (event: LogSourceEvent) => void, ): void { this.eventListeners = this.eventListeners.filter( (eventListener) => eventListener.eventType !== eventType || eventListener.listener !== listener, ); } emitEvent(event: LogSourceEvent): void { this.eventListeners.forEach((eventListener) => { if (eventListener.eventType === event.type) { eventListener.listener(event); } }); } publishLogEntry(logEntry: LogEntry): void { // Validate the log entry const validationResult = this.validateLogEntry(logEntry); if (validationResult !== null) { console.error('Validation error:', validationResult); return; } const sourceData: SourceData = { id: this.sourceId, name: this.sourceName }; logEntry.sourceData = sourceData; // Add the name of the log source as a field in the log entry const logSourceField: Field = { key: 'log_source', value: this.sourceName }; logEntry.fields.splice(1, 0, logSourceField); this.emitEvent({ type: 'log-entry', data: logEntry }); } validateLogEntry(logEntry: LogEntry): string | null { try { if (!logEntry.timestamp) { return 'Log entry has no valid timestamp'; } if (!Array.isArray(logEntry.fields)) { return 'Log entry fields must be an array'; } if (logEntry.fields.length === 0) { return 'Log entry fields must not be empty'; } // Handle backwards compatibility if (logEntry.severity) { logEntry.level = logEntry.severity; delete logEntry.severity; } for (const field of logEntry.fields) { if (!field.key || typeof field.key !== 'string') { return 'Invalid field key'; } // Handle backwards compatibility if (field.key === 'severity') { field.key = 'level'; } if ( field.value === undefined || (typeof field.value !== 'string' && typeof field.value !== 'boolean' && typeof field.value !== 'number' && typeof field.value !== 'object') ) { return 'Invalid field value'; } } if (logEntry.level !== undefined && typeof logEntry.level !== 'string') { return 'Invalid level value'; } return null; } catch (error) { if (error instanceof Error) { console.error('Validation error:', error.message); } return 'An unexpected error occurred during validation'; } } }