feat: implement debug logging functionality and UI for message processing
This commit is contained in:
273
app/src/main/java/com/rosetta/messenger/utils/MessageLogger.kt
Normal file
273
app/src/main/java/com/rosetta/messenger/utils/MessageLogger.kt
Normal file
@@ -0,0 +1,273 @@
|
||||
package com.rosetta.messenger.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
|
||||
/**
|
||||
* Утилита для логирования сообщений
|
||||
* Безопасна для release сборки - логи не выводятся в production
|
||||
*
|
||||
* Логи отображаются:
|
||||
* 1. В Logcat (всегда в debug)
|
||||
* 2. В Debug Logs внутри чата (через ProtocolManager.debugLogs)
|
||||
*/
|
||||
object MessageLogger {
|
||||
private const val TAG = "RosettaMsg"
|
||||
|
||||
// Включить/выключить логирование (только в DEBUG)
|
||||
private val isEnabled: Boolean = android.os.Build.TYPE != "user"
|
||||
|
||||
/**
|
||||
* Добавить лог в UI (Debug Logs в чате)
|
||||
*/
|
||||
private fun addToUI(message: String) {
|
||||
ProtocolManager.addLog(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование отправки сообщения
|
||||
*/
|
||||
fun logSendStart(
|
||||
messageId: String,
|
||||
toPublicKey: String,
|
||||
textLength: Int,
|
||||
attachmentsCount: Int,
|
||||
isSavedMessages: Boolean,
|
||||
replyToMessageId: String?
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val shortKey = toPublicKey.take(12)
|
||||
val msg = "📤 SEND | id:\$shortId to:\$shortKey len:\$textLength att:\$attachmentsCount saved:\$isSavedMessages reply:\${replyToMessageId?.take(8) ?: \"-\"}"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование успешного шифрования
|
||||
*/
|
||||
fun logEncryptionSuccess(
|
||||
messageId: String,
|
||||
encryptedContentLength: Int,
|
||||
encryptedKeyLength: Int
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "🔐 ENCRYPT | id:\$shortId content:\${encryptedContentLength}b key:\${encryptedKeyLength}b"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование сохранения в БД
|
||||
*/
|
||||
fun logDbSave(
|
||||
messageId: String,
|
||||
dialogKey: String,
|
||||
isNew: Boolean
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val shortDialog = dialogKey.take(12)
|
||||
val msg = "💾 DB | id:\$shortId dialog:\$shortDialog new:\$isNew"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование отправки пакета на сервер
|
||||
*/
|
||||
fun logPacketSend(
|
||||
messageId: String,
|
||||
toPublicKey: String,
|
||||
timestamp: Long
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val shortKey = toPublicKey.take(12)
|
||||
val msg = "📡 PACKET→ | id:\$shortId to:\$shortKey ts:\$timestamp"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование успешной отправки
|
||||
*/
|
||||
fun logSendSuccess(messageId: String, duration: Long) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "✅ SENT | id:\$shortId time:\${duration}ms"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование ошибки отправки
|
||||
*/
|
||||
fun logSendError(messageId: String, error: Throwable) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "❌ SEND ERR | id:\$shortId err:\${error.message?.take(50)}"
|
||||
Log.e(TAG, msg, error)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование входящего сообщения
|
||||
*/
|
||||
fun logReceiveStart(
|
||||
messageId: String,
|
||||
fromPublicKey: String,
|
||||
toPublicKey: String,
|
||||
contentLength: Int,
|
||||
attachmentsCount: Int,
|
||||
timestamp: Long
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val shortFrom = fromPublicKey.take(12)
|
||||
val shortTo = toPublicKey.take(12)
|
||||
val msg = "📥 RECV | id:\$shortId from:\$shortFrom to:\$shortTo len:\${contentLength}b att:\$attachmentsCount ts:\$timestamp"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование проверки на дубликат
|
||||
*/
|
||||
fun logDuplicateCheck(messageId: String, isDuplicate: Boolean) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val status = if (isDuplicate) "⚠️DUP" else "✓NEW"
|
||||
val msg = "🔍 CHECK | id:\$shortId \$status"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование блокировки отправителя
|
||||
*/
|
||||
fun logBlockedSender(fromPublicKey: String) {
|
||||
if (!isEnabled) return
|
||||
val shortKey = fromPublicKey.take(12)
|
||||
val msg = "🚫 BLOCKED | from:\$shortKey"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование успешной расшифровки
|
||||
*/
|
||||
fun logDecryptionSuccess(
|
||||
messageId: String,
|
||||
plainTextLength: Int,
|
||||
attachmentsCount: Int
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "🔓 DECRYPT | id:\$shortId text:\${plainTextLength}c att:\$attachmentsCount"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование ошибки расшифровки
|
||||
*/
|
||||
fun logDecryptionError(messageId: String, error: Throwable) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "❌ DECRYPT ERR | id:\$shortId err:\${error.message?.take(50)}"
|
||||
Log.e(TAG, msg, error)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование успешного получения
|
||||
*/
|
||||
fun logReceiveSuccess(messageId: String, duration: Long) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val msg = "✅ RECEIVED | id:\$shortId time:\${duration}ms"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование обновления статуса доставки
|
||||
*/
|
||||
fun logDeliveryStatus(
|
||||
messageId: String,
|
||||
toPublicKey: String,
|
||||
status: String
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortId = messageId.take(8)
|
||||
val shortKey = toPublicKey.take(12)
|
||||
val msg = "📬 DELIVERY | id:\$shortId to:\$shortKey status:\$status"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование прочтения сообщений
|
||||
*/
|
||||
fun logReadStatus(
|
||||
fromPublicKey: String,
|
||||
messagesCount: Int
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortKey = fromPublicKey.take(12)
|
||||
val msg = "👁 READ | from:\$shortKey count:\$messagesCount"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование обновления диалога
|
||||
*/
|
||||
fun logDialogUpdate(
|
||||
dialogKey: String,
|
||||
lastMessage: String?,
|
||||
unreadCount: Int
|
||||
) {
|
||||
if (!isEnabled) return
|
||||
val shortDialog = dialogKey.take(12)
|
||||
val shortMsg = lastMessage?.take(20) ?: "-"
|
||||
val msg = "📋 DIALOG | key:\$shortDialog last:\"\$shortMsg\" unread:\$unreadCount"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование обновления кэша
|
||||
*/
|
||||
fun logCacheUpdate(dialogKey: String, totalMessages: Int) {
|
||||
if (!isEnabled) return
|
||||
val shortDialog = dialogKey.take(12)
|
||||
val msg = "🗃 CACHE | dialog:\$shortDialog total:\$totalMessages"
|
||||
Log.d(TAG, msg)
|
||||
addToUI(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Общий debug лог
|
||||
*/
|
||||
fun debug(message: String) {
|
||||
if (!isEnabled) return
|
||||
Log.d(TAG, message)
|
||||
addToUI(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Общий error лог
|
||||
*/
|
||||
fun error(message: String, error: Throwable? = null) {
|
||||
if (!isEnabled) return
|
||||
if (error != null) {
|
||||
Log.e(TAG, message, error)
|
||||
} else {
|
||||
Log.e(TAG, message)
|
||||
}
|
||||
addToUI("❌ \$message")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.rosetta.messenger.utils
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Глобальный менеджер throttle для отправки сообщений
|
||||
* Предотвращает дублирование при быстром нажатии на кнопку отправки
|
||||
*
|
||||
* 🔥 КРИТИЧНО: Singleton обеспечивает app-wide throttle,
|
||||
* в отличие от per-ViewModel throttle который обходится при навигации между чатами
|
||||
*/
|
||||
object MessageThrottleManager {
|
||||
|
||||
// Время последней отправки для каждого диалога
|
||||
private val sendTimes = ConcurrentHashMap<String, Long>()
|
||||
|
||||
// Минимальный интервал между отправками в одном диалоге (мс)
|
||||
private const val THROTTLE_MS = 100L
|
||||
|
||||
// Дополнительный throttle для повторной отправки одинакового контента
|
||||
private val lastContentHashes = ConcurrentHashMap<String, Int>()
|
||||
private const val CONTENT_THROTTLE_MS = 500L
|
||||
|
||||
/**
|
||||
* Проверяет, можно ли отправить сообщение в указанный диалог
|
||||
* @param dialogKey уникальный ключ диалога
|
||||
* @return true если отправка разрешена, false если нужно подождать
|
||||
*/
|
||||
fun canSend(dialogKey: String): Boolean {
|
||||
val now = System.currentTimeMillis()
|
||||
val lastTime = sendTimes[dialogKey] ?: 0L
|
||||
|
||||
if (now - lastTime < THROTTLE_MS) {
|
||||
MessageLogger.debug("⏱️ THROTTLE: Message blocked for $dialogKey (${now - lastTime}ms < ${THROTTLE_MS}ms)")
|
||||
return false
|
||||
}
|
||||
|
||||
sendTimes[dialogKey] = now
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет throttle с учётом содержимого сообщения
|
||||
* Предотвращает отправку идентичных сообщений подряд
|
||||
*
|
||||
* @param dialogKey уникальный ключ диалога
|
||||
* @param contentHash хэш содержимого сообщения (text.hashCode())
|
||||
* @return true если отправка разрешена
|
||||
*/
|
||||
fun canSendWithContent(dialogKey: String, contentHash: Int): Boolean {
|
||||
if (!canSend(dialogKey)) return false
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val key = "$dialogKey:$contentHash"
|
||||
val lastTime = sendTimes[key] ?: 0L
|
||||
|
||||
// Блокируем отправку идентичного контента в течение CONTENT_THROTTLE_MS
|
||||
if (now - lastTime < CONTENT_THROTTLE_MS) {
|
||||
MessageLogger.debug("⏱️ CONTENT THROTTLE: Duplicate content blocked for $dialogKey")
|
||||
return false
|
||||
}
|
||||
|
||||
sendTimes[key] = now
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка всех throttle данных (вызывается при logout)
|
||||
*/
|
||||
fun clear() {
|
||||
sendTimes.clear()
|
||||
lastContentHashes.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user