Skip to main content
Glama
index.ts12.2 kB
import * as pino from 'pino'; import * as fs from 'fs'; import * as path from 'path'; import { FileManager, FileType } from '../utils/file-manager.js'; import { ILogger } from './types.js'; import { PinoLogger } from './pino-logger.js'; import { SentryLogger } from './sentry-logger.js'; /** * Available log levels in ascending order of importance. * - trace: Extremely detailed logs * - debug: Detailed information for debugging * - info: General application flow information * - warn: Potentially harmful situations * - error: Error events that might still allow the application to continue * - fatal: Very severe error events that will likely lead to application termination */ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; /** * Supported cloud provider types for logging */ export enum CloudProvider { NONE = 'none', GCP = 'gcp', AWS = 'aws', AZURE = 'azure' } /** * Configuration for Google Cloud Logging */ export interface GCPLoggerConfig { /** * Google Cloud Project ID */ projectId: string; /** * Log name in GCP */ logName?: string; /** * Resource type and labels */ resource?: { type: string; labels: Record<string, string>; }; /** * Service context for error reporting */ serviceContext?: { service: string; version: string; }; /** * Additional labels to add to all log entries */ labels?: Record<string, string>; /** * Use synchronous logging (useful for serverless environments) * @default false */ synchronous?: boolean; } /** * Configuration for AWS CloudWatch Logging */ export interface AWSLoggerConfig { /** * CloudWatch log group name */ logGroupName: string; /** * CloudWatch log stream name */ logStreamName?: string; /** * AWS region */ region: string; /** * AWS credentials */ credentials?: { accessKeyId: string; secretAccessKey: string; }; } /** * Configuration for Azure Monitor Logging */ export interface AzureLoggerConfig { /** * Azure Application Insights connection string or instrumentation key */ connectionString: string; /** * Role name for the service */ role?: string; /** * Role instance name */ roleInstance?: string; } /** * Cloud provider configuration options */ export type CloudLoggerConfig = { provider: CloudProvider.GCP; config: GCPLoggerConfig; } | { provider: CloudProvider.AWS; config: AWSLoggerConfig; } | { provider: CloudProvider.AZURE; config: AzureLoggerConfig; } | { provider: CloudProvider.NONE; }; /** * Logger configuration options */ export interface LoggerOptions { /** * Log level to use * @default 'info' */ level?: LogLevel; /** * Enable pretty printing for development * @default true */ pretty?: boolean; /** * Additional configuration to pass to pino */ pinoOptions?: pino.LoggerOptions; /** * Optional output file path to write logs to */ outputFile?: string; /** * Cloud provider configuration for sending logs to cloud services */ cloud?: CloudLoggerConfig; /** * Include additional standard fields with each log */ standardFields?: { /** * Application or service name */ application?: string; /** * Environment (e.g., production, development, staging) */ environment?: string; /** * Version of the application */ version?: string; /** * Agent ID for multi-agent setups */ agentId?: string; /** * Custom fields to include with every log */ custom?: Record<string, any>; }; } /** * Global logger configuration that can be modified */ export const LoggerConfig = { /** * Default log level for all loggers */ defaultLevel: (process.env.LOG_LEVEL as LogLevel) || 'info', /** * Enable pretty printing by default */ prettyPrint: true, /** * Enable file output by default */ enableFileOutput: true, /** * Default log file location */ defaultLogFile: 'wallet-app.log', /** * Cloud provider configuration */ cloud: { provider: CloudProvider.NONE, } as CloudLoggerConfig, /** * Standard fields to include with all logs */ standardFields: { application: 'midnight-mcp', environment: process.env.NODE_ENV || 'development', version: process.env.APP_VERSION || '0.0.1', }, }; /** * Ensure the directory for a log file exists * @param filePath Path to the log file */ function ensureLogDirectoryExists(filePath: string): void { const fileManager = FileManager.getInstance(); const dirPath = path.dirname(filePath); fileManager.ensureDirectoryExists(dirPath); } /** * Create GCP-compatible log formatter */ export function createGCPFormatter(config: GCPLoggerConfig): pino.LoggerOptions { return { base: { serviceContext: config.serviceContext || { service: LoggerConfig.standardFields.application, version: LoggerConfig.standardFields.version, }, resource: config.resource || { type: 'global', labels: {}, }, labels: config.labels || {}, }, messageKey: 'message', formatters: { level(label, number) { // Map pino levels to GCP severity const severityMap: Record<string, string> = { trace: 'DEBUG', debug: 'DEBUG', info: 'INFO', warn: 'WARNING', error: 'ERROR', fatal: 'CRITICAL', }; return { severity: severityMap[label] || 'DEFAULT', level: number, }; }, log(object) { // Add timestamp in RFC3339 format const timestamp = new Date().toISOString(); return { ...object, timestamp }; }, }, }; } /** * Create a compatible transport for the given cloud provider */ function createCloudTransport(cloudConfig: CloudLoggerConfig): pino.DestinationStream | null { switch (cloudConfig.provider) { case CloudProvider.GCP: // For GCP we'll use pino-stackdriver return pino.transport({ target: 'pino-stackdriver', options: { projectId: cloudConfig.config.projectId, logName: cloudConfig.config.logName || 'midnight-mcp', // Spread the config but exclude projectId to avoid duplication ...(({ projectId, ...rest }) => rest)(cloudConfig.config), }, }); case CloudProvider.AWS: // For AWS we'd use pino-cloudwatch return pino.transport({ target: 'pino-cloudwatch', options: { group: cloudConfig.config.logGroupName, stream: cloudConfig.config.logStreamName || `${new Date().toISOString().split('T')[0]}-${LoggerConfig.standardFields.environment}`, aws: { region: cloudConfig.config.region, credentials: cloudConfig.config.credentials, }, }, }); case CloudProvider.AZURE: // For Azure we'd use pino-applicationinsights return pino.transport({ target: 'pino-applicationinsights', options: { connectionString: cloudConfig.config.connectionString, role: cloudConfig.config.role || LoggerConfig.standardFields.application, roleInstance: cloudConfig.config.roleInstance || LoggerConfig.standardFields.environment, }, }); case CloudProvider.NONE: default: return null; } } /** * Create a logger instance with the provided configuration * @param name Name of the logger (typically a component or module name) * @param options Configuration options * @returns Configured logger instance */ export function createLogger(name: string, options: LoggerOptions = {}): pino.Logger { const level = options.level || LoggerConfig.defaultLevel; const pretty = options.pretty ?? LoggerConfig.prettyPrint; const cloud = options.cloud || LoggerConfig.cloud; // Prepare standard fields const standardFields = { ...LoggerConfig.standardFields, ...options.standardFields, }; // Get agent ID from environment or standard fields const agentId = process.env.AGENT_ID || standardFields.agentId || 'default'; // Base logger options - ensure we don't override the level with custom levels let baseOptions: pino.LoggerOptions = { level, name, base: { application: standardFields.application, environment: standardFields.environment, version: standardFields.version, ...standardFields.custom, }, }; // Only merge pinoOptions if they don't contain custom levels that would conflict if (options.pinoOptions) { const { customLevels, ...safePinoOptions } = options.pinoOptions; baseOptions = { ...baseOptions, ...safePinoOptions, }; // If custom levels are provided, ensure the default level is included if (customLevels) { // Get the numeric value for the current level /* istanbul ignore next */ const levelValue = pino.levels.values[level] || 30; // Default to info level value baseOptions.customLevels = { ...customLevels, [level]: levelValue, }; } } // Apply cloud-specific formatters if needed if (cloud.provider === CloudProvider.GCP) { baseOptions = { ...baseOptions, ...createGCPFormatter(cloud.config), }; } // Configure destination streams const destinations: pino.DestinationStream[] = []; // Add pretty console transport if enabled if (pretty) { destinations.push( pino.transport({ target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname', } }) ); } else { // Standard output without pretty printing destinations.push(pino.destination(1)); // stdout } // Add file transport if enabled and file path provided const outputFile = options.outputFile || (LoggerConfig.enableFileOutput ? LoggerConfig.defaultLogFile.replace('.log', `-${agentId}.log`) : undefined); if (outputFile) { const fileManager = FileManager.getInstance(); const logPath = fileManager.getPath(FileType.LOG, agentId, outputFile); ensureLogDirectoryExists(logPath); destinations.push(pino.destination(logPath)); } // Add cloud transport if configured const cloudTransport = createCloudTransport(cloud); if (cloudTransport) { destinations.push(cloudTransport); } // If multiple destinations, use multistream if (destinations.length > 1) { return pino.pino(baseOptions, pino.multistream(destinations)); } // Single destination return pino.pino(baseOptions, destinations[0]); } /** * Configure global logger settings * @param options Configuration options to apply globally */ export function configureGlobalLogging(options: { level?: LogLevel; prettyPrint?: boolean; enableFileOutput?: boolean; defaultLogFile?: string; cloud?: CloudLoggerConfig; standardFields?: typeof LoggerConfig.standardFields; }): void { if (options.level) { LoggerConfig.defaultLevel = options.level; } if (options.prettyPrint !== undefined) { LoggerConfig.prettyPrint = options.prettyPrint; } if (options.enableFileOutput !== undefined) { LoggerConfig.enableFileOutput = options.enableFileOutput; } if (options.defaultLogFile) { LoggerConfig.defaultLogFile = options.defaultLogFile; } if (options.cloud) { LoggerConfig.cloud = options.cloud; } if (options.standardFields) { LoggerConfig.standardFields = { ...LoggerConfig.standardFields, ...options.standardFields, }; } } export { pino }; export default createLogger; let logger: ILogger; export function configureLogger(type: 'pino' | 'sentry', options?: any) { if (type === 'sentry') { logger = new SentryLogger(/* pass options if needed */); } else { logger = new PinoLogger(/* pass options if needed */); } } export function getLogger(): ILogger { if (!logger) logger = new PinoLogger(); return logger; }

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/evilpixi/pixi-midnight-mcp'

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