feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
310
reactrebuild0825/functions/lib/logger.js
Normal file
310
reactrebuild0825/functions/lib/logger.js
Normal file
@@ -0,0 +1,310 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Structured Logger Utility for Firebase Cloud Functions
|
||||
*
|
||||
* Provides consistent structured logging with proper data masking
|
||||
* and performance tracking for scanner operations.
|
||||
*/
|
||||
const __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) {k2 = k;}
|
||||
let desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) {k2 = k;}
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
const __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
const __importStar = (this && this.__importStar) || (function () {
|
||||
let ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
const ar = [];
|
||||
for (const 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;}
|
||||
const result = {};
|
||||
if (mod != null) {for (let 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
|
||||
Reference in New Issue
Block a user