import TelemetryException from "Common/Models/DatabaseModels/TelemetryException"; import TelemetryExceptionService from "Common/Server/Services/TelemetryExceptionService"; import OneUptimeDate from "Common/Types/Date"; import BadDataException from "Common/Types/Exception/BadDataException"; import ObjectID from "Common/Types/ObjectID"; import Crypto from "Common/Utils/Crypto"; export interface ExceptionFingerprintInput { message?: string; stackTrace?: string; exceptionType?: string; projectId?: ObjectID; serviceId?: ObjectID; } export interface TelemetryExceptionPayload { fingerprint: string; projectId: ObjectID; serviceId: ObjectID; exceptionType?: string; stackTrace?: string; message?: string; release?: string; // current release from the incoming event environment?: string; } export default class ExceptionUtil { /** * Normalizes a string by replacing dynamic values with placeholders. * This ensures that exceptions with the same root cause but different * dynamic values (like IDs, timestamps, etc.) get the same fingerprint. * * @param text - The text to normalize (message or stack trace) * @returns The normalized text with dynamic values replaced */ public static normalizeForFingerprint(text: string): string { if (!text) { return ""; } let normalized: string = text; // Order matters! More specific patterns should come before generic ones. // 1. UUIDs (e.g., 550e8400-e29b-41d4-a716-446655440000) normalized = normalized.replace( /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "", ); // 2. MongoDB ObjectIDs (24 hex characters) normalized = normalized.replace(/\b[0-9a-f]{24}\b/gi, ""); /* * 3. Stripe-style IDs (e.g., sub_xxx, cus_xxx, pi_xxx, ch_xxx, etc.) * These have a prefix followed by underscore and alphanumeric characters */ normalized = normalized.replace( /\b(sub|cus|pi|ch|pm|card|price|prod|inv|txn|evt|req|acct|payout|ba|btok|src|tok|seti|si|cs|link|file|dp|icr|ii|il|is|isci|mbur|or|po|qt|rcpt|re|refund|sku|tax|txi|tr|us|wh)_[A-Za-z0-9]{10,32}\b/g, "", ); /* * 4. Generic API/Service IDs - alphanumeric strings that look like IDs * Matches patterns like: prefix_alphanumeric or just long alphanumeric strings * Common in many services (AWS, GCP, etc.) */ normalized = normalized.replace( /\b[a-z]{2,10}_[A-Za-z0-9]{8,}\b/g, "", ); // 5. JWT tokens (three base64 segments separated by dots) normalized = normalized.replace( /\beyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\b/g, "", ); // 6. Base64 encoded strings (long sequences, likely tokens or encoded data) normalized = normalized.replace( /\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "", ); // 7. IP addresses (IPv4) normalized = normalized.replace( /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, "", ); // 8. IP addresses (IPv6) - simplified pattern normalized = normalized.replace( /\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b/g, "", ); normalized = normalized.replace(/\b::1\b/g, ""); // localhost IPv6 // 9. Email addresses normalized = normalized.replace( /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "", ); /* * 10. URLs with dynamic paths/query params (normalize the dynamic parts) * Keep the domain but normalize path segments that look like IDs */ normalized = normalized.replace( /\/[0-9a-f]{8,}(?=\/|$|\?|#|\s|'|")/gi, "/", ); /* * 11. Timestamps in various formats * ISO 8601 timestamps */ normalized = normalized.replace( /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?/g, "", ); // Unix timestamps (10 or 13 digits) normalized = normalized.replace(/\b1[0-9]{9,12}\b/g, ""); // 12. Date formats (YYYY-MM-DD, MM/DD/YYYY, etc.) normalized = normalized.replace(/\b\d{4}[-/]\d{2}[-/]\d{2}\b/g, ""); normalized = normalized.replace(/\b\d{2}[-/]\d{2}[-/]\d{4}\b/g, ""); // 13. Time formats (HH:MM:SS, HH:MM) normalized = normalized.replace(/\b\d{2}:\d{2}(?::\d{2})?\b/g, "