"use strict"; /** * Structured Logger Utility for Firebase Cloud Functions * * Provides consistent structured logging with proper data masking * and performance tracking for scanner operations. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Sentry = exports.logger = void 0; exports.withLogging = withLogging; const firebase_functions_1 = require("firebase-functions"); const Sentry = __importStar(require("@sentry/node")); exports.Sentry = Sentry; // Initialize Sentry for Cloud Functions const initializeSentry = () => { // Only initialize if DSN is provided and not a mock const dsn = process.env.SENTRY_DSN; if (!dsn || dsn.includes('mock')) { console.info('Sentry: Skipping initialization (no DSN or mock DSN detected)'); return; } Sentry.init({ dsn, environment: process.env.NODE_ENV || 'production', tracesSampleRate: 0.1, integrations: [ // Add Node.js specific integrations Sentry.httpIntegration(), Sentry.expressIntegration(), ], beforeSend: (event, hint) => { // Filter out noisy errors if (event.exception?.values?.[0]?.type === 'TypeError' && event.exception?.values?.[0]?.value?.includes('fetch')) { return null; } return event; }, }); }; // Initialize Sentry when module loads initializeSentry(); /** * Mask sensitive data in QR codes, tokens, or other sensitive strings */ function maskSensitiveData(data) { if (!data || data.length <= 8) { return '***'; } // Show first 4 and last 4 characters, mask the middle const start = data.substring(0, 4); const end = data.substring(data.length - 4); const maskLength = Math.min(data.length - 8, 20); // Cap mask length const mask = '*'.repeat(maskLength); return `${start}${mask}${end}`; } /** * Format log context with sensitive data masking */ function formatLogContext(context) { const formatted = {}; // Copy non-sensitive fields directly const safeCopyFields = ['sessionId', 'accountId', 'orgId', 'eventId', 'ticketTypeId', 'deviceId', 'userId', 'operation']; for (const field of safeCopyFields) { if (context[field]) { formatted[field] = context[field]; } } // Mask sensitive fields if (context.qr) { formatted.qr_masked = maskSensitiveData(context.qr); } if (context.deviceId) { formatted.device_short = context.deviceId.split('_')[1]?.substring(0, 8) || 'unknown'; } formatted.timestamp = new Date().toISOString(); return formatted; } /** * Core structured logger class */ class StructuredLogger { /** * Log scanner verification result with full context */ logScannerVerify(data) { const logData = { ...formatLogContext(data), result: data.result, latencyMs: data.latencyMs, reason: data.reason, timestamp: data.timestamp || new Date().toISOString(), }; // Use different log levels based on result if (data.result === 'valid') { firebase_functions_1.logger.info('Scanner verification successful', logData); } else if (data.result === 'already_scanned') { firebase_functions_1.logger.warn('Scanner verification - already scanned', logData); } else { firebase_functions_1.logger.warn('Scanner verification failed', logData); } // Send to Sentry if it's an error or concerning result if (data.result === 'invalid' && data.reason !== 'ticket_not_found') { Sentry.withScope((scope) => { scope.setTag('feature', 'scanner'); scope.setTag('scanner.result', data.result); scope.setContext('scanner_verification', logData); Sentry.captureMessage(`Scanner verification failed: ${data.reason}`, 'warning'); }); } } /** * Log performance metrics for scanner operations */ logPerformance(data) { const logData = { operation: data.operation, duration_ms: data.duration, ...(data.context ? formatLogContext(data.context) : {}), metadata: data.metadata, timestamp: new Date().toISOString(), }; firebase_functions_1.logger.info('Performance metric', logData); // Send slow operations to Sentry if (data.duration > 5000) { // Operations slower than 5 seconds Sentry.withScope((scope) => { scope.setTag('feature', 'performance'); scope.setTag('performance.operation', data.operation); scope.setContext('performance_metric', logData); Sentry.captureMessage(`Slow operation: ${data.operation} took ${data.duration}ms`, 'warning'); }); } } /** * Log general information with context */ info(message, context, metadata) { const logData = { message, ...(context ? formatLogContext(context) : {}), ...metadata, timestamp: new Date().toISOString(), }; firebase_functions_1.logger.info(message, logData); } /** * Log warnings with context */ warn(message, context, metadata) { const logData = { message, ...(context ? formatLogContext(context) : {}), ...metadata, timestamp: new Date().toISOString(), }; firebase_functions_1.logger.warn(message, logData); // Send warnings to Sentry with context Sentry.withScope((scope) => { if (context?.operation) { scope.setTag('operation', context.operation); } scope.setContext('warning_context', logData); Sentry.captureMessage(message, 'warning'); }); } /** * Log errors with context and send to Sentry */ error(message, error, context, metadata) { const logData = { message, error_message: error?.message, error_stack: error?.stack, ...(context ? formatLogContext(context) : {}), ...metadata, timestamp: new Date().toISOString(), }; firebase_functions_1.logger.error(message, logData); // Send to Sentry with full context Sentry.withScope((scope) => { if (context?.operation) { scope.setTag('operation', context.operation); } if (context?.sessionId) { scope.setTag('scanner.session', context.sessionId); } scope.setContext('error_context', logData); if (error) { Sentry.captureException(error); } else { Sentry.captureMessage(message, 'error'); } }); } /** * Log debug information (only in development) */ debug(message, context, metadata) { if (process.env.NODE_ENV !== 'production') { const logData = { message, ...(context ? formatLogContext(context) : {}), ...metadata, timestamp: new Date().toISOString(), }; firebase_functions_1.logger.debug(message, logData); } } /** * Capture exception directly to Sentry with context */ captureException(error, context) { Sentry.withScope((scope) => { if (context) { scope.setContext('exception_context', formatLogContext(context)); if (context.operation) { scope.setTag('operation', context.operation); } if (context.sessionId) { scope.setTag('scanner.session', context.sessionId); } } Sentry.captureException(error); }); } /** * Start a performance transaction */ startTransaction(name, op) { return Sentry.startSpan({ name, op }, () => { }); } /** * Add breadcrumb for debugging */ addBreadcrumb(message, category = 'general', data) { Sentry.addBreadcrumb({ message, category, level: 'info', data: { timestamp: new Date().toISOString(), ...data, }, }); } } // Singleton logger instance exports.logger = new StructuredLogger(); /** * Middleware wrapper for Cloud Functions to automatically log performance */ function withLogging(operationName, fn, contextExtractor) { return async (...args) => { const startTime = performance.now(); const context = contextExtractor ? contextExtractor(...args) : undefined; exports.logger.addBreadcrumb(`Starting operation: ${operationName}`, 'function', context); try { const result = await fn(...args); const duration = performance.now() - startTime; exports.logger.logPerformance({ operation: operationName, duration, context, }); return result; } catch (error) { const duration = performance.now() - startTime; exports.logger.error(`Operation failed: ${operationName}`, error, context, { duration }); throw error; } }; } //# sourceMappingURL=logger.js.map