UNPKG

dnsweeper

Version:

Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.

287 lines (251 loc) 7.79 kB
/* eslint-disable no-console */ import chalk from 'chalk'; import ora from 'ora'; import { getLogger, createLogger, setGlobalLogger, type StructuredLogger, } from './structured-logger.js'; import type { Ora } from 'ora'; export class Logger { private verbose: boolean; private quiet: boolean; private spinner: Ora | null = null; private structuredLogger: StructuredLogger; constructor( options: { verbose?: boolean | undefined; quiet?: boolean | undefined; enableStructuredLogging?: boolean; logFile?: string; logLevel?: string; } = {}, ) { this.verbose = options.verbose ?? false; this.quiet = options.quiet ?? false; // 構造化ログを初期化 if (options.enableStructuredLogging !== false) { this.structuredLogger = createLogger({ level: options.logLevel || (this.verbose ? 'debug' : 'info'), console: false, // 既存のコンソール出力を維持 file: options.logFile, meta: { service: 'dnsweeper', component: 'cli', }, }); setGlobalLogger(this.structuredLogger); } else { this.structuredLogger = getLogger(); } } info(message: string, meta?: Record<string, any>): void { if (!this.quiet) { console.log(chalk.blue('ℹ'), message); } this.structuredLogger.info(message, meta); } success(message: string, meta?: Record<string, any>): void { if (!this.quiet) { console.log(chalk.green('✓'), message); } this.structuredLogger.info(`SUCCESS: ${message}`, meta); } error(message: string, error?: Error, meta?: Record<string, any>): void { console.error(chalk.red('✗'), message); this.structuredLogger.error(message, meta, error); } warn(message: string, meta?: Record<string, any>): void { if (!this.quiet) { console.warn(chalk.yellow('⚠'), message); } this.structuredLogger.warn(message, meta); } debug(message: string, meta?: Record<string, any>): void { if (this.verbose && !this.quiet) { console.log(chalk.gray('[DEBUG]'), message); } this.structuredLogger.debug(message, meta); } startSpinner(text: string, meta?: Record<string, any>): void { if (!this.quiet) { this.spinner = ora(text).start(); } this.structuredLogger.info(`SPINNER_START: ${text}`, meta); } stopSpinner(success: boolean = true, text?: string, meta?: Record<string, any>): void { if (this.spinner) { if (success) { this.spinner.succeed(text); this.structuredLogger.info(`SPINNER_SUCCESS: ${text || 'Operation completed'}`, meta); } else { this.spinner.fail(text); this.structuredLogger.error(`SPINNER_FAIL: ${text || 'Operation failed'}`, meta); } this.spinner = null; } } table(data: unknown[], meta?: Record<string, any>): void { if (!this.quiet) { console.table(data); } this.structuredLogger.info('TABLE_OUTPUT', { ...meta, tableData: Array.isArray(data) ? data.slice(0, 10) : data, // 最初の10行のみログ }); } json(data: unknown, meta?: Record<string, any>): void { console.log(JSON.stringify(data, null, 2)); this.structuredLogger.info('JSON_OUTPUT', { ...meta, jsonData: data }); } // === 新しい構造化ログメソッド === /** * HTTP リクエスト/レスポンスをログ */ http( method: string, url: string, statusCode?: number, duration?: number, meta?: Record<string, any>, ): void { const message = `${method} ${url}${statusCode ? ` ${statusCode}` : ''}${duration ? ` ${duration}ms` : ''}`; if (!this.quiet) { const color = statusCode && statusCode >= 400 ? chalk.red : chalk.cyan; console.log(color('HTTP'), message); } this.structuredLogger.http(message, { ...meta, method, url, statusCode, duration, }); } /** * パフォーマンス測定 */ async profile<T>( label: string, fn: () => Promise<T> | T, meta?: Record<string, any>, ): Promise<T> { const start = Date.now(); this.debug(`Starting: ${label}`, meta); try { const result = await fn(); const duration = Date.now() - start; this.success(`Completed: ${label} (${duration}ms)`, { ...meta, duration }); return result; } catch (error) { const duration = Date.now() - start; this.error( `Failed: ${label} (${duration}ms)`, error instanceof Error ? error : new Error(String(error)), { ...meta, duration }, ); throw error; } } /** * タイマー開始 */ startTimer(label: string, meta?: Record<string, any>): () => void { const start = Date.now(); this.debug(`Timer started: ${label}`, meta); return () => { const duration = Date.now() - start; this.info(`Timer: ${label} completed in ${duration}ms`, { ...meta, duration }); }; } /** * 子ロガーを作成 */ child(meta: Record<string, any>, context?: string): Logger { const childLogger = new Logger({ verbose: this.verbose, quiet: this.quiet, enableStructuredLogging: false, // 既存の構造化ログを使用 }); childLogger.structuredLogger = this.structuredLogger.child(meta, context); return childLogger; } /** * コンテキストをプッシュ */ pushContext(context: string): void { this.structuredLogger.pushContext(context); } /** * コンテキストをポップ */ popContext(): string | undefined { return this.structuredLogger.popContext(); } /** * 構造化ログの統計情報を表示 */ showLogStats(): void { // ログ統計の表示(実装は構造化ログシステムに依存) this.info('Log statistics feature available via structured logging'); } /** * ログレベルを変更 */ setLogLevel(level: string): void { // 注意: 既存のトランスポートのレベルは変更されない this.structuredLogger.info(`Log level change requested: ${level}`, { newLevel: level }); } /** * 重要なイベントをログ(常に出力) */ critical(message: string, meta?: Record<string, any>, error?: Error): void { console.error(chalk.red.bold('🚨 CRITICAL'), message); this.structuredLogger.error(`CRITICAL: ${message}`, meta, error); } /** * セキュリティ関連のログ */ security(message: string, meta?: Record<string, any>): void { if (!this.quiet) { console.log(chalk.magenta('🔒 SECURITY'), message); } this.structuredLogger.warn(`SECURITY: ${message}`, { ...meta, category: 'security' }); } /** * パフォーマンス警告 */ performance( message: string, duration: number, threshold: number = 1000, meta?: Record<string, any>, ): void { const isWarning = duration > threshold; const color = isWarning ? chalk.yellow : chalk.green; if (!this.quiet) { console.log(color('⏱ PERF'), `${message} (${duration}ms)`); } if (isWarning) { this.structuredLogger.warn(`PERFORMANCE: ${message}`, { ...meta, duration, threshold }); } else { this.structuredLogger.info(`PERFORMANCE: ${message}`, { ...meta, duration, threshold }); } } /** * ビジネスロジック関連のログ */ business(message: string, meta?: Record<string, any>): void { if (!this.quiet) { console.log(chalk.blue('📊 BUSINESS'), message); } this.structuredLogger.info(`BUSINESS: ${message}`, { ...meta, category: 'business' }); } /** * 既存の構造化ロガーを取得 */ getStructuredLogger(): StructuredLogger { return this.structuredLogger; } }