UNPKG

legal-markdown-js

Version:

Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version

328 lines 12.2 kB
/** * @fileoverview Pipeline Logging System for Legal Markdown Processing * * This module provides comprehensive logging capabilities for the Legal Markdown * processing pipeline. It supports multiple logging levels, structured output, * performance metrics, and integration with external monitoring systems. * * Features: * - Structured logging with consistent format * - Performance metrics and timing * - Debug output with step-by-step visibility * - Error tracking and reporting * - Configurable log levels and outputs * - Integration with pipeline events * - Memory usage tracking * - Field tracking logging * * @example * ```typescript * import { PipelineLogger, ConsolePipelineLogger } from './pipeline-logger.js'; * * const logger = new ConsolePipelineLogger({ * level: 'debug', * enableMetrics: true, * enableColors: true * }); * * logger.startPipeline({ steps: [], fieldTrackingMode: 'centralized' }); * logger.startStep('mixins', 1500); * logger.completeStep('mixins', stepResult); * logger.completePipeline(pipelineResult); * ``` */ /** * Log level enumeration for controlling output verbosity * * @enum {string} LogLevel */ export var LogLevel; (function (LogLevel) { LogLevel["ERROR"] = "error"; LogLevel["WARN"] = "warn"; LogLevel["INFO"] = "info"; LogLevel["DEBUG"] = "debug"; LogLevel["TRACE"] = "trace"; })(LogLevel || (LogLevel = {})); /** * Console-based pipeline logger with rich formatting * * @class ConsolePipelineLogger * @example * ```typescript * const logger = new ConsolePipelineLogger({ * level: LogLevel.DEBUG, * enableColors: true, * enableMetrics: true * }); * * // Use with pipeline manager * const pipeline = new PipelineManager(logger); * ``` */ export class ConsolePipelineLogger { config; startTime = 0; stepTimings = new Map(); metrics = { totalSteps: 0, successfulSteps: 0, failedSteps: 0, skippedSteps: 0, totalFieldsTracked: 0, totalProcessingTime: 0, memoryPeakUsage: 0, }; constructor(config = {}) { // Default to ERROR level for quiet operation, can be overridden with LOG_LEVEL env var const envLogLevel = typeof process !== 'undefined' && process.env && process.env.LOG_LEVEL; const defaultLevel = envLogLevel || LogLevel.ERROR; this.config = { level: config.level || defaultLevel, enableMetrics: config.enableMetrics ?? true, enableColors: config.enableColors ?? (process.stdout.isTTY || false), enableTimestamps: config.enableTimestamps ?? true, prefix: config.prefix || '[PIPELINE]', outputFormat: config.outputFormat || 'structured', enableMemoryTracking: config.enableMemoryTracking ?? false, }; } startPipeline(config) { this.startTime = Date.now(); this.resetMetrics(); this.log(LogLevel.INFO, 'Pipeline execution starting', { stepsCount: config.steps.length, fieldTrackingMode: config.fieldTrackingMode, debugLogging: config.enableDebugLogging, }); if (this.shouldLog(LogLevel.DEBUG)) { this.log(LogLevel.DEBUG, 'Pipeline steps configured', { steps: config.steps.map(step => ({ name: step.name, order: step.order, enabled: step.enabled, dependencies: step.dependencies, })), }); } } startStep(stepName, inputSize) { this.stepTimings.set(stepName, Date.now()); this.metrics.totalSteps++; this.log(LogLevel.DEBUG, `Step '${stepName}' starting`, { inputSize: this.formatBytes(inputSize), stepIndex: this.metrics.totalSteps, memoryUsage: this.getMemoryUsage(), }); } completeStep(stepName, result) { const startTime = this.stepTimings.get(stepName); const duration = startTime ? Date.now() - startTime : result.duration; this.metrics.successfulSteps++; this.metrics.totalFieldsTracked += result.fieldsTracked; this.metrics.totalProcessingTime += duration; const logLevel = result.errors.length > 0 ? LogLevel.WARN : LogLevel.INFO; this.log(logLevel, `Step '${stepName}' completed`, { duration: `${duration}ms`, inputSize: this.formatBytes(result.inputSize), outputSize: this.formatBytes(result.outputSize), sizeChange: this.formatSizeChange(result.inputSize, result.outputSize), fieldsTracked: result.fieldsTracked, errors: result.errors.length, warnings: result.warnings.length, success: result.success, }); if (result.errors.length > 0 && this.shouldLog(LogLevel.WARN)) { result.errors.forEach(error => { this.log(LogLevel.WARN, `Step error in '${stepName}': ${error.message}`, { code: error.code, severity: error.severity, recoverable: error.recoverable, }); }); } if (result.warnings.length > 0 && this.shouldLog(LogLevel.DEBUG)) { result.warnings.forEach(warning => { this.log(LogLevel.DEBUG, `Step warning in '${stepName}': ${warning}`); }); } } skipStep(stepName, reason) { this.metrics.skippedSteps++; this.log(LogLevel.DEBUG, `Step '${stepName}' skipped`, { reason }); } errorStep(stepName, error) { this.metrics.failedSteps++; this.log(LogLevel.ERROR, `Step '${stepName}' failed: ${error.message}`, { code: error.code, severity: error.severity, recoverable: error.recoverable, location: error.location, }); } completePipeline(result) { const totalDuration = Date.now() - this.startTime; const logLevel = result.success ? LogLevel.INFO : LogLevel.ERROR; this.log(logLevel, 'Pipeline execution completed', { success: result.success, totalDuration: `${totalDuration}ms`, stepsExecuted: result.stepResults.length, contentSize: this.formatBytes(result.content.length), fieldsTracked: result.fieldReport?.total || 0, errors: result.errors.length, warnings: result.warnings.length, exportedFiles: result.exportedFiles?.length || 0, }); if (this.shouldLog(LogLevel.INFO) && this.config.enableMetrics) { this.logMetricsSummary(); } } errorPipeline(error) { this.log(LogLevel.ERROR, `Pipeline execution failed: ${error.message}`, { code: error.code, stepName: error.stepName, recoverable: error.recoverable, }); } logFieldTracking(fieldName, action, metadata) { if (this.shouldLog(LogLevel.TRACE)) { this.log(LogLevel.TRACE, `Field tracking: ${action} for '${fieldName}'`, metadata); } } generateReport() { const lines = []; lines.push('='.repeat(60)); lines.push('PIPELINE EXECUTION REPORT'); lines.push('='.repeat(60)); lines.push(`Total Steps: ${this.metrics.totalSteps}`); lines.push(`Successful: ${this.metrics.successfulSteps}`); lines.push(`Failed: ${this.metrics.failedSteps}`); lines.push(`Skipped: ${this.metrics.skippedSteps}`); lines.push(`Total Fields Tracked: ${this.metrics.totalFieldsTracked}`); lines.push(`Total Processing Time: ${this.metrics.totalProcessingTime}ms`); if (this.config.enableMemoryTracking) { lines.push(`Peak Memory Usage: ${this.formatBytes(this.metrics.memoryPeakUsage)}`); } lines.push('='.repeat(60)); return lines.join('\n'); } resetMetrics() { this.metrics = { totalSteps: 0, successfulSteps: 0, failedSteps: 0, skippedSteps: 0, totalFieldsTracked: 0, totalProcessingTime: 0, memoryPeakUsage: 0, }; this.stepTimings.clear(); } shouldLog(level) { const levels = [LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG, LogLevel.TRACE]; const configIndex = levels.indexOf(this.config.level); const messageIndex = levels.indexOf(level); return messageIndex <= configIndex; } log(level, message, data) { if (!this.shouldLog(level)) { return; } const timestamp = this.config.enableTimestamps ? new Date().toISOString() : ''; const prefix = this.config.prefix; const colorizedLevel = this.colorizeLevel(level); if (this.config.outputFormat === 'json') { const logEntry = { timestamp, level, message, data: data || {}, }; console.log(JSON.stringify(logEntry)); } else if (this.config.outputFormat === 'structured') { const parts = [timestamp, prefix, colorizedLevel, message].filter(Boolean); console.log(parts.join(' '), data ? JSON.stringify(data, null, 2) : ''); } else { const parts = [timestamp, prefix, colorizedLevel, message].filter(Boolean); console.log(parts.join(' ')); } this.trackMemoryUsage(); } colorizeLevel(level) { if (!this.config.enableColors) { return level.toUpperCase(); } const colors = { [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 = colors[level] || ''; return `${color}${level.toUpperCase()}${reset}`; } formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; } formatSizeChange(inputSize, outputSize) { const diff = outputSize - inputSize; const sign = diff >= 0 ? '+' : ''; return `${sign}${diff} chars`; } getMemoryUsage() { if (!this.config.enableMemoryTracking) { return 'N/A'; } const usage = process.memoryUsage(); return this.formatBytes(usage.heapUsed); } trackMemoryUsage() { if (this.config.enableMemoryTracking) { const usage = process.memoryUsage(); this.metrics.memoryPeakUsage = Math.max(this.metrics.memoryPeakUsage, usage.heapUsed); } } logMetricsSummary() { this.log(LogLevel.INFO, 'Pipeline metrics summary', { efficiency: { successRate: `${((this.metrics.successfulSteps / this.metrics.totalSteps) * 100).toFixed(1)}%`, averageStepTime: `${(this.metrics.totalProcessingTime / this.metrics.totalSteps).toFixed(1)}ms`, fieldsPerSecond: (this.metrics.totalFieldsTracked / (this.metrics.totalProcessingTime / 1000)).toFixed(1), }, breakdown: { successful: this.metrics.successfulSteps, failed: this.metrics.failedSteps, skipped: this.metrics.skippedSteps, }, }); } } /** * Null logger that discards all log messages (useful for testing) * * @class NullPipelineLogger */ export class NullPipelineLogger { startPipeline() { } startStep() { } completeStep() { } skipStep() { } errorStep() { } completePipeline() { } errorPipeline() { } logFieldTracking() { } generateReport() { return ''; } } //# sourceMappingURL=pipeline-logger.js.map