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
JavaScript
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