Skip to main content
Glama
liratanak

Tonle OpenProject MCP Server

by liratanak
logger.ts5.96 kB
/** * Logging utility for OpenProject MCP Server * Creates daily log files separated by caller/initiator * * IMPORTANT: Logs are stored in an absolute path: * ~/Tonle/logs/ * * This ensures logs are always written to the same location regardless * of the working directory when the MCP server is started. */ import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; // Get the absolute path to the project root (where this file's parent directory is) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROJECT_ROOT = path.resolve(__dirname, '..'); // Default logs directory - always use absolute path const DEFAULT_LOGS_DIR = path.join(PROJECT_ROOT, 'logs'); export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'; export interface LogEntry { timestamp: string; level: LogLevel; caller: string; category: string; message: string; data?: any; } export class Logger { private logsDir: string; private enableConsole: boolean; constructor(logsDir?: string, enableConsole: boolean = false) { // Use provided path or default to absolute project logs directory this.logsDir = logsDir || DEFAULT_LOGS_DIR; this.enableConsole = enableConsole; this.ensureLogsDirectory(); } /** * Get the logs directory path (useful for debugging/documentation) */ getLogsDir(): string { return this.logsDir; } private ensureLogsDirectory(): void { if (!fs.existsSync(this.logsDir)) { fs.mkdirSync(this.logsDir, { recursive: true }); } } private getLogFileName(caller: string): string { const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD const sanitizedCaller = caller.replace(/[^a-zA-Z0-9-_]/g, '_'); return path.join(this.logsDir, `${date}-${sanitizedCaller}.log`); } private formatLogEntry(entry: LogEntry): string { const dataStr = entry.data ? `\n Data: ${JSON.stringify(entry.data, null, 2)}` : ''; return `[${entry.timestamp}] [${entry.level}] [${entry.caller}] [${entry.category}] ${entry.message}${dataStr}\n`; } private writeLog(entry: LogEntry): void { const logFile = this.getLogFileName(entry.caller); const formattedEntry = this.formatLogEntry(entry); // Write to file fs.appendFileSync(logFile, formattedEntry); // Optionally write to console for debugging if (this.enableConsole) { console.error(formattedEntry.trim()); } } log(level: LogLevel, caller: string, category: string, message: string, data?: any): void { const entry: LogEntry = { timestamp: new Date().toISOString(), level, caller, category, message, data, }; this.writeLog(entry); } // Convenience methods info(caller: string, category: string, message: string, data?: any): void { this.log('INFO', caller, category, message, data); } warn(caller: string, category: string, message: string, data?: any): void { this.log('WARN', caller, category, message, data); } error(caller: string, category: string, message: string, data?: any): void { this.log('ERROR', caller, category, message, data); } debug(caller: string, category: string, message: string, data?: any): void { this.log('DEBUG', caller, category, message, data); } // Specialized logging methods for different event types logApiRequest(caller: string, method: string, endpoint: string, params?: any, body?: any): void { this.info(caller, 'API_REQUEST', `${method} ${endpoint}`, { method, endpoint, params, body, }); } logApiResponse(caller: string, method: string, endpoint: string, status: number, data?: any): void { this.info(caller, 'API_RESPONSE', `${method} ${endpoint} - Status: ${status}`, { method, endpoint, status, responseData: data, }); } logApiError(caller: string, method: string, endpoint: string, error: Error): void { this.error(caller, 'API_ERROR', `${method} ${endpoint} failed`, { method, endpoint, error: error.message, stack: error.stack, }); } logToolInvocation(caller: string, toolName: string, params: any): void { this.info(caller, 'TOOL_INVOCATION', `Tool: ${toolName}`, { toolName, params, }); } logToolResult(caller: string, toolName: string, success: boolean, result?: any, error?: Error): void { if (success) { this.info(caller, 'TOOL_RESULT', `Tool ${toolName} succeeded`, { toolName, result, }); } else { this.error(caller, 'TOOL_RESULT', `Tool ${toolName} failed`, { toolName, error: error?.message, stack: error?.stack, }); } } logServerEvent(caller: string, event: string, details?: any): void { this.info(caller, 'SERVER_EVENT', event, details); } logSessionEvent(caller: string, sessionId: string, event: string, details?: any): void { this.info(caller, 'SESSION_EVENT', `Session ${sessionId}: ${event}`, { sessionId, event, ...details, }); } logAuth(caller: string, success: boolean, userInfo?: any): void { if (success) { this.info(caller, 'AUTH', 'Authentication successful', userInfo); } else { this.warn(caller, 'AUTH', 'Authentication failed', userInfo); } } } // Create a singleton instance // Default to console logging enabled unless explicitly disabled const enableConsole = process.env.LOG_TO_CONSOLE !== 'false'; const logger = new Logger(undefined, enableConsole); // undefined uses DEFAULT_LOGS_DIR // Log where logs are being written (helps with debugging) // This runs at module load time try { const initMessage = `Logger initialized. Logs directory: ${logger.getLogsDir()}`; console.error(`[Tonle MCP] ${initMessage}`); } catch { // Silently ignore if console.error fails } export default logger; export { DEFAULT_LOGS_DIR, PROJECT_ROOT };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/liratanak/openproject-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server