package com.complycube.reactnative.logging import android.util.Log import org.json.JSONArray import org.json.JSONObject enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR, NONE } enum class LogTag(val tag: String) { SETTINGS("CC/Settings"), LIFECYCLE("CC/Lifecycle"), PARSER("CC/Parser"), VALIDATION("CC/Validation"), EVENT("CC/Event"), NETWORK("CC/Network"), UI("CC/UI"), SDK("CC/SDK"), OTHER("CC/Other") } data class LogConfig( var enabled: Boolean = true, var minLevel: LogLevel = LogLevel.DEBUG, var redactPII: Boolean = true, var maxLineLength: Int = 3500 ) object Logger { @Volatile var config: LogConfig = LogConfig() fun configure(block: LogConfig.() -> Unit) { synchronized(this) { config.block() } } inline fun v(tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) = log(LogLevel.VERBOSE, tag, t, msg) inline fun d(tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) = log(LogLevel.DEBUG, tag, t, msg) inline fun i(tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) = log(LogLevel.INFO, tag, t, msg) inline fun w(tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) = log(LogLevel.WARN, tag, t, msg) inline fun e(tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) = log(LogLevel.ERROR, tag, t, msg) inline fun log(level: LogLevel, tag: LogTag, t: Throwable? = null, crossinline msg: () -> String) { val cfg = config if (!cfg.enabled || level.ordinal < cfg.minLevel.ordinal) return var text = runCatching { msg() }.getOrElse { "" } if (cfg.redactPII) text = Redactor.redactString(text) if (text.length <= cfg.maxLineLength) { print(level, tag.tag, text, t) } else { var start = 0 val len = text.length while (start < len) { val end = (start + cfg.maxLineLength).coerceAtMost(len) print(level, tag.tag, text.substring(start, end), t.takeIf { start == 0 }) start = end } } } // Safe stringification for structured data (Map, List, JSON, etc.) fun secure(any: Any?): String = Redactor.toSafeString(any) @PublishedApi internal fun print(level: LogLevel, tag: String, msg: String, t: Throwable?) { try { when (level) { LogLevel.VERBOSE -> if (t != null) Log.v(tag, msg, t) else Log.v(tag, msg) LogLevel.DEBUG -> if (t != null) Log.d(tag, msg, t) else Log.d(tag, msg) LogLevel.INFO -> if (t != null) Log.i(tag, msg, t) else Log.i(tag, msg) LogLevel.WARN -> if (t != null) Log.w(tag, msg, t) else Log.w(tag, msg) LogLevel.ERROR -> if (t != null) Log.e(tag, msg, t) else Log.e(tag, msg) LogLevel.NONE -> { /* no-op */ } } } catch (_: RuntimeException) { // Avoid crashing in JVM unit tests where android.util.Log is not mocked. } } } public object Redactor { private val SENSITIVE_KEYS = setOf( "clienttoken", "client_token", "token", "accesstoken", "authorization", "apikey", "api_key", "secret", "password", "pwd", "bearer", "ssn", "national_id", "nationalidentitynumber", "documentnumber", "livephotoid", "livevideoid", "documentids", "poaids" ) private val patterns: List String>> = listOf( Regex("(?i)bearer\\s+[A-Za-z0-9._\\-]+") to { _ -> "Bearer [REDACTED]" }, Regex("\\b[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\b") to { _ -> "[JWT]" }, Regex("\\b(?:live|test)_[A-Za-z0-9]{12,}\\b") to { _ -> "[API_KEY]" }, Regex("\\b[0-9a-fA-F]{24}\\b") to { _ -> "[ID]" }, Regex("([A-Z0-9._%+-])[A-Z0-9._%+-]*(@[A-Z0-9.-]+\\.[A-Z]{2,})", RegexOption.IGNORE_CASE) to { m -> m.groupValues[1] + "***" + m.groupValues[2] }, ) fun redactString(input: String): String { var out = input for ((rx, repl) in patterns) out = out.replace(rx, repl) return out } fun toSafeString(any: Any?): String = when (any) { null -> "null" is JSONObject -> sanitizeJsonObject(any).toString() is JSONArray -> sanitizeJsonArray(any).toString() is Map<*, *> -> JSONObject(sanitizeMap(any)).toString() is Iterable<*> -> JSONArray(sanitizeList(any)).toString() is String -> redactString(any) else -> redactString(any.toString()) } fun sanitizeMap(map: Map<*, *>): Map { val out = HashMap(map.size) map.forEach { (k, v) -> val key = k?.toString() ?: return@forEach out[key] = if (key.lowercase() in SENSITIVE_KEYS) "[REDACTED]" else sanitizeValue(v) } return out } fun sanitizeList(list: Iterable<*>): List = list.map { sanitizeValue(it) } fun sanitizeValue(v: Any?): Any? = when (v) { null -> null is JSONObject -> sanitizeJsonObject(v) is JSONArray -> sanitizeJsonArray(v) is Map<*, *> -> sanitizeMap(v) is Iterable<*> -> sanitizeList(v) is String -> redactString(v) else -> v } fun sanitizeJsonObject(obj: JSONObject): JSONObject { val copy = JSONObject() val iter = obj.keys() while (iter.hasNext()) { val key = iter.next() val value = obj.opt(key) copy.put(key, if (key.lowercase() in SENSITIVE_KEYS) "[REDACTED]" else sanitizeValue(value)) } return copy } fun sanitizeJsonArray(arr: JSONArray): JSONArray { val copy = JSONArray() for (i in 0 until arr.length()) { copy.put(sanitizeValue(arr.get(i))) } return copy } }