mcp-database-inspector 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -0
- package/dist/database/connection.d.ts +13 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +155 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/manager.d.ts +28 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +621 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/postgres-connection.d.ts +10 -0
- package/dist/database/postgres-connection.d.ts.map +1 -0
- package/dist/database/postgres-connection.js +113 -0
- package/dist/database/postgres-connection.js.map +1 -0
- package/dist/database/types.d.ts +84 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +6 -0
- package/dist/database/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +186 -0
- package/dist/server.js.map +1 -0
- package/dist/test-defaults.d.ts +2 -0
- package/dist/test-defaults.d.ts.map +1 -0
- package/dist/test-defaults.js +57 -0
- package/dist/test-defaults.js.map +1 -0
- package/dist/tools/analyze-query.d.ts +27 -0
- package/dist/tools/analyze-query.d.ts.map +1 -0
- package/dist/tools/analyze-query.js +71 -0
- package/dist/tools/analyze-query.js.map +1 -0
- package/dist/tools/execute-query.d.ts +33 -0
- package/dist/tools/execute-query.d.ts.map +1 -0
- package/dist/tools/execute-query.js +57 -0
- package/dist/tools/execute-query.js.map +1 -0
- package/dist/tools/get-foreign-keys.d.ts +38 -0
- package/dist/tools/get-foreign-keys.d.ts.map +1 -0
- package/dist/tools/get-foreign-keys.js +391 -0
- package/dist/tools/get-foreign-keys.js.map +1 -0
- package/dist/tools/get-indexes.d.ts +38 -0
- package/dist/tools/get-indexes.d.ts.map +1 -0
- package/dist/tools/get-indexes.js +472 -0
- package/dist/tools/get-indexes.js.map +1 -0
- package/dist/tools/information-schema-query.d.ts +33 -0
- package/dist/tools/information-schema-query.d.ts.map +1 -0
- package/dist/tools/information-schema-query.js +76 -0
- package/dist/tools/information-schema-query.js.map +1 -0
- package/dist/tools/inspect-table.d.ts +38 -0
- package/dist/tools/inspect-table.d.ts.map +1 -0
- package/dist/tools/inspect-table.js +351 -0
- package/dist/tools/inspect-table.js.map +1 -0
- package/dist/tools/list-databases.d.ts +14 -0
- package/dist/tools/list-databases.d.ts.map +1 -0
- package/dist/tools/list-databases.js +83 -0
- package/dist/tools/list-databases.js.map +1 -0
- package/dist/tools/list-tables.d.ts +19 -0
- package/dist/tools/list-tables.d.ts.map +1 -0
- package/dist/tools/list-tables.js +130 -0
- package/dist/tools/list-tables.js.map +1 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +98 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +28 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +132 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/validators/input-validator.d.ts +76 -0
- package/dist/validators/input-validator.d.ts.map +1 -0
- package/dist/validators/input-validator.js +295 -0
- package/dist/validators/input-validator.js.map +1 -0
- package/dist/validators/query-validator.d.ts +19 -0
- package/dist/validators/query-validator.d.ts.map +1 -0
- package/dist/validators/query-validator.js +229 -0
- package/dist/validators/query-validator.js.map +1 -0
- package/enhanced_sql_prompt.md +324 -0
- package/examples/claude-config.json +23 -0
- package/examples/roo-config.json +16 -0
- package/package.json +42 -0
- package/src/database/connection.ts +165 -0
- package/src/database/manager.ts +682 -0
- package/src/database/postgres-connection.ts +123 -0
- package/src/database/types.ts +93 -0
- package/src/index.ts +136 -0
- package/src/server.ts +254 -0
- package/src/test-defaults.ts +63 -0
- package/src/tools/analyze-query.test.ts +100 -0
- package/src/tools/analyze-query.ts +112 -0
- package/src/tools/execute-query.ts +91 -0
- package/src/tools/get-foreign-keys.test.ts +51 -0
- package/src/tools/get-foreign-keys.ts +488 -0
- package/src/tools/get-indexes.test.ts +51 -0
- package/src/tools/get-indexes.ts +570 -0
- package/src/tools/information-schema-query.ts +125 -0
- package/src/tools/inspect-table.test.ts +59 -0
- package/src/tools/inspect-table.ts +440 -0
- package/src/tools/list-databases.ts +119 -0
- package/src/tools/list-tables.ts +181 -0
- package/src/utils/errors.ts +103 -0
- package/src/utils/logger.ts +158 -0
- package/src/validators/input-validator.ts +318 -0
- package/src/validators/query-validator.ts +267 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { DatabaseManager } from '../database/manager.js';
|
|
3
|
+
import { InputValidator } from '../validators/input-validator.js';
|
|
4
|
+
import { Logger } from '../utils/logger.js';
|
|
5
|
+
import { ToolError, createErrorResponse } from '../utils/errors.js';
|
|
6
|
+
|
|
7
|
+
// Tool schema
|
|
8
|
+
const ListTablesArgsSchema = z.object({
|
|
9
|
+
database: z.string().min(1, 'Database name is required')
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export interface ListTablesTool {
|
|
13
|
+
name: 'list_tables';
|
|
14
|
+
description: 'List all tables in the specified database with metadata';
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object';
|
|
17
|
+
properties: {
|
|
18
|
+
database: {
|
|
19
|
+
type: 'string';
|
|
20
|
+
description: 'Name of the database to list tables from';
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
required: ['database'];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const listTablesToolDefinition: ListTablesTool = {
|
|
28
|
+
name: 'list_tables',
|
|
29
|
+
description: 'List all tables in the specified database with metadata',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
database: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Name of the database to list tables from'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ['database']
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function handleListTables(
|
|
43
|
+
args: unknown,
|
|
44
|
+
dbManager: DatabaseManager
|
|
45
|
+
): Promise<any> {
|
|
46
|
+
try {
|
|
47
|
+
Logger.info('Executing list_tables tool');
|
|
48
|
+
|
|
49
|
+
// Validate arguments
|
|
50
|
+
const validationResult = ListTablesArgsSchema.safeParse(args);
|
|
51
|
+
if (!validationResult.success) {
|
|
52
|
+
Logger.warn('Invalid arguments for list_tables', validationResult.error);
|
|
53
|
+
throw new ToolError(
|
|
54
|
+
`Invalid arguments: ${validationResult.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
|
|
55
|
+
'list_tables'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { database } = validationResult.data;
|
|
60
|
+
|
|
61
|
+
// Sanitize database name
|
|
62
|
+
const sanitizedDatabase = InputValidator.sanitizeString(database);
|
|
63
|
+
|
|
64
|
+
// Validate database name format
|
|
65
|
+
const dbNameValidation = InputValidator.validateDatabaseName(sanitizedDatabase);
|
|
66
|
+
if (!dbNameValidation.isValid) {
|
|
67
|
+
throw new ToolError(
|
|
68
|
+
`Invalid database name: ${dbNameValidation.error}`,
|
|
69
|
+
'list_tables'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Logger.info(`Listing tables for database: ${sanitizedDatabase}`);
|
|
74
|
+
Logger.time('list_tables_execution');
|
|
75
|
+
|
|
76
|
+
// Get tables from database
|
|
77
|
+
const tables = await dbManager.getTables(sanitizedDatabase);
|
|
78
|
+
|
|
79
|
+
Logger.timeEnd('list_tables_execution');
|
|
80
|
+
Logger.info(`Found ${tables.length} tables in database: ${sanitizedDatabase}`);
|
|
81
|
+
|
|
82
|
+
// Group tables by type
|
|
83
|
+
const tablesByType = tables.reduce((acc, table) => {
|
|
84
|
+
const type = table.tableType;
|
|
85
|
+
if (!acc[type]) {
|
|
86
|
+
acc[type] = [];
|
|
87
|
+
}
|
|
88
|
+
acc[type].push(table);
|
|
89
|
+
return acc;
|
|
90
|
+
}, {} as Record<string, typeof tables>);
|
|
91
|
+
|
|
92
|
+
// Calculate statistics
|
|
93
|
+
const stats = {
|
|
94
|
+
totalTables: tables.length,
|
|
95
|
+
baseTables: (tablesByType['BASE TABLE'] || []).length,
|
|
96
|
+
views: (tablesByType['VIEW'] || []).length,
|
|
97
|
+
systemTables: (tablesByType['SYSTEM TABLE'] || []).length,
|
|
98
|
+
totalEstimatedRows: tables.reduce((sum, table) => sum + (table.tableRows || 0), 0)
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Format response
|
|
102
|
+
const response = {
|
|
103
|
+
database: sanitizedDatabase,
|
|
104
|
+
tables: tables.map(table => ({
|
|
105
|
+
name: table.tableName,
|
|
106
|
+
type: table.tableType,
|
|
107
|
+
engine: table.engine,
|
|
108
|
+
estimatedRows: table.tableRows,
|
|
109
|
+
comment: table.tableComment,
|
|
110
|
+
category: table.tableType === 'BASE TABLE' ? 'table' :
|
|
111
|
+
table.tableType === 'VIEW' ? 'view' :
|
|
112
|
+
'system'
|
|
113
|
+
})),
|
|
114
|
+
statistics: stats,
|
|
115
|
+
tablesByType: Object.keys(tablesByType).map(type => ({
|
|
116
|
+
type,
|
|
117
|
+
count: tablesByType[type].length,
|
|
118
|
+
tables: tablesByType[type].map(t => t.tableName)
|
|
119
|
+
})),
|
|
120
|
+
summary: {
|
|
121
|
+
hasData: tables.length > 0,
|
|
122
|
+
message: tables.length === 0
|
|
123
|
+
? `No tables found in database '${sanitizedDatabase}'. Database may be empty or inaccessible.`
|
|
124
|
+
: `Found ${tables.length} table(s) in database '${sanitizedDatabase}': ${stats.baseTables} base tables, ${stats.views} views.`
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
Logger.debug('list_tables completed successfully', {
|
|
129
|
+
database: sanitizedDatabase,
|
|
130
|
+
tableCount: tables.length,
|
|
131
|
+
baseTables: stats.baseTables,
|
|
132
|
+
views: stats.views
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
content: [{
|
|
137
|
+
type: 'text',
|
|
138
|
+
text: JSON.stringify(response, null, 2)
|
|
139
|
+
}]
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
Logger.error('Error in list_tables tool', error);
|
|
144
|
+
|
|
145
|
+
if (error instanceof ToolError) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
throw new ToolError(
|
|
150
|
+
`Failed to list tables: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
151
|
+
'list_tables',
|
|
152
|
+
error instanceof Error ? error : undefined
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function getTablesSummary(
|
|
158
|
+
dbManager: DatabaseManager,
|
|
159
|
+
database: string
|
|
160
|
+
): Promise<string> {
|
|
161
|
+
try {
|
|
162
|
+
const tables = await dbManager.getTables(database);
|
|
163
|
+
const baseTables = tables.filter(t => t.tableType === 'BASE TABLE').length;
|
|
164
|
+
const views = tables.filter(t => t.tableType === 'VIEW').length;
|
|
165
|
+
|
|
166
|
+
if (tables.length === 0) {
|
|
167
|
+
return `Database '${database}' has no tables`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parts = [];
|
|
171
|
+
if (baseTables > 0) parts.push(`${baseTables} table(s)`);
|
|
172
|
+
if (views > 0) parts.push(`${views} view(s)`);
|
|
173
|
+
|
|
174
|
+
return `Database '${database}' contains ${parts.join(', ')}: ${
|
|
175
|
+
tables.slice(0, 5).map(t => t.tableName).join(', ')
|
|
176
|
+
}${tables.length > 5 ? `, and ${tables.length - 5} more` : ''}`;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
Logger.error(`Error getting table summary for ${database}`, error);
|
|
179
|
+
return `Error retrieving tables for database '${database}'`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export class DatabaseError extends Error {
|
|
2
|
+
constructor(message: string, public readonly cause?: Error) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'DatabaseError';
|
|
5
|
+
|
|
6
|
+
if (cause) {
|
|
7
|
+
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ValidationError extends Error {
|
|
13
|
+
constructor(message: string, public readonly field?: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'ValidationError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ConnectionError extends DatabaseError {
|
|
20
|
+
constructor(message: string, public readonly host?: string, public readonly port?: number, cause?: Error) {
|
|
21
|
+
super(message, cause);
|
|
22
|
+
this.name = 'ConnectionError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class QueryError extends DatabaseError {
|
|
27
|
+
constructor(message: string, public readonly query?: string, cause?: Error) {
|
|
28
|
+
super(message, cause);
|
|
29
|
+
this.name = 'QueryError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ConfigurationError extends Error {
|
|
34
|
+
constructor(message: string, public readonly configKey?: string) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'ConfigurationError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ToolError extends Error {
|
|
41
|
+
constructor(message: string, public readonly toolName?: string, cause?: Error) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'ToolError';
|
|
44
|
+
|
|
45
|
+
if (cause) {
|
|
46
|
+
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isRecoverableError(error: Error): boolean {
|
|
52
|
+
// Determine if an error is recoverable (temporary) or fatal
|
|
53
|
+
if (error instanceof ConnectionError) {
|
|
54
|
+
// Network timeouts, temporary connection issues
|
|
55
|
+
return error.message.includes('timeout') ||
|
|
56
|
+
error.message.includes('ECONNREFUSED') ||
|
|
57
|
+
error.message.includes('ENOTFOUND');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (error instanceof QueryError) {
|
|
61
|
+
// Syntax errors are not recoverable, but timeouts might be
|
|
62
|
+
return error.message.includes('timeout');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sanitizeErrorMessage(error: Error): string {
|
|
69
|
+
// Remove sensitive information from error messages
|
|
70
|
+
let message = error.message;
|
|
71
|
+
|
|
72
|
+
// Remove password information from connection strings
|
|
73
|
+
message = message.replace(/password=[^&\s]+/gi, 'password=***');
|
|
74
|
+
message = message.replace(/:([^@\s]+)@/g, ':***@');
|
|
75
|
+
|
|
76
|
+
return message;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createErrorResponse(error: Error): { error: string; code?: string } {
|
|
80
|
+
const sanitized = sanitizeErrorMessage(error);
|
|
81
|
+
|
|
82
|
+
if (error instanceof ValidationError) {
|
|
83
|
+
return { error: sanitized, code: 'VALIDATION_ERROR' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (error instanceof ConnectionError) {
|
|
87
|
+
return { error: sanitized, code: 'CONNECTION_ERROR' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (error instanceof QueryError) {
|
|
91
|
+
return { error: sanitized, code: 'QUERY_ERROR' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (error instanceof DatabaseError) {
|
|
95
|
+
return { error: sanitized, code: 'DATABASE_ERROR' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (error instanceof ToolError) {
|
|
99
|
+
return { error: sanitized, code: 'TOOL_ERROR' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { error: sanitized, code: 'UNKNOWN_ERROR' };
|
|
103
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
enum LogLevel {
|
|
2
|
+
ERROR = 0,
|
|
3
|
+
WARN = 1,
|
|
4
|
+
INFO = 2,
|
|
5
|
+
DEBUG = 3,
|
|
6
|
+
TRACE = 4
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface LogEntry {
|
|
10
|
+
timestamp: string;
|
|
11
|
+
level: string;
|
|
12
|
+
message: string;
|
|
13
|
+
data?: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class Logger {
|
|
17
|
+
private static logLevel: LogLevel = LogLevel.INFO;
|
|
18
|
+
private static logs: LogEntry[] = [];
|
|
19
|
+
private static maxLogs = 1000;
|
|
20
|
+
|
|
21
|
+
static setLogLevel(level: 'error' | 'warn' | 'info' | 'debug' | 'trace'): void {
|
|
22
|
+
const levelMap = {
|
|
23
|
+
error: LogLevel.ERROR,
|
|
24
|
+
warn: LogLevel.WARN,
|
|
25
|
+
info: LogLevel.INFO,
|
|
26
|
+
debug: LogLevel.DEBUG,
|
|
27
|
+
trace: LogLevel.TRACE
|
|
28
|
+
};
|
|
29
|
+
this.logLevel = levelMap[level] ?? LogLevel.INFO;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static getLogLevel(): string {
|
|
33
|
+
const levelNames = ['error', 'warn', 'info', 'debug', 'trace'];
|
|
34
|
+
return levelNames[this.logLevel] || 'info';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static shouldLog(level: LogLevel): boolean {
|
|
38
|
+
return level <= this.logLevel;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static addLog(level: string, message: string, data?: any): void {
|
|
42
|
+
const entry: LogEntry = {
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
level,
|
|
45
|
+
message,
|
|
46
|
+
data
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.logs.push(entry);
|
|
50
|
+
|
|
51
|
+
// Keep only the most recent logs
|
|
52
|
+
if (this.logs.length > this.maxLogs) {
|
|
53
|
+
this.logs = this.logs.slice(-this.maxLogs);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Output to stderr to avoid interfering with MCP protocol on stdout
|
|
57
|
+
const logMessage = this.formatLog(entry);
|
|
58
|
+
console.error(logMessage);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private static formatLog(entry: LogEntry): string {
|
|
62
|
+
const timestamp = entry.timestamp.split('T')[1].split('.')[0]; // HH:MM:SS format
|
|
63
|
+
let message = `[${timestamp}] ${entry.level.toUpperCase()}: ${entry.message}`;
|
|
64
|
+
|
|
65
|
+
if (entry.data !== undefined) {
|
|
66
|
+
message += ` ${JSON.stringify(entry.data)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return message;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static error(message: string, data?: any): void {
|
|
73
|
+
if (this.shouldLog(LogLevel.ERROR)) {
|
|
74
|
+
this.addLog('error', message, data);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static warn(message: string, data?: any): void {
|
|
79
|
+
if (this.shouldLog(LogLevel.WARN)) {
|
|
80
|
+
this.addLog('warn', message, data);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static info(message: string, data?: any): void {
|
|
85
|
+
if (this.shouldLog(LogLevel.INFO)) {
|
|
86
|
+
this.addLog('info', message, data);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static debug(message: string, data?: any): void {
|
|
91
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
92
|
+
this.addLog('debug', message, data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static trace(message: string, data?: any): void {
|
|
97
|
+
if (this.shouldLog(LogLevel.TRACE)) {
|
|
98
|
+
this.addLog('trace', message, data);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static getLogs(limit?: number): LogEntry[] {
|
|
103
|
+
if (limit && limit > 0) {
|
|
104
|
+
return this.logs.slice(-limit);
|
|
105
|
+
}
|
|
106
|
+
return [...this.logs];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static clearLogs(): void {
|
|
110
|
+
this.logs = [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Performance timing utilities
|
|
114
|
+
static time(label: string): void {
|
|
115
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
116
|
+
console.time(`[TIMER] ${label}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static timeEnd(label: string): void {
|
|
121
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
122
|
+
console.timeEnd(`[TIMER] ${label}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Safe logging that won't throw errors
|
|
127
|
+
static safeLog(level: 'error' | 'warn' | 'info' | 'debug' | 'trace', message: string, data?: any): void {
|
|
128
|
+
try {
|
|
129
|
+
switch (level) {
|
|
130
|
+
case 'error':
|
|
131
|
+
this.error(message, data);
|
|
132
|
+
break;
|
|
133
|
+
case 'warn':
|
|
134
|
+
this.warn(message, data);
|
|
135
|
+
break;
|
|
136
|
+
case 'info':
|
|
137
|
+
this.info(message, data);
|
|
138
|
+
break;
|
|
139
|
+
case 'debug':
|
|
140
|
+
this.debug(message, data);
|
|
141
|
+
break;
|
|
142
|
+
case 'trace':
|
|
143
|
+
this.trace(message, data);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Fallback to console.error if our logging fails
|
|
148
|
+
console.error(`Logging failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
149
|
+
console.error(`Original message: ${message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Initialize from environment variable
|
|
155
|
+
const envLogLevel = process.env.LOG_LEVEL?.toLowerCase();
|
|
156
|
+
if (envLogLevel && ['error', 'warn', 'info', 'debug', 'trace'].includes(envLogLevel)) {
|
|
157
|
+
Logger.setLogLevel(envLogLevel as any);
|
|
158
|
+
}
|