@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
305 lines • 10.1 kB
JavaScript
;
// Enhanced structured logging with correlation IDs and context
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.StructuredLogger = exports.FileLogAppender = exports.ConsoleLogAppender = exports.LogLevel = void 0;
/**
* Log levels
*/
var LogLevel;
(function (LogLevel) {
LogLevel["ERROR"] = "error";
LogLevel["WARN"] = "warn";
LogLevel["INFO"] = "info";
LogLevel["DEBUG"] = "debug";
LogLevel["TRACE"] = "trace";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
/**
* Console log appender with structured output
*/
class ConsoleLogAppender {
constructor() {
this.name = 'console';
}
async write(entry) {
const colorMap = {
[LogLevel.ERROR]: '\x1b[31m', // Red
[LogLevel.WARN]: '\x1b[33m', // Yellow
[LogLevel.INFO]: '\x1b[36m', // Cyan
[LogLevel.DEBUG]: '\x1b[37m', // White
[LogLevel.TRACE]: '\x1b[90m', // Gray
};
const reset = '\x1b[0m';
const color = colorMap[entry.level] || reset;
// Format the main log line
const time = new Date(entry.timestamp).toISOString();
const level = entry.level.toUpperCase().padEnd(5);
const component = entry.context.component ? `[${entry.context.component}]` : '';
const correlation = entry.context.correlationId ? `{${entry.context.correlationId.slice(0, 8)}}` : '';
console.log(`${color}${time} ${level}${reset} ${component}${correlation} ${entry.message}`);
// Add context details if present
const contextKeys = Object.keys(entry.context).filter(k => !['component', 'correlationId'].includes(k) && entry.context[k] !== undefined);
if (contextKeys.length > 0) {
const contextStr = contextKeys.map(k => `${k}=${entry.context[k]}`).join(' ');
console.log(` └─ ${contextStr}`);
}
// Add metadata if present
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
console.log(` └─ ${JSON.stringify(entry.metadata)}`);
}
// Add stack trace for errors
if (entry.stack) {
console.log(` └─ Stack:\n${entry.stack.split('\n').map(line => ` ${line}`).join('\n')}`);
}
}
}
exports.ConsoleLogAppender = ConsoleLogAppender;
/**
* File log appender with JSON format
*/
class FileLogAppender {
constructor(filePath) {
this.filePath = filePath;
this.name = 'file';
}
async write(entry) {
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
const logLine = JSON.stringify(entry) + '\n';
try {
await fs.appendFile(this.filePath, logLine);
}
catch (error) {
// Fallback to console if file write fails
console.error('Failed to write to log file:', error);
console.log(logLine);
}
}
}
exports.FileLogAppender = FileLogAppender;
/**
* Enhanced structured logger
*/
class StructuredLogger {
constructor(config = {}) {
this.appenders = [];
this.config = {
level: config.level ?? LogLevel.INFO,
defaultContext: config.defaultContext ?? {},
enabled: config.enabled ?? true,
appenders: config.appenders ?? [new ConsoleLogAppender()],
includeStack: config.includeStack ?? true,
};
this.appenders = [...this.config.appenders];
this.correlationIdGenerator = this.createCorrelationIdGenerator();
}
/**
* Create a child logger with additional context
*/
child(context) {
return new StructuredLogger({
...this.config,
defaultContext: { ...this.config.defaultContext, ...context },
});
}
/**
* Generate a new correlation ID
*/
generateCorrelationId() {
return this.correlationIdGenerator();
}
/**
* Add log appender
*/
addAppender(appender) {
this.appenders.push(appender);
}
/**
* Remove log appender
*/
removeAppender(name) {
this.appenders = this.appenders.filter(a => a.name !== name);
}
/**
* Log an error
*/
error(message, meta) {
this.log(LogLevel.ERROR, message, meta);
}
/**
* Log a warning
*/
warn(message, meta) {
this.log(LogLevel.WARN, message, meta);
}
/**
* Log info
*/
info(message, meta) {
this.log(LogLevel.INFO, message, meta);
}
/**
* Log debug information
*/
debug(message, meta) {
this.log(LogLevel.DEBUG, message, meta);
}
/**
* Log trace information
*/
trace(message, meta) {
this.log(LogLevel.TRACE, message, meta);
}
/**
* Log with specific level and context
*/
logWithContext(level, message, context, meta) {
this.log(level, message, meta, context);
}
/**
* Core logging method
*/
log(level, message, meta, additionalContext) {
if (!this.config.enabled || !this.shouldLog(level)) {
return;
}
// Extract error stack if meta is an Error
let stack;
let metadata = meta;
if (meta instanceof Error) {
stack = meta.stack;
metadata = {
errorName: meta.name,
errorMessage: meta.message,
...meta,
};
}
else if (this.config.includeStack && (level === LogLevel.ERROR || level === LogLevel.WARN)) {
// Capture stack trace for errors and warnings
const error = new Error();
stack = error.stack?.split('\n').slice(3).join('\n'); // Remove the first 3 lines (Error creation)
}
const entry = {
level,
message,
timestamp: new Date().toISOString(),
context: {
...this.config.defaultContext,
...additionalContext,
// Add correlation ID if not present
correlationId: additionalContext?.correlationId ||
this.config.defaultContext.correlationId ||
this.generateCorrelationId(),
},
stack,
metadata,
};
// Write to all appenders
this.writeToAppenders(entry);
}
/**
* Check if we should log at this level
*/
shouldLog(level) {
const levels = [LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG, LogLevel.TRACE];
const currentIndex = levels.indexOf(this.config.level);
const messageIndex = levels.indexOf(level);
return messageIndex <= currentIndex;
}
/**
* Write log entry to all appenders
*/
async writeToAppenders(entry) {
const promises = this.appenders.map(async (appender) => {
try {
await appender.write(entry);
}
catch (error) {
// Fallback: log to console if appender fails
console.error(`Log appender ${appender.name} failed:`, error);
console.log(JSON.stringify(entry));
}
});
// Don't await to avoid blocking the calling code
Promise.allSettled(promises);
}
/**
* Create correlation ID generator
*/
createCorrelationIdGenerator() {
let counter = 0;
const instanceId = Math.random().toString(36).substr(2, 4);
return () => {
counter = (counter + 1) % 10000;
const timestamp = Date.now().toString(36);
return `${instanceId}-${timestamp}-${counter.toString().padStart(4, '0')}`;
};
}
/**
* Create request-scoped logger
*/
static forRequest(requestId, additionalContext) {
return new StructuredLogger({
defaultContext: {
requestId,
...additionalContext,
},
});
}
/**
* Create task-scoped logger
*/
static forTask(taskId, additionalContext) {
return new StructuredLogger({
defaultContext: {
taskId,
component: 'task-processor',
...additionalContext,
},
});
}
/**
* Create component-scoped logger
*/
static forComponent(component, additionalContext) {
return new StructuredLogger({
defaultContext: {
component,
...additionalContext,
},
});
}
}
exports.StructuredLogger = StructuredLogger;
//# sourceMappingURL=structuredLogger.js.map