Updated on 2026-08-14
This commit is contained in:
parent
91c2fb2d58
commit
d718cf557d
18 changed files with 1135 additions and 280 deletions
|
|
@ -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,
|
||||
|
|
|
|||
36
app/src/main/java/com/tangem/tap/common/log/FileLogWriter.kt
Normal file
36
app/src/main/java/com/tangem/tap/common/log/FileLogWriter.kt
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue