package com.xuqm.sdk.bugcollect object Fingerprint { // exceptionType = exception class name (e.g. "NullPointerException"), NOT the level string // Only the innermost "at" frame (throw site) is used for grouping. // Kotlin coroutine dispatcher frames (frame 2+) vary across runs of the same logical error, // which caused identical errors to create separate issues. fun compute(exceptionType: String, message: String, stack: String): String { val top1 = stack.lines().filter { it.trim().startsWith("at ") }.take(1).joinToString("|") val normalized = message.replace(Regex("\\b\\d{4,}\\b"), "N") return sha256("$exceptionType:$normalized:$top1") } private fun sha256(s: String): String = java.security.MessageDigest.getInstance("SHA-256") .digest(s.toByteArray()) .joinToString("") { "%02x".format(it) } }