package logger import ( "log/slog" "regexp" "strings" ) var sensitiveClientLogKeys = map[string]struct{}{ "password": {}, "currentpassword": {}, "newpassword": {}, "oldpassword": {}, "token": {}, "accesstoken": {}, "refreshtoken": {}, "secret": {}, "clientsecret": {}, "authorization": {}, "cookie": {}, "setcookie": {}, "verificationcode": {}, "code": {}, "loginchallenge": {}, "loginverifier": {}, "sessionjwt": {}, "accessjwt": {}, "refreshjwt": {}, } var ( logJSONSensitivePattern = regexp.MustCompile(`(?i)"(password|currentpassword|newpassword|oldpassword|token|accesstoken|refreshtoken|secret|clientsecret|authorization|cookie|setcookie|verificationcode|code|loginchallenge|loginverifier|sessionjwt|accessjwt|refreshjwt)"\s*:\s*"[^"]*"`) logKVPattern = regexp.MustCompile(`(?i)\b(password|current_password|currentpassword|new_password|newpassword|old_password|oldpassword|token|access_token|accesstoken|refresh_token|refreshtoken|authorization|cookie|session_jwt|sessionjwt|access_jwt|accessjwt|refresh_jwt|refreshjwt)\b\s*[:=]\s*([^\s,;]+)`) ) func IsProductionEnv(appEnv string) bool { return IsProductionLikeEnv(appEnv) } func parseBoolFlag(raw string) bool { switch strings.ToLower(strings.TrimSpace(raw)) { case "1", "true", "yes", "y", "on": return true default: return false } } func ClientDebugEnabled(appEnv, productionDebugFlag string) bool { if !IsProductionEnv(appEnv) { return true } return parseBoolFlag(productionDebugFlag) } func NormalizeClientLogLevel(level string) slog.Level { switch strings.ToUpper(strings.TrimSpace(level)) { case "SEVERE", "ERROR": return slog.LevelError case "WARNING", "WARN": return slog.LevelWarn case "DEBUG", "FINE", "TRACE": return slog.LevelDebug default: return slog.LevelInfo } } func ShouldAcceptClientLog(appEnv, productionDebugFlag, level string) bool { if ClientDebugEnabled(appEnv, productionDebugFlag) { return true } return NormalizeClientLogLevel(level) >= slog.LevelWarn } func ShouldFilterNoisyClientInfo(appEnv, productionDebugFlag, message string) bool { if ClientDebugEnabled(appEnv, productionDebugFlag) { return false } msg := strings.ToLower(message) return strings.Contains(msg, "navigating to") || strings.Contains(msg, "going to") || strings.Contains(msg, "redirecting to") || strings.Contains(msg, "full paths for routes") } func SanitizeClientLogMessage(message string) string { if strings.TrimSpace(message) == "" { return message } sanitized := logJSONSensitivePattern.ReplaceAllStringFunc(message, func(segment string) string { parts := strings.SplitN(segment, ":", 2) if len(parts) != 2 { return segment } return parts[0] + `:"*****"` }) sanitized = logKVPattern.ReplaceAllString(sanitized, `$1=*****`) return sanitized } func SanitizeClientLogData(data map[string]interface{}) map[string]interface{} { if len(data) == 0 { return data } out := make(map[string]interface{}, len(data)) for k, v := range data { if isSensitiveClientLogKey(k) { out[k] = "*****" continue } out[k] = sanitizeClientLogValue(v) } return out } func sanitizeClientLogValue(v interface{}) interface{} { switch val := v.(type) { case map[string]interface{}: return SanitizeClientLogData(val) case []interface{}: next := make([]interface{}, len(val)) for i := range val { next[i] = sanitizeClientLogValue(val[i]) } return next case string: return SanitizeClientLogMessage(val) default: return val } } func isSensitiveClientLogKey(key string) bool { normalized := strings.ToLower(strings.TrimSpace(key)) normalized = strings.ReplaceAll(normalized, "-", "") normalized = strings.ReplaceAll(normalized, "_", "") normalized = strings.ReplaceAll(normalized, ".", "") normalized = strings.ReplaceAll(normalized, " ", "") _, ok := sensitiveClientLogKeys[normalized] return ok }