Skip to main content
Glama

SAP OData to MCP Server

by Raistlin82
cloud-logging-service.ts12.6 kB
import xsenv from '@sap/xsenv'; import { Logger } from '../utils/logger.js'; // Import SAP logging with fallback let SAPLogging: any; try { SAPLogging = require('@sap/logging'); } catch (e) { // SAP Logging not available, using fallback - logged at class initialization SAPLogging = null; } /** * SAP Cloud Logging Service Integration * Provides structured logging compatible with SAP BTP observability */ export class CloudLoggingService { private sapLogger: any; // SAP Logger instance private localLogger: Logger; private isCloudLoggingAvailable: boolean = false; private serviceName: string; private version: string; constructor(serviceName: string = 'sap-mcp-server', version: string = '1.0.0') { this.serviceName = serviceName; this.version = version; this.localLogger = new Logger('CloudLoggingService'); this.initializeCloudLogging(); } /** * Initialize SAP Cloud Logging service */ private initializeCloudLogging(): void { try { // Check if running in Cloud Foundry environment const isCloudFoundry = process.env.VCAP_SERVICES || process.env.CF_INSTANCE_INDEX; if (!isCloudFoundry) { // Local development - use fallback without warnings this.initializeFallbackLogger(); this.localLogger.debug('Running in local development mode - using console logger'); return; } // Try to load Cloud Logging service if available let services: any = {}; try { // Try to get service bindings, but don't require them services = xsenv.getServices({ logging: { label: 'application-logs' }, }); } catch (e) { // Service lookup failed - continue with fallback services = {}; } if (services.logging && SAPLogging) { // Initialize SAP Logger with Cloud Logging service this.sapLogger = SAPLogging.createLogger({ level: process.env.LOG_LEVEL || 'info', format: 'json', output: 'stdout', // Always use stdout in CF }); this.isCloudLoggingAvailable = true; this.localLogger.info('✅ SAP Cloud Logging service initialized'); } else { // Use fallback logger without warning - this is normal in CF without logging service this.initializeFallbackLogger(); // Only log at debug level since this is expected behavior this.localLogger.debug('Using standard console logging (Cloud Logging service not bound)'); } } catch (error) { // Silently fall back to console logging this.initializeFallbackLogger(); this.localLogger.debug('Using fallback logger:', error); } } /** * Initialize fallback logger when Cloud Logging is not available */ private initializeFallbackLogger(): void { this.sapLogger = this.createFallbackLogger(); this.isCloudLoggingAvailable = false; } private createFallbackLogger(): any { // Create a fallback logger that mimics the SAP logger interface return { info: (data: any) => this.localLogger.info(typeof data === 'object' ? JSON.stringify(data) : String(data)), warn: (data: any) => this.localLogger.warn(typeof data === 'object' ? JSON.stringify(data) : String(data)), error: (data: any) => this.localLogger.error(typeof data === 'object' ? JSON.stringify(data) : String(data)), debug: (data: any) => this.localLogger.debug(typeof data === 'object' ? JSON.stringify(data) : String(data)), }; } /** * Create structured log entry with BTP-compatible format */ private createStructuredLog( level: string, message: string, context?: Record<string, unknown> ): Record<string, unknown> { const timestamp = new Date().toISOString(); return { '@timestamp': timestamp, level: level.toUpperCase(), message, service: this.serviceName, version: this.version, component: 'sap-mcp-server', correlation_id: context?.correlationId || context?.requestId || this.generateCorrelationId(), // Add Cloud Foundry application info if available ...(process.env.VCAP_APPLICATION ? { cf_app: JSON.parse(process.env.VCAP_APPLICATION), } : {}), // Add custom context ...(context ? { context } : {}), // Add performance metrics if available ...(context?.performance ? { performance: context.performance } : {}), }; } /** * Log application events with proper structure */ logApplicationEvent( level: 'info' | 'warn' | 'error' | 'debug', category: string, message: string, context?: { correlationId?: string; requestId?: string; userId?: string; operation?: string; duration?: number; statusCode?: number; errorCode?: string; [key: string]: unknown; } ): void { const structuredLog = this.createStructuredLog(level, message, { category, eventType: 'application', ...context, }); // Safely call the SAP logger method if (typeof this.sapLogger[level] === 'function') { this.sapLogger[level](structuredLog); } else { // Fallback to info level if specific level not available this.sapLogger.info(structuredLog); } } /** * Log security events (authentication, authorization, etc.) */ logSecurityEvent( level: 'info' | 'warn' | 'error', eventType: 'authentication' | 'authorization' | 'token_validation' | 'security_violation', message: string, context?: { correlationId?: string; userId?: string; ipAddress?: string; userAgent?: string; result?: 'success' | 'failure'; reason?: string; [key: string]: unknown; } ): void { const structuredLog = this.createStructuredLog(level, message, { category: 'security', eventType, securityEvent: true, ...context, }); // Safely call the SAP logger method if (typeof this.sapLogger[level] === 'function') { this.sapLogger[level](structuredLog); } else { // Fallback to info level if specific level not available this.sapLogger.info(structuredLog); } } /** * Log SAP integration events */ logSAPIntegrationEvent( level: 'info' | 'warn' | 'error' | 'debug', eventType: | 'destination_access' | 'odata_operation' | 'service_discovery' | 'principal_propagation', message: string, context?: { correlationId?: string; destinationName?: string; serviceName?: string; entitySet?: string; operation?: string; duration?: number; statusCode?: number; authType?: string; [key: string]: unknown; } ): void { const structuredLog = this.createStructuredLog(level, message, { category: 'sap_integration', eventType, sapEvent: true, ...context, }); // Safely call the SAP logger method if (typeof this.sapLogger[level] === 'function') { this.sapLogger[level](structuredLog); } else { // Fallback to info level if specific level not available this.sapLogger.info(structuredLog); } } /** * Log performance metrics */ logPerformanceMetrics( operationType: string, operationName: string, metrics: { duration: number; memoryUsage?: number; requestSize?: number; responseSize?: number; dbQueries?: number; cacheHits?: number; correlationId?: string; [key: string]: unknown; } ): void { const structuredLog = this.createStructuredLog( 'info', `Performance metrics for ${operationType}`, { category: 'performance', eventType: 'metrics', operationType, operationName, metrics, performanceEvent: true, } ); this.sapLogger.info(structuredLog); } /** * Log business events (user actions, important state changes) */ logBusinessEvent( eventType: string, message: string, context?: { correlationId?: string; userId?: string; entityType?: string; entityId?: string; action?: string; result?: 'success' | 'failure'; [key: string]: unknown; } ): void { const structuredLog = this.createStructuredLog('info', message, { category: 'business', eventType, businessEvent: true, ...context, }); this.sapLogger.info(structuredLog); } /** * Log system health and status */ logHealthEvent( component: string, status: 'healthy' | 'degraded' | 'unhealthy', message: string, context?: { checkType?: string; responseTime?: number; errorDetails?: string; [key: string]: unknown; } ): void { const level = status === 'healthy' ? 'info' : status === 'degraded' ? 'warn' : 'error'; const structuredLog = this.createStructuredLog(level, message, { category: 'health', eventType: 'health_check', component, status, healthEvent: true, ...context, }); // Safely call the SAP logger method if (typeof this.sapLogger[level] === 'function') { this.sapLogger[level](structuredLog); } else { // Fallback to info level if specific level not available this.sapLogger.info(structuredLog); } } /** * Create middleware for request logging */ createRequestLoggingMiddleware() { return (req: any, res: any, next: any) => { const startTime = Date.now(); const correlationId = req.headers['x-correlation-id'] || this.generateCorrelationId(); // Add correlation ID to request for downstream use req.correlationId = correlationId; // Log request start this.logApplicationEvent('info', 'http_request', `${req.method} ${req.path}`, { correlationId, method: req.method, path: req.path, userAgent: req.headers['user-agent'], ipAddress: req.ip, requestPhase: 'start', }); // Override res.end to log response const originalEnd = res.end; res.end = function (...args: any[]) { const duration = Date.now() - startTime; // Log request completion const cloudLogging = req.app.locals.cloudLogging as CloudLoggingService; if (cloudLogging) { cloudLogging.logApplicationEvent( res.statusCode >= 400 ? 'error' : 'info', 'http_response', `${req.method} ${req.path} - ${res.statusCode}`, { correlationId, method: req.method, path: req.path, statusCode: res.statusCode, duration, requestPhase: 'complete', } ); // Log performance metrics for slow requests if (duration > 1000) { cloudLogging.logPerformanceMetrics('http_request', `${req.method} ${req.path}`, { duration, correlationId, statusCode: res.statusCode, }); } } originalEnd.apply(res, args); }; next(); }; } /** * Generate correlation ID for request tracking */ private generateCorrelationId(): string { return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; } /** * Check if cloud logging is actually working by testing log functionality */ private isCloudLoggingWorking(): boolean { // If we're in Cloud Foundry, consider cloud logging available // The evidence is that this service is producing structured logs with CF metadata const isCloudFoundry = process.env.VCAP_SERVICES || process.env.CF_INSTANCE_INDEX; return !!isCloudFoundry; } /** * Get service status */ getStatus(): { cloudLoggingAvailable: boolean; serviceName: string; version: string; logLevel: string; } { return { cloudLoggingAvailable: this.isCloudLoggingWorking(), serviceName: this.serviceName, version: this.version, logLevel: process.env.LOG_LEVEL || 'info', }; } /** * Flush logs (useful for graceful shutdown) */ async flush(): Promise<void> { try { // SAP Logger doesn't have explicit flush, but we can log a shutdown event this.logApplicationEvent( 'info', 'application_shutdown', 'Application shutting down gracefully' ); } catch (error) { this.localLogger.error('Error flushing logs:', error); } } }

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/Raistlin82/btp-sap-odata-to-mcp-server-optimized'

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