Skip to main content
Glama

NTFY MCP Server

logger.ts6.88 kB
import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import winston from "winston"; type LogLevel = "debug" | "info" | "warn" | "error"; export type ChildLogger = { debug: (message: string, context?: Record<string, unknown>) => void; info: (message: string, context?: Record<string, unknown>) => void; warn: (message: string, context?: Record<string, unknown>) => void; error: (message: string, context?: Record<string, unknown>) => void; }; // Handle ESM module dirname const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Resolve logs directory relative to project root (2 levels up from utils/) const projectRoot = path.resolve(__dirname, '..', '..'); const logsDir = path.join(projectRoot, 'logs'); class Logger { private static instance: Logger; private logger: winston.Logger; private constructor() { const logLevel = (process.env.LOG_LEVEL as LogLevel) || "info"; // Ensure logs directory exists if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } // Common format for all transports const commonFormat = winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), // The 'context' object passed to logger methods (e.g., logger.info(message, context)) // will be available here in the 'info' object passed to the printf function. // Winston automatically merges the metadata passed during logger creation // with the metadata passed at the call site if you use logger.child(). // However, since we are implementing a custom child logger wrapper, // we need to handle the merging manually. winston.format.printf(({ timestamp, level, message, context, stack }) => { // Ensure context is an object before stringifying const contextStr = (context && typeof context === 'object' && Object.keys(context).length > 0) ? `\n Context: ${JSON.stringify(context, null, 2)}` : ""; const stackStr = stack ? `\n Stack: ${stack}` : ""; return `[${timestamp}] ${level}: ${message}${contextStr}${stackStr}`; }) ); this.logger = winston.createLogger({ level: logLevel, // Use json format for structured logging internally, printf for files format: winston.format.json(), transports: [ // Combined log file for all levels new winston.transports.File({ filename: path.join(logsDir, 'combined.log'), format: commonFormat // Apply the custom format here }), // Separate log files for each level new winston.transports.File({ filename: path.join(logsDir, 'error.log'), level: 'error', format: commonFormat }), new winston.transports.File({ filename: path.join(logsDir, 'warn.log'), level: 'warn', format: commonFormat }), new winston.transports.File({ filename: path.join(logsDir, 'info.log'), level: 'info', format: commonFormat }), new winston.transports.File({ filename: path.join(logsDir, 'debug.log'), level: 'debug', format: commonFormat }) ] }); // Add console transport only if not in production if (process.env.NODE_ENV !== 'production') { this.logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple(), // Use simple format for console readability winston.format.printf(({ level, message, timestamp, context, stack }) => { const contextStr = (context && typeof context === 'object' && Object.keys(context).length > 0) ? ` ${JSON.stringify(context)}` : ""; const stackStr = stack ? `\n${stack}` : ""; // Simple console format return `${level}: ${message}${contextStr}${stackStr}`; }) ) })); } } public static getInstance(): Logger { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } // Base log methods now accept the context directly public debug(message: string, context?: Record<string, unknown>) { this.logger.debug(message, context); } public info(message: string, context?: Record<string, unknown>) { this.logger.info(message, context); } public warn(message: string, context?: Record<string, unknown>) { this.logger.warn(message, context); } public error(message: string, context?: Record<string, unknown>) { // Ensure error context includes stack if available const errorContext = context || {}; if (errorContext.error instanceof Error && !errorContext.stack) { errorContext.stack = errorContext.error.stack; } this.logger.error(message, errorContext); } /** * Creates a child logger that automatically includes the provided metadata * in the context object of every log message. * * @param metadata - Static metadata to include with every log from this child. * @returns A ChildLogger instance. */ public createChildLogger(metadata: { module: string; service?: string; serviceId?: string; componentName?: string; subscriberId?: string; component?: string; requestId?: string; subscriptionTime?: string; environment?: string; serverId?: string; [key: string]: any; // Allow additional properties }): ChildLogger { // Filter out undefined values from metadata once const staticMetadata = Object.fromEntries( Object.entries(metadata).filter(([_, v]) => v !== undefined) ); return { debug: (message: string, context?: Record<string, unknown>) => { // Merge static metadata with call-site context const mergedContext = { ...staticMetadata, ...context }; this.debug(message, mergedContext); }, info: (message: string, context?: Record<string, unknown>) => { const mergedContext = { ...staticMetadata, ...context }; this.info(message, mergedContext); }, warn: (message: string, context?: Record<string, unknown>) => { const mergedContext = { ...staticMetadata, ...context }; this.warn(message, mergedContext); }, error: (message: string, context?: Record<string, unknown>) => { const mergedContext = { ...staticMetadata, ...context }; // Ensure error context includes stack if available if (mergedContext.error instanceof Error && !mergedContext.stack) { mergedContext.stack = mergedContext.error.stack; } this.error(message, mergedContext); } }; } } export const logger = Logger.getInstance();

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/cyanheads/ntfy-mcp-server'

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