Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-30 15:29:56 +04:00
parent 91c2fb2d58
commit d718cf557d
18 changed files with 1135 additions and 280 deletions

View file

@ -164,7 +164,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
appScope.launch {
launch(Dispatchers.IO) {
loadNativeLibraries()
updateLogFiles()
}
}
@ -196,22 +195,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
)
}
private fun updateLogFiles() {
appLogsStore.deleteOldLogsFile()
if (!BuildConfig.TESTER_MENU_ENABLED) {
appLogsStore.deleteLastLogFile()
}
// Temporarily logs are not saved
// scope.launch {
// if (!appPreferencesStore.getSyncOrDefault(WAS_LOG_FILE_CLEARED, false)) {
// appLogsStore.deleteLastLogFile()
// appPreferencesStore.store(WAS_LOG_FILE_CLEARED, true)
// }
// }
}
override fun newImageLoader(): ImageLoader {
return createCoilImageLoader(
context = this,

View file

@ -0,0 +1,36 @@
package com.tangem.tap.common.log
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.utils.logging.Severity
import com.tangem.utils.logging.TangemLogger
/**
* [TangemLogger.LogWriter] that persists log entries to [AppLogsStore].
*
* Only [Severity.Error] and [Severity.Info] are written. The `shouldSanitize` flag is forwarded to
* [AppLogsStore.saveLogMessage], so callers that deliberately log unsanitized content
* (`shouldSanitize = false`) bypass the sanitizer.
*/
internal class FileLogWriter(
private val appLogsStore: AppLogsStore,
) : TangemLogger.LogWriter {
override fun isLoggable(severity: Severity, tag: String): Boolean {
return severity == Severity.Error || severity == Severity.Info
}
override fun write(
severity: Severity,
tag: String,
message: String,
throwable: Throwable?,
shouldSanitize: Boolean,
) {
appLogsStore.saveLogMessage(
tag = tag,
message = message,
throwable = throwable,
shouldSanitize = shouldSanitize,
)
}
}

View file

@ -0,0 +1,86 @@
package com.tangem.tap.common.log
import android.os.Build
import android.util.Log
import com.tangem.utils.logging.Severity
import com.tangem.utils.logging.TangemLogger
/**
* [TangemLogger.LogWriter] that pretty-prints log entries to Logcat.
*
* Wraps each entry in unicode borders and chunks long messages so that they fit
* Android's per-entry byte limit (~4076 bytes).
*/
internal class LogcatLogWriter : TangemLogger.LogWriter {
override fun write(
severity: Severity,
tag: String,
message: String,
throwable: Throwable?,
shouldSanitize: Boolean,
) {
val priority = severity.toAndroidPriority()
val truncatedTag = tag.truncateForLogcat()
val finalMessage = if (throwable != null) {
"$message\n${Log.getStackTraceString(throwable)}"
} else {
message
}
printBoxed(priority, truncatedTag, finalMessage)
}
private fun printBoxed(priority: Int, tag: String, message: String) {
Log.println(priority, tag, TOP_BORDER)
val bytes = message.toByteArray()
val length = bytes.size
if (length <= CHUNK_SIZE) {
printContent(priority, tag, message)
} else {
var i = 0
while (i < length) {
val count = (length - i).coerceAtMost(CHUNK_SIZE)
printContent(priority, tag, String(bytes, i, count))
i += CHUNK_SIZE
}
}
Log.println(priority, tag, BOTTOM_BORDER)
}
private fun printContent(priority: Int, tag: String, chunk: String) {
chunk.split(System.lineSeparator()).forEach { line ->
Log.println(priority, tag, "$HORIZONTAL_LINE $line")
}
}
@Suppress("MagicNumber")
private fun String.truncateForLogcat(): String {
// Tag length limit was removed in API 26.
return if (length <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= 26) {
this
} else {
substring(0, MAX_TAG_LENGTH)
}
}
private fun Severity.toAndroidPriority(): Int = when (this) {
Severity.Verbose -> Log.VERBOSE
Severity.Debug -> Log.DEBUG
Severity.Info -> Log.INFO
Severity.Warn -> Log.WARN
Severity.Error -> Log.ERROR
Severity.Assert -> Log.ASSERT
}
private companion object {
// Android's max per-entry byte limit is ~4076; leave headroom for borders.
const val CHUNK_SIZE = 4000
const val MAX_TAG_LENGTH = 23
const val HORIZONTAL_LINE = ""
const val DIVIDER = "────────────────────────────────────────────────────────"
const val TOP_BORDER = "$DIVIDER$DIVIDER"
const val BOTTOM_BORDER = "$DIVIDER$DIVIDER"
}
}

View file

@ -1,17 +1,8 @@
package com.tangem.tap.common.log
import android.os.Build
import android.util.Log
import co.touchlab.kermit.BaseLogger
import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Logger
import co.touchlab.kermit.Severity
import com.orhanobut.logger.AndroidLogAdapter
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.utils.logging.TangemLogger
import com.tangem.wallet.BuildConfig
import java.util.regex.Pattern
import com.orhanobut.logger.Logger as PrettyLogger
/**
* Tangem app logger
@ -24,98 +15,14 @@ class TangemAppLoggerInitializer(
private val appLogsStore: AppLogsStore,
) {
/** Initialize */
fun initialize() {
if (IS_LOG_ENABLED) {
PrettyLogger.addLogAdapter(AndroidLogAdapter(TimberFormatStrategy()))
}
Logger.setLogWriters(KermitLogWriter(::finalLogOutput))
}
private fun finalLogOutput(priority: Int, tag: String?, message: String, t: Throwable?) {
if (IS_LOG_ENABLED) {
PrettyLogger.log(priority, tag, message, t)
}
if (PERMITTED_PRIORITY.contains(priority)) {
appLogsStore.saveLogMessage(
tag = tag ?: "TangemAppLogger",
message = message,
)
}
}
@Suppress("BooleanPropertyNaming")
private companion object {
val IS_LOG_ENABLED: Boolean = BuildConfig.LOG_ENABLED
val PERMITTED_PRIORITY = listOf(Log.ERROR, Log.INFO)
}
}
private class KermitLogWriter(
private val finalLogOutput: (priority: Int, tag: String?, message: String, t: Throwable?) -> Unit,
) : LogWriter() {
private val fqcnIgnore = setOf(
LogWriter::class.java.name,
KermitLogWriter::class.java.name,
BaseLogger::class.java.name,
Logger::class.java.name,
TangemLogger::class.java.name,
TangemLogger.TaggedLogger::class.java.name,
)
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
val priority = when (severity) {
Severity.Verbose -> PrettyLogger.VERBOSE
Severity.Debug -> PrettyLogger.DEBUG
Severity.Info -> PrettyLogger.INFO
Severity.Warn -> PrettyLogger.WARN
Severity.Error -> PrettyLogger.ERROR
Severity.Assert -> PrettyLogger.ASSERT
}
val finalTag = if (tag != KERMIT_LOGGER_DEFAULT_TAG) {
tag
} else {
/**
* like in [Logger.debugTree.tag]
*/
@Suppress("UnnecessaryLet", "ThrowingExceptionsWithoutMessageOrCause")
Throwable().stackTrace
.first { it.className !in fqcnIgnore }
.let(::createStackElementTag)
}
finalLogOutput(priority, finalTag, message, throwable)
}
/**
* copy from [Logger.debugTree.createStackElementTag]
*/
@Suppress("MagicNumber")
private fun createStackElementTag(element: StackTraceElement): String? {
var tag = element.className.substringAfterLast('.')
val m = ANONYMOUS_CLASS.matcher(tag)
if (m.find()) {
tag = m.replaceAll("")
}
// Tag length limit was removed in API 26.
return if (tag.length <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= 26) {
tag
} else {
tag.substring(0, MAX_TAG_LENGTH)
}
}
private companion object {
private const val KERMIT_LOGGER_DEFAULT_TAG = ""
/**
* copy from [Logger.debugTree.Companion]
*/
private const val MAX_TAG_LENGTH = 23
private val ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$")
TangemLogger.setLogWriters(
buildList {
if (BuildConfig.LOG_ENABLED) {
add(LogcatLogWriter())
}
add(FileLogWriter(appLogsStore))
},
)
}
}

View file

@ -1,63 +0,0 @@
package com.tangem.tap.common.log
import com.orhanobut.logger.FormatStrategy
import com.orhanobut.logger.LogStrategy
import com.orhanobut.logger.LogcatLogStrategy
class TimberFormatStrategy : FormatStrategy {
private val logStrategy: LogStrategy = LogcatLogStrategy()
override fun log(priority: Int, tag: String?, message: String) {
logTopBorder(priority, tag)
val bytes = message.toByteArray()
val length = bytes.size
if (length <= CHUNK_SIZE) {
logContent(priority, tag, message)
logBottomBorder(priority, tag)
return
}
var i = 0
while (i < length) {
val count = (length - i).coerceAtMost(CHUNK_SIZE)
// create a new String with system's default charset (which is UTF-8 for Android)
logContent(priority, tag, String(bytes, i, count))
i += CHUNK_SIZE
}
logBottomBorder(priority, tag)
}
private fun logTopBorder(logType: Int, tag: String?) {
logChunk(logType, tag, TOP_BORDER)
}
private fun logBottomBorder(logType: Int, tag: String?) {
logChunk(logType, tag, BOTTOM_BORDER)
}
private fun logContent(logType: Int, tag: String?, chunk: String) {
chunk.split(System.lineSeparator()).forEach { line ->
logChunk(logType, tag, "$HORIZONTAL_LINE $line")
}
}
private fun logChunk(priority: Int, tag: String?, chunk: String) {
logStrategy.log(priority, tag, chunk)
}
private companion object {
/**
* Android's max limit for a log entry is ~4076 bytes,
* so 4000 bytes is used as chunk size since default charset
* is UTF-8
*/
private const val CHUNK_SIZE = 4000
const val TOP_LEFT_CORNER = ""
const val BOTTOM_LEFT_CORNER = ""
const val HORIZONTAL_LINE = ""
const val DOUBLE_DIVIDER = "────────────────────────────────────────────────────────"
const val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
const val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
}
}