/** * Logger service for writing log messages to configured handlers * * Loggers are created from a {@link LoggerFactory} with a specific scope/category. * All log messages written to a logger will be sent to all handlers configured * in the factory. * * @example Write messages at every level * ```ts * import { createLogger } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * * logger.info('Component initialized'); * logger.debug('Processing data', { itemCount: 42 }); * logger.warn('Deprecated API usage detected'); * * try { * await fetch('/api/data'); * } catch (error) { * logger.error('Failed to fetch data', asError(error), { url: '/api/data' }); * } * * function asError(value: unknown): Error | undefined { * return value instanceof Error ? value : undefined; * } * ``` * * @beta * @group Logger */ export interface Logger { /** * The scope/category for this logger instance */ readonly scope: string; /** * Logs a debug message * * Use for detailed diagnostic information useful during development and troubleshooting. * Debug logs are typically disabled in production and should include technical details * like internal state, intermediate values, or execution flow. * * @param message - The log message * @param data - Optional additional data to include with the log entry * * @example Log debug messages * ```ts * import { * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * const http = platform.get(PlatformServiceName.Http); * const cache = new Map(); * * const cacheKey = 'user:123'; * let user = cache.get(cacheKey); * * if (user === undefined) { * logger.debug('Cache miss, fetching from API', { key: cacheKey }); * * user = await http.get('user/123'); * cache.set(cacheKey, user); * } * * logger.debug('Rendering component', { * limetype: context.limetype, * id: context.id, * }); * ``` */ debug(message: string, data?: LogData): void; /** * Logs an informational message * * Use for significant application events that represent normal behavior. * Info logs should capture key milestones, successful operations, or state transitions * that are useful for understanding application flow in production. * * @param message - The log message * @param data - Optional additional data to include with the log entry * * @example Log informational messages * ```ts * import { * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * const application = platform.get(PlatformServiceName.Application); * const user = application.getCurrentUser(); * * if (user) { * logger.info('User authenticated', { userId: user.id }); * } * * logger.info('File uploaded successfully', { * filename: 'report.pdf', * size: 2048, * }); * ``` */ info(message: string, data?: LogData): void; /** * Logs a warning message * * Use for unexpected situations that don't prevent the application from functioning * but may indicate potential issues, deprecated usage, or suboptimal conditions. * Warnings should be actionable and suggest something that may need attention. * * @param message - The log message * @param data - Optional additional data to include with the log entry * * @example Log warnings * ```ts * import { * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * const http = platform.get(PlatformServiceName.Http); * * const threshold = 3000; * const startedAt = Date.now(); * * await http.get('deal'); * * const duration = Date.now() - startedAt; * * if (duration > threshold) { * logger.warn('API response time exceeded threshold', { * duration, * threshold, * }); * } * * logger.warn('Using deprecated configuration option', { * option: 'legacy_mode', * }); * ``` */ warn(message: string, data?: LogData): void; /** * Logs an error message * * Use for failures and exceptions that prevent normal operation or indicate * something has gone wrong. Error logs should always describe what failed * and include relevant context to aid in diagnosis. * * @param message - The log message * @param error - Optional error object * @param data - Optional additional data to include with the log entry * * @example Log errors * ```ts * import { * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * const http = platform.get(PlatformServiceName.Http); * * const userId = 123; * * try { * await http.get(`user/${userId}`); * } catch (error) { * logger.error('Failed to fetch user data', asError(error), { userId }); * } * * const invalidConfig = { retries: -1 }; * * if (invalidConfig.retries < 0) { * logger.error('Invalid configuration detected', undefined, { * config: invalidConfig, * }); * } * * function asError(value: unknown): Error | undefined { * return value instanceof Error ? value : undefined; * } * ``` */ error(message: string, error?: Error, data?: LogData): void; } /** * Handler for processing log messages * * Implementations can handle logs by writing to different outputs such as * console, memory, IndexedDB, or external services. * * @example Write log entries to the console * ```ts * import { * LogEntry, * LogHandler, * LogLevel, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * class ConsoleHandler implements LogHandler { * // Annotated so the property keeps the LogLevel type instead of widening to string * public minLevel: LogLevel = LogLevel.Info; * * public handle(entry: LogEntry): void { * const prefix = `[${entry.level.toUpperCase()}] [${entry.scope}]`; * * console.log(`${prefix} ${entry.message}`, entry.data); * } * } * * const consoleHandler = new ConsoleHandler(); * * platform.get(PlatformServiceName.Logger).addHandler(consoleHandler); * * consoleHandler.minLevel = LogLevel.Debug; * ``` * * @beta * @group Logger */ export interface LogHandler { /** * Handles a log entry * * Implementations should handle their own errors internally to ensure * reliability. However, any uncaught errors thrown by this method will * be caught by the factory to prevent one handler from breaking others. * Errors will be logged to the console but will not stop other handlers * from receiving the log entry. * * @param entry - The log entry to handle */ handle(entry: LogEntry): void | Promise; /** * Minimum log level this handler should receive * * If specified, the factory will only pass log entries with a level * equal to or higher than this value. If not specified, all log * entries are passed to the handler. * * Level priority (lowest to highest): Debug → Info → Warn → Error * * This can be changed at runtime to dynamically adjust filtering. */ minLevel?: LogLevel; } /** * Extended handler interface for handlers that support log storage and retrieval * * Handlers like memory and IndexedDB implement this interface to allow * querying and exporting stored logs. * * @remarks * Implementations should respect the configured retention policies (`maxEntries` * and `maxAge`) to prevent unbounded growth. These limits can be changed at * runtime to dynamically adjust retention behavior. * * For high-volume logging, consider batching writes to reduce overhead. * * @example Keep log entries in memory * ```ts * import { * LogEntry, * LogStore, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const ONE_DAY = 24 * 60 * 60 * 1000; * * class MemoryHandler implements LogStore { * public maxEntries = 10_000; * public maxAge = 7 * ONE_DAY; * * private entries: LogEntry[] = []; * * public handle(entry: LogEntry): void { * this.entries.push(entry); * this.applyRetention(); * } * * public getLogs(): LogEntry[] { * return [...this.entries]; * } * * public clear(): void { * this.entries = []; * } * * private applyRetention(): void { * const oldest = Date.now() - this.maxAge; * * this.entries = this.entries * .filter((entry) => entry.timestamp >= oldest) * .slice(-this.maxEntries); * } * } * * const memoryHandler = new MemoryHandler(); * * platform.get(PlatformServiceName.Logger).addHandler(memoryHandler); * ``` * * @beta * @group Logger */ export interface LogStore extends LogHandler { /** * Retrieves all stored log entries * * @returns Array of all log entries */ getLogs(): LogEntry[] | Promise; /** * Clears all stored log entries */ clear(): void | Promise; /** * Maximum number of log entries to retain * * When the number of stored entries exceeds this limit, the oldest * entries should be removed. If not specified, no entry-count limit * is enforced. * * This can be changed at runtime to dynamically adjust retention. */ maxEntries?: number; /** * Maximum age of log entries in milliseconds * * Entries older than this age should be removed during cleanup. * If not specified, no age-based cleanup is performed. * * This can be changed at runtime to dynamically adjust retention. * * @example Set the retention age * ```ts * import { LogStore } from '@limetech/lime-web-components'; * * const ONE_HOUR = 60 * 60 * 1000; * const ONE_WEEK = 7 * 24 * ONE_HOUR; * * function retainForOneWeek(store: LogStore): void { * store.maxAge = ONE_WEEK; * } * * function retainForOneHour(store: LogStore): void { * store.maxAge = ONE_HOUR; * } * ``` */ maxAge?: number; } /** * Factory for creating {@link Logger} instances * * The factory is initialized with one or more log handlers. All loggers created * from the factory will write to all configured handlers. * * @example Use the factory service * ```ts * import { * LogEntry, * LogHandler, * LogLevel, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const loggerFactory = platform.get(PlatformServiceName.Logger); * * // A component should prefer the exported `createLogger`, which adds the package * // name to the scope. Go through the factory when you need the handler methods. * const httpLogger = loggerFactory.createLogger('http'); * const authLogger = loggerFactory.createLogger('auth'); * * class AuditHandler implements LogHandler { * public minLevel: LogLevel = LogLevel.Warn; * * public handle(entry: LogEntry): void { * void fetch('/api/audit', { * method: 'POST', * body: JSON.stringify(entry), * }); * } * } * * const auditHandler = new AuditHandler(); * * loggerFactory.addHandler(auditHandler); * * const allLogs = await loggerFactory.getLogs(); * * await loggerFactory.clearLogs(); * * loggerFactory.removeHandler(auditHandler); * ``` * * @beta * @group Logger */ export interface LoggerFactory { /** * Creates a logger instance for a specific scope/category * * @param scope - The scope or category for the logger (e.g., 'http', 'auth', 'component-name') * @returns A logger instance that writes to all configured handlers */ createLogger(scope: string): Logger; /** * Registers a new handler with the factory * * The handler will receive all future log entries from all loggers * (both existing and newly created). Historical logs are not replayed. * * @param handler - The handler to register */ addHandler(handler: LogHandler): void; /** * Removes a handler from the factory * * @param handler - The handler to remove * @returns `true` if the handler was removed, `false` if it was not found */ removeHandler(handler: LogHandler): boolean; /** * Retrieves logs from all handlers that support storage * * This method aggregates logs from all handlers that implement {@link LogStore}. * Handlers that don't support log retrieval (like console) are ignored. * * @returns Array of all log entries from all storage handlers */ getLogs(): Promise; /** * Clears logs from all handlers that support storage * * This method clears logs from all handlers that implement {@link LogStore}. * Handlers that don't support clearing (like console) are ignored. */ clearLogs(): Promise; } /** * Base properties shared by all log entries * * @beta * @group Logger */ export interface BaseLogEntry { /** * The scope/category this log entry belongs to */ scope: string; /** * The log message */ message: string; /** * Timestamp when the log entry was created (milliseconds since Unix epoch) * * Use `Date.now()` to create, `new Date(timestamp)` to convert back to Date object. */ timestamp: number; /** * Optional additional structured data */ data?: LogData; } /** * Log entry for Debug, Info, and Warn levels * * @beta * @group Logger */ export interface StandardLogEntry extends BaseLogEntry { level: typeof LogLevel.Debug | typeof LogLevel.Info | typeof LogLevel.Warn; } /** * Log entry for Error level with optional error object * * @beta * @group Logger */ export interface ErrorLogEntry extends BaseLogEntry { level: typeof LogLevel.Error; /** * Optional error object * * @remarks * Error objects do not serialize well to JSON - only `name` and `message` * are preserved, while `stack` is lost. Storage handlers that persist logs * should manually serialize errors before storage: * * ```typescript * const serialized = { * name: error.name, * message: error.message, * stack: error.stack * }; * ``` */ error?: Error; } /** * A log entry with type-safe level discrimination * * When `level` is `LogLevel.Error`, the `error` property is available. * * @beta * @group Logger */ export type LogEntry = StandardLogEntry | ErrorLogEntry; /** * Log level values * * @beta * @group Logger */ export declare const LogLevel: { readonly Debug: "debug"; readonly Info: "info"; readonly Warn: "warn"; readonly Error: "error"; }; /** * Log level type indicating the severity of a log message * * @beta * @group Logger */ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; /** * Additional structured data that can be attached to a log entry * * Values should be JSON-serializable (primitives, arrays, plain objects) to * ensure compatibility with storage handlers. Avoid functions, symbols, * undefined, and circular references. * * @beta * @group Logger */ export type LogData = Record;