UNPKG

@gftdcojp/ksqldb-orm

Version:

ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions

376 lines 14.4 kB
"use strict"; 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.consoleLogger = exports.log = exports.Logger = void 0; const winston = __importStar(require("winston")); const path = __importStar(require("path")); const fs = __importStar(require("fs")); /** * @fileoverview 統一されたログ機能 * @description プロジェクト全体で使用する統一されたログ機能を提供します */ // 環境変数の安全な取得(ブラウザ環境では undefined) const getEnvVar = (key) => { try { return typeof process !== 'undefined' && process.env ? process.env[key] : undefined; } catch { return undefined; } }; // ログディレクトリの絶対パス取得 const getLogDirectory = () => { // 環境変数でログディレクトリを指定可能 const customLogDir = getEnvVar('GFTD_LOG_DIR') || getEnvVar('LOG_DIR'); if (customLogDir) { return path.resolve(customLogDir); } // デフォルトでプロジェクトルートの logs ディレクトリを使用 // Next.js や他の環境でも動作するように process.cwd() を使用 const projectRoot = process.cwd(); return path.join(projectRoot, 'logs'); }; // ブラウザ環境かどうかチェック const isBrowser = () => { try { return typeof window !== 'undefined' && typeof process === 'undefined'; } catch { return true; // エラーの場合はブラウザ環境と仮定 } }; // サーバーレス環境の検出 const isServerlessEnvironment = () => { try { // 各種サーバーレス環境の検出 const awsLambda = getEnvVar('AWS_LAMBDA_FUNCTION_NAME'); const vercel = getEnvVar('VERCEL') || getEnvVar('NOW_REGION'); const netlify = getEnvVar('NETLIFY'); const cloudflare = getEnvVar('CF_PAGES'); const railway = getEnvVar('RAILWAY_ENVIRONMENT'); const render = getEnvVar('RENDER'); // AWS Lambda環境の特徴的なパスパターン const cwd = process.cwd(); const isLambdaPath = cwd.includes('/var/task') || cwd.includes('/var/runtime'); // 読み取り専用ファイルシステムの検出 const isReadOnlyFs = process.env.LAMBDA_TASK_ROOT !== undefined; return !!(awsLambda || vercel || netlify || cloudflare || railway || render || isLambdaPath || isReadOnlyFs); } catch { return false; } }; // Node.js環境かつファイルシステムが利用可能かチェック const isFileSystemAvailable = () => { try { // ブラウザ環境では無効 if (isBrowser()) { return false; } // サーバーレス環境では無効 if (isServerlessEnvironment()) { return false; } // テスト環境では無効(テストファイルの汚染防止) if (getEnvVar('NODE_ENV') === 'test') { return false; } // ファイルシステムAPIの存在確認 return typeof fs !== 'undefined' && typeof fs.mkdirSync === 'function'; } catch { return false; } }; // ログディレクトリの作成 const ensureLogDirectory = () => { if (!isFileSystemAvailable()) { // ファイルシステムが利用できない理由を詳細に記録 const reasons = []; if (isBrowser()) { reasons.push('browser environment'); } if (isServerlessEnvironment()) { reasons.push('serverless environment detected'); } if (getEnvVar('NODE_ENV') === 'test') { reasons.push('test environment'); } const reasonText = reasons.length > 0 ? ` (${reasons.join(', ')})` : ''; console.warn(`File logging is disabled: Unable to create log directory or file system is not available${reasonText}`); return null; } try { const logDir = getLogDirectory(); // ディレクトリが存在しない場合は作成 if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } return logDir; } catch (error) { // ディレクトリ作成に失敗した場合は詳細なエラー情報を出力 const errorMessage = error instanceof Error ? error.message : String(error); console.warn(`Failed to create log directory: ${errorMessage}`); console.warn('File logging is disabled: Unable to create log directory or file system is not available'); return null; } }; /** * 機密情報をマスキングするパターン */ const SENSITIVE_PATTERNS = [ // JWT トークン { pattern: /eyJ[A-Za-z0-9_\/+-]*\.eyJ[A-Za-z0-9_\/+-]*\.[A-Za-z0-9_\/+-]*/g, replacement: '[JWT_TOKEN_REDACTED]' }, // Bearer トークン { pattern: /Bearer\s+[A-Za-z0-9_\/+-]+/gi, replacement: 'Bearer [TOKEN_REDACTED]' }, // API キー(様々な形式) { pattern: /(?:api_key|apikey|api-key)\s*[:=]\s*["']?[A-Za-z0-9_\/+-]{16,}["']?/gi, replacement: 'api_key=[API_KEY_REDACTED]' }, // パスワード { pattern: /(?:password|passwd|pwd)\s*[:=]\s*["']?[^\s"',;}{]+["']?/gi, replacement: 'password=[PASSWORD_REDACTED]' }, // Basic認証 { pattern: /Basic\s+[A-Za-z0-9_\/+-]+=*/gi, replacement: 'Basic [BASIC_AUTH_REDACTED]' }, // 秘密鍵 { pattern: /(?:secret|secret_key|secretkey)\s*[:=]\s*["']?[A-Za-z0-9_\/+-]{16,}["']?/gi, replacement: 'secret=[SECRET_REDACTED]' }, // クレジットカード番号 { pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replacement: '[CREDIT_CARD_REDACTED]' }, // 電話番号 { pattern: /\b\d{3}-\d{4}-\d{4}\b/g, replacement: '[PHONE_REDACTED]' }, // メールアドレス(部分的にマスキング) { pattern: /([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,})/g, replacement: (_match, user, domain) => { const maskedUser = user.length > 2 ? user.charAt(0) + '*'.repeat(user.length - 2) + user.charAt(user.length - 1) : user; return `${maskedUser}@${domain}`; } }, ]; /** * 機密情報をマスキングする関数 * @param data - マスキング対象のデータ * @returns マスキング済みのデータ */ function sanitizeSensitiveData(data) { if (typeof data === 'string') { let sanitized = data; for (const { pattern, replacement } of SENSITIVE_PATTERNS) { if (typeof replacement === 'function') { sanitized = sanitized.replace(pattern, replacement); } else { sanitized = sanitized.replace(pattern, replacement); } } return sanitized; } if (Array.isArray(data)) { return data.map(item => sanitizeSensitiveData(item)); } if (data && typeof data === 'object') { const sanitized = {}; for (const [key, value] of Object.entries(data)) { // キー名でも機密情報を判定 const lowerKey = key.toLowerCase(); if (lowerKey.includes('password') || lowerKey.includes('secret') || lowerKey.includes('token') || lowerKey.includes('key') || lowerKey.includes('auth') || lowerKey.includes('credential')) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = sanitizeSensitiveData(value); } } return sanitized; } return data; } // ログレベル設定 const logLevel = getEnvVar('GFTD_LOG_LEVEL') || getEnvVar('LOG_LEVEL') || 'info'; // 機密情報サニタイズフォーマット const sanitizeFormat = winston.format((info) => { // メッセージとメタデータの両方をサニタイズ info.message = sanitizeSensitiveData(info.message); // メタデータオブジェクトをサニタイズ const { level, message, timestamp, stack, ...meta } = info; const sanitizedMeta = sanitizeSensitiveData(meta); return { level, message, timestamp, stack, ...sanitizedMeta }; }); // ログフォーマット設定 const logFormat = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), winston.format.errors({ stack: true }), sanitizeFormat(), // 機密情報サニタイズを追加 winston.format.colorize({ all: true }), winston.format.printf(({ level, message, timestamp, stack }) => { if (stack) { return `[${timestamp}] [${level}] ${message}\n${stack}`; } return `[${timestamp}] [${level}] ${message}`; })); // ログトランスポート設定 const transports = [ new winston.transports.Console({ format: logFormat, level: logLevel }) ]; // ログディレクトリの準備 const logDirectory = ensureLogDirectory(); // ファイルログ設定(ファイルシステムが利用可能な場合のみ) if (logDirectory) { // エラーログファイル transports.push(new winston.transports.File({ filename: path.join(logDirectory, 'error.log'), level: 'error', maxsize: 10 * 1024 * 1024, // 10MB maxFiles: 5, format: winston.format.combine(winston.format.timestamp(), sanitizeFormat(), // 機密情報サニタイズを追加 winston.format.json()) })); // 統合ログファイル transports.push(new winston.transports.File({ filename: path.join(logDirectory, 'combined.log'), maxsize: 50 * 1024 * 1024, // 50MB maxFiles: 10, format: winston.format.combine(winston.format.timestamp(), sanitizeFormat(), // 機密情報サニタイズを追加 winston.format.json()) })); // ファイルロギング有効時の確認メッセージ console.info(`📄 File logging enabled: ${logDirectory}`); } // Winstonロガー作成 const winstonLogger = winston.createLogger({ level: logLevel, format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()), transports, exitOnError: false }); /** * アプリケーション全体で使用する統一されたログ機能 */ class Logger { constructor() { this.logger = winstonLogger; } /** * シングルトンインスタンスを取得 */ static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } /** * 現在のログ設定情報を取得 */ getLogInfo() { return { logDirectory: logDirectory, transports: transports.map(t => t.constructor.name), level: logLevel }; } /** * デバッグログ * @param message ログメッセージ * @param meta 追加メタデータ */ debug(message, meta) { this.logger.debug(message, meta); } /** * 情報ログ * @param message ログメッセージ * @param meta 追加メタデータ */ info(message, meta) { this.logger.info(message, meta); } /** * 警告ログ * @param message ログメッセージ * @param meta 追加メタデータ */ warn(message, meta) { this.logger.warn(message, meta); } /** * エラーログ * @param message ログメッセージ * @param error エラーオブジェクト * @param meta 追加メタデータ */ error(message, error, meta) { if (error instanceof Error) { this.logger.error(message, { ...meta, error: error.message, stack: error.stack }); } else { this.logger.error(message, { ...meta, error }); } } /** * 成功ログ (info level) * @param message ログメッセージ * @param meta 追加メタデータ */ success(message, meta) { this.logger.info(`✅ ${message}`, meta); } /** * 失敗ログ (error level) * @param message ログメッセージ * @param error エラーオブジェクト * @param meta 追加メタデータ */ failure(message, error, meta) { this.error(`❌ ${message}`, error, meta); } } exports.Logger = Logger; // デフォルトのログ機能をエクスポート exports.log = Logger.getInstance(); // 従来のコンソールログ代替機能 exports.consoleLogger = { debug: (message, ...args) => exports.log.debug(message, { args }), info: (message, ...args) => exports.log.info(message, { args }), warn: (message, ...args) => exports.log.warn(message, { args }), error: (message, ...args) => exports.log.error(message, args[0], { args: args.slice(1) }), log: (message, ...args) => exports.log.info(message, { args }), success: (message, ...args) => exports.log.success(message, { args }), failure: (message, error, ...args) => exports.log.failure(message, error, { args }) }; //# sourceMappingURL=logger.js.map