UNPKG

sql-talk

Version:

SQL Talk - 自然言語をSQLに変換するMCPサーバー(安全性保護・SSHトンネル対応) / SQL Talk - MCP Server for Natural Language to SQL conversion with safety guards and SSH tunnel support

118 lines 3.93 kB
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs'; import { dirname, resolve } from 'path'; import { logger } from './logger.js'; import { configManager } from './config.js'; export class AuditLogger { static instance; logPath = null; sink = 'file'; constructor() { } static getInstance() { if (!AuditLogger.instance) { AuditLogger.instance = new AuditLogger(); } return AuditLogger.instance; } initialize() { try { const config = configManager.getConfig(); this.sink = config.audit.sink; if (this.sink === 'file') { this.logPath = resolve(process.cwd(), config.audit.path); // Ensure directory exists const dir = dirname(this.logPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } // Initialize file if it doesn't exist if (!existsSync(this.logPath)) { writeFileSync(this.logPath, '', 'utf-8'); } logger.info(`Audit logging initialized: ${this.logPath}`); } else { logger.info('Audit logging initialized: stdout'); } } catch (error) { logger.error('Failed to initialize audit logging:', error); // Fallback to stdout this.sink = 'stdout'; } } log(entry) { const auditEntry = { ts: new Date().toISOString(), actor: 'mcp-server', ...entry }; try { const logLine = JSON.stringify(auditEntry) + '\n'; if (this.sink === 'file' && this.logPath) { appendFileSync(this.logPath, logLine, 'utf-8'); } else if (!process.env.MCP_MODE) { // Only output to stdout if not in MCP mode process.stdout.write(`[AUDIT] ${logLine}`); } else { // In MCP mode, fallback to stderr process.stderr.write(`[AUDIT] ${logLine}`); } } catch (error) { logger.error('Failed to write audit log:', error); // Fallback to logger logger.info('Audit entry (fallback):', auditEntry); } } logSqlExecution(sql, elapsedMs, rows, piiMasked, error) { this.log({ event: 'sql_execution', sql, elapsed_ms: elapsedMs, rows, pii_masked: piiMasked, error }); } logSchemaRefresh(scope, elapsedMs, tablesAdded, tablesRemoved, error) { this.log({ event: 'schema_refresh', elapsed_ms: elapsedMs, sql: `REFRESH_SCHEMA(${scope})`, rows: tablesAdded, error }); } logCommentProposal(target, proposalCount, elapsedMs, error) { this.log({ event: 'comment_proposal', sql: `PROPOSE_COMMENTS(${target})`, elapsed_ms: elapsedMs, rows: proposalCount, error }); } logCommentApplication(sqlText, affected, elapsedMs, approved, error) { this.log({ event: 'comment_application', sql: sqlText.slice(0, 200) + (sqlText.length > 200 ? '...' : ''), elapsed_ms: elapsedMs, rows: affected, pii_masked: !approved, error }); } logToolExecution(toolName, input, elapsedMs, success, error) { this.log({ event: `tool_${toolName}`, sql: JSON.stringify(input).slice(0, 100), elapsed_ms: elapsedMs, pii_masked: !success, error }); } } export const auditLogger = AuditLogger.getInstance(); //# sourceMappingURL=audit.js.map