From 14b537fd95a75112fb0fa6bdd85a39f98f907bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Thu, 28 Aug 2025 17:05:01 +0200 Subject: [PATCH] Initial commit --- .gitignore | 97 ++++++++++++++++++++++++++ index.ts | 4 ++ logger.d.ts | 38 +++++++++++ logger.ts | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 ++++ 5 files changed, 344 insertions(+) create mode 100644 .gitignore create mode 100644 index.ts create mode 100644 logger.d.ts create mode 100644 logger.ts create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00efece --- /dev/null +++ b/.gitignore @@ -0,0 +1,97 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,linux,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,linux,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,visualstudiocode diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..8d31697 --- /dev/null +++ b/index.ts @@ -0,0 +1,4 @@ +export * from './logger'; +export { LoggerComponent, LogLevel, getLogger, logger } from './logger'; +export const version = '1.0.0'; +export const ior = 'com.metatrom.examples.logger@1.0.0'; diff --git a/logger.d.ts b/logger.d.ts new file mode 100644 index 0000000..4da1844 --- /dev/null +++ b/logger.d.ts @@ -0,0 +1,38 @@ +declare module 'ior:esm:com.metatrom.examples.logger@1.0.0' { + export enum LogLevel { + TRACE = 0, + DEBUG = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + FATAL = 5, + } + + export interface ILoggerComponent { + init(): Promise; + setLevel(level: LogLevel): void; + trace(message: string, ...args: unknown[]): void; + debug(message: string, ...args: unknown[]): void; + info(message: string, ...args: unknown[]): void; + warn(message: string, ...args: unknown[]): void; + error(message: string, ...args: unknown[]): void; + fatal(message: string, ...args: unknown[]): void; + child(context: string): ILoggerComponent; + } + + export class LoggerComponent implements ILoggerComponent { + constructor(context?: string); + init(): Promise; + setLevel(level: LogLevel): void; + trace(message: string, ...args: unknown[]): void; + debug(message: string, ...args: unknown[]): void; + info(message: string, ...args: unknown[]): void; + warn(message: string, ...args: unknown[]): void; + error(message: string, ...args: unknown[]): void; + fatal(message: string, ...args: unknown[]): void; + child(context: string): ILoggerComponent; + } + + export function getLogger(context?: string): ILoggerComponent; + export const logger: LoggerComponent; +} diff --git a/logger.ts b/logger.ts new file mode 100644 index 0000000..e269dc1 --- /dev/null +++ b/logger.ts @@ -0,0 +1,190 @@ +// Define types locally to avoid circular dependency +export enum LogLevel { + TRACE = 0, + DEBUG = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + FATAL = 5, +} + +export interface ILoggerComponent { + init(): Promise; + setLevel(level: LogLevel): void; + trace(message: string, ...args: unknown[]): void; + debug(message: string, ...args: unknown[]): void; + info(message: string, ...args: unknown[]): void; + warn(message: string, ...args: unknown[]): void; + error(message: string, ...args: unknown[]): void; + fatal(message: string, ...args: unknown[]): void; + child(context: string): ILoggerComponent; +} + +// ANSI color codes for terminal output +const COLORS = { + RESET: '\x1b[0m', + BRIGHT: '\x1b[1m', + DIM: '\x1b[2m', + RED: '\x1b[31m', + GREEN: '\x1b[32m', + YELLOW: '\x1b[33m', + BLUE: '\x1b[34m', + MAGENTA: '\x1b[35m', + CYAN: '\x1b[36m', + WHITE: '\x1b[37m', + GRAY: '\x1b[90m', +}; + +const LEVEL_COLORS = { + [LogLevel.TRACE]: COLORS.GRAY, + [LogLevel.DEBUG]: COLORS.CYAN, + [LogLevel.INFO]: COLORS.GREEN, + [LogLevel.WARN]: COLORS.YELLOW, + [LogLevel.ERROR]: COLORS.RED, + [LogLevel.FATAL]: COLORS.BRIGHT + COLORS.RED, +}; + +const LEVEL_NAMES = { + [LogLevel.TRACE]: 'TRACE', + [LogLevel.DEBUG]: 'DEBUG', + [LogLevel.INFO]: 'INFO ', + [LogLevel.WARN]: 'WARN ', + [LogLevel.ERROR]: 'ERROR', + [LogLevel.FATAL]: 'FATAL', +}; + +export class LoggerComponent implements ILoggerComponent { + private level: LogLevel; + private context: string; + private static globalLevel: LogLevel = LogLevel.INFO; + + constructor(context = 'main') { + this.context = context; + this.level = LoggerComponent.globalLevel; + + // Check environment variable for log level + const envLevel = process.env.LOG_LEVEL?.toUpperCase(); + if (envLevel && LogLevel[envLevel as keyof typeof LogLevel] !== undefined) { + this.level = LogLevel[envLevel as keyof typeof LogLevel]; + LoggerComponent.globalLevel = this.level; + } else if (process.env.DEBUG === 'true') { + this.level = LogLevel.DEBUG; + LoggerComponent.globalLevel = LogLevel.DEBUG; + } + } + + async init(): Promise { + this.info(`Logger initialized for context: ${this.context}`); + } + + setLevel(level: LogLevel): void { + this.level = level; + LoggerComponent.globalLevel = level; + } + + private formatMessage( + level: LogLevel, + message: string, + args: unknown[], + ): string { + const timestamp = new Date().toISOString(); + const levelColor = LEVEL_COLORS[level]; + const levelName = LEVEL_NAMES[level]; + + // Format the main message + let formattedMessage = message; + if (args.length > 0) { + // Handle object arguments + const argStrings = args.map((arg) => { + // Special handling for Error objects + if (arg instanceof Error) { + return `${arg.name}: ${arg.message}${arg.stack ? '\n' + arg.stack : ''}`; + } + if (typeof arg === 'object' && arg !== null) { + try { + // Check if it's an empty object + const str = JSON.stringify(arg, null, 2); + if (str === '{}' && Object.getOwnPropertyNames(arg).length > 0) { + // Object has non-enumerable properties, try to get more info + return `${arg.constructor.name}: ${String(arg)}`; + } + return str; + } catch { + return String(arg); + } + } + return String(arg); + }); + formattedMessage = `${message} ${argStrings.join(' ')}`; + } + + // Build the log line with colors + return `${COLORS.DIM}[${timestamp}]${COLORS.RESET} ${levelColor}[${levelName}]${COLORS.RESET} ${COLORS.BRIGHT}[${this.context}]${COLORS.RESET} ${formattedMessage}`; + } + + private log(level: LogLevel, message: string, args: unknown[]): void { + if (level < this.level) return; + + const formattedMessage = this.formatMessage(level, message, args); + + switch (level) { + case LogLevel.TRACE: + case LogLevel.DEBUG: + console.log(formattedMessage); + break; + case LogLevel.INFO: + console.log(formattedMessage); + break; + case LogLevel.WARN: + console.warn(formattedMessage); + break; + case LogLevel.ERROR: + case LogLevel.FATAL: + console.error(formattedMessage); + break; + } + } + + trace(message: string, ...args: unknown[]): void { + this.log(LogLevel.TRACE, message, args); + } + + debug(message: string, ...args: unknown[]): void { + this.log(LogLevel.DEBUG, message, args); + } + + info(message: string, ...args: unknown[]): void { + this.log(LogLevel.INFO, message, args); + } + + warn(message: string, ...args: unknown[]): void { + this.log(LogLevel.WARN, message, args); + } + + error(message: string, ...args: unknown[]): void { + this.log(LogLevel.ERROR, message, args); + } + + fatal(message: string, ...args: unknown[]): void { + this.log(LogLevel.FATAL, message, args); + } + + child(context: string): ILoggerComponent { + const childLogger = new LoggerComponent(`${this.context}:${context}`); + childLogger.level = this.level; + return childLogger; + } +} + +// Export a singleton instance for convenience +let defaultLogger: LoggerComponent | null = null; + +export function getLogger(context = 'main'): ILoggerComponent { + if (!defaultLogger) { + defaultLogger = new LoggerComponent(context); + } + return defaultLogger; +} + +// Export default instance +export const logger = new LoggerComponent('main'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..c68b14a --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "@metatrom/logger", + "version": "1.0.0", + "main": "logger.ts", + "type": "module", + "metatrom": { + "ior": "com.metatrom.examples.logger@1.0.0", + "capabilities": { + "p2p": false, + "contracts": false, + "viewer": true, + "sync": false + } + } +}