diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt index 7a7e2c72b0..1b3f599f3f 100644 --- a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt +++ b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt @@ -3,19 +3,19 @@ package com.tangem.tap.common.log import android.util.Log import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.Logger -import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.wallet.BuildConfig import timber.log.Timber /** * Tangem app logger * - * @property settingsRepository repository for saving logs + * @property appLogsStore app logs store * [REDACTED_AUTHOR] */ class TangemAppLoggerInitializer( - private val settingsRepository: SettingsRepository, + private val appLogsStore: AppLogsStore, ) { /** Initialize */ @@ -35,7 +35,7 @@ class TangemAppLoggerInitializer( } if (PERMITTED_PRIORITY.contains(priority)) { - settingsRepository.saveLogMessage(message) + appLogsStore.saveLogMessage(message) } } } diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt b/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt index fad6b562f7..ef740906db 100644 --- a/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt +++ b/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt @@ -3,26 +3,26 @@ package com.tangem.tap.common.log import com.tangem.Log import com.tangem.LogFormat import com.tangem.TangemSdkLogger -import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.datasource.local.logs.AppLogsStore /** * CardSDK logger implementation * - * @property levels logging levels - * @property messageFormatter message formatter - * @property settingsRepository settings repository + * @property levels logging levels + * @property messageFormatter message formatter + * @property appLogsStore app logs store * [REDACTED_AUTHOR] */ internal class TangemCardSDKLogger( private val levels: List, private val messageFormatter: LogFormat, - private val settingsRepository: SettingsRepository, + private val appLogsStore: AppLogsStore, ) : TangemSdkLogger { override fun log(message: () -> String, level: Log.Level) { if (!levels.contains(level)) return - settingsRepository.saveLogMessage(message = messageFormatter.format(message, level)) + appLogsStore.saveLogMessage(message = messageFormatter.format(message, level)) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt index 21cca072f7..a544190398 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt @@ -9,7 +9,7 @@ import timber.log.Timber val logMiddleware: Middleware = { _, _ -> { nextDispatch -> { action -> - Timber.i("Dispatch action: $action") + Timber.i("Dispatch action: ${action::class.java.simpleName}") nextDispatch(action) } } diff --git a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt index c275de1626..a85c3e4667 100644 --- a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt +++ b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt @@ -1,20 +1,20 @@ package com.tangem.tap.data import com.tangem.blockchain.common.logging.BlockchainSDKLogger -import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.datasource.local.logs.AppLogsStore /** * BlockchainSDK logger implementation * - * @property settingsRepository settings repository + * @property appLogsStore app logs store * [REDACTED_AUTHOR] */ internal class TangemBlockchainSDKLogger( - private val settingsRepository: SettingsRepository, + private val appLogsStore: AppLogsStore, ) : BlockchainSDKLogger { override fun log(level: BlockchainSDKLogger.Level, message: String) { - settingsRepository.saveLogMessage(message) + appLogsStore.saveLogMessage(message) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt b/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt index e361ca70a6..400f58d8b2 100644 --- a/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt @@ -4,7 +4,7 @@ import com.tangem.Log import com.tangem.LogFormat import com.tangem.TangemSdkLogger import com.tangem.blockchain.common.logging.BlockchainSDKLogger -import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.common.log.TangemCardSDKLogger import com.tangem.tap.data.TangemBlockchainSDKLogger @@ -20,13 +20,13 @@ internal object TangemLoggingModule { @Provides @Singleton - fun provideAppLoggerInitializer(settingsRepository: SettingsRepository): TangemAppLoggerInitializer { - return TangemAppLoggerInitializer(settingsRepository) + fun provideAppLoggerInitializer(appLogsStore: AppLogsStore): TangemAppLoggerInitializer { + return TangemAppLoggerInitializer(appLogsStore) } @Provides @Singleton - fun provideCardSDKLogger(settingsRepository: SettingsRepository): TangemSdkLogger { + fun provideCardSDKLogger(appLogsStore: AppLogsStore): TangemSdkLogger { val logLevels = listOf( Log.Level.ApduCommand, Log.Level.Apdu, @@ -44,13 +44,13 @@ internal object TangemLoggingModule { return TangemCardSDKLogger( levels = logLevels, messageFormatter = LogFormat.StairsFormatter(), - settingsRepository = settingsRepository, + appLogsStore = appLogsStore, ) } @Provides @Singleton - fun provideBlockchainSDKLogger(settingsRepository: SettingsRepository): BlockchainSDKLogger { - return TangemBlockchainSDKLogger(settingsRepository) + fun provideBlockchainSDKLogger(appLogsStore: AppLogsStore): BlockchainSDKLogger { + return TangemBlockchainSDKLogger(appLogsStore) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt index 38a05e8d79..d3fa302f26 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt @@ -1,69 +1,109 @@ package com.tangem.datasource.local.logs -import androidx.datastore.preferences.core.MutablePreferences -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys +import android.content.Context import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormatterBuilder +import timber.log.Timber +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter import javax.inject.Inject +import javax.inject.Singleton /** * Store for saving app logs * - * @property appPreferencesStore app preferences store - * @param dispatchers coroutine dispatcher provider + * @property applicationContext application context + * @param dispatchers coroutine dispatcher provider * [REDACTED_AUTHOR] */ +@Singleton class AppLogsStore @Inject constructor( - private val appPreferencesStore: AppPreferencesStore, + @ApplicationContext private val applicationContext: Context, dispatchers: CoroutineDispatcherProvider, ) { private val scope = CoroutineScope(dispatchers.io) private val mutex = Mutex() + private val file = File(applicationContext.filesDir, LOG_FILE_NAME) + + private val formatter = DateTimeFormatterBuilder() + .appendDayOfMonth(2) + .appendLiteral('.') + .appendMonthOfYear(2) + .appendLiteral(' ') + .appendHourOfDay(1) + .appendLiteral(':') + .appendMinuteOfHour(2) + .appendLiteral(':') + .appendSecondOfMinute(2) + .appendLiteral('.') + .appendMillisOfSecond(3) + .toFormatter() + + /** Get log file */ + fun getFile(): File? = if (file.exists()) file else null + /** Save log [message] */ fun saveLogMessage(message: String) { - val newLogs = DateTime.now().millis.toString() to message + launchWithLock { + createFileIfNotExist() - appPreferencesStore.editDataWithLock { preferences -> - val savedLogs = preferences.getObjectMap(PreferencesKeys.APP_LOGS_KEY) + writeMessage(message) + } + } - preferences.setObjectMap(key = PreferencesKeys.APP_LOGS_KEY, value = savedLogs + newLogs) + /** Save log that consists from [messages] */ + fun saveLogMessage(vararg messages: String) { + launchWithLock { + createFileIfNotExist() + + writeMessage(*messages) } } /** Delete deprecated logs if file size exceeds [maxSize] */ fun deleteDeprecatedLogs(maxSize: Int) { - appPreferencesStore.editDataWithLock { preferences -> - val savedLogs = preferences.getObjectMap(PreferencesKeys.APP_LOGS_KEY) - - var sum = 0 - preferences.setObjectMap( - key = PreferencesKeys.APP_LOGS_KEY, - value = savedLogs.entries - .sortedBy(Map.Entry::key) - .takeLastWhile { - sum += it.value.length - sum < maxSize - } - .associate { it.key to it.value }, - ) - } - } - - private fun AppPreferencesStore.editDataWithLock( - transform: suspend AppPreferencesStore.(MutablePreferences) -> Unit, - ) { - scope.launch { - mutex.withLock { - editData(transform) + launchWithLock { + if (file.exists() && file.length() > maxSize) { + file.delete() } } } + + private fun writeMessage(vararg messages: String) { + BufferedWriter(FileWriter(file, true)).use { writer -> + writer.append(formatter.print(DateTime.now())) + writer.append(": ") + messages.forEach(writer::append) + writer.newLine() + } + } + + private fun createFileIfNotExist() { + if (!file.exists()) { + runCatching { file.createNewFile() } + .onFailure(Timber::e) + } + } + + private fun launchWithLock(callback: () -> Unit) { + scope.launch { + mutex.withLock { + callback() + } + } + } + + private companion object { + const val LOG_FILE_NAME = "logs.txt" + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt index ee74b94ab5..b593688bd9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt @@ -54,8 +54,8 @@ internal class NetworkLogsSaveInterceptor( val connectionProtocol = if (connection != null) " ${connection.protocol()}" else "" appLogsStore.saveLogMessage( - "--> ${request.method} ${request.url}$connectionProtocol\n" + - createRequestEndMessage(request), + "--> ${request.method} ${request.url}$connectionProtocol\n", + createRequestEndMessage(request), ) } @@ -88,12 +88,6 @@ internal class NetworkLogsSaveInterceptor( } private fun logResponseMessage(response: Response, startNs: Long) { - val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) - - val responseMessage = if (response.message.isEmpty()) "" else ' ' + response.message - val startMessage = "<-- ${response.code}$responseMessage ${response.request.url} " + - "(${tookMs}ms)" - val responseHeaders = response.headers val responseBody = response.body!! val contentLength = responseBody.contentLength() @@ -138,7 +132,17 @@ internal class NetworkLogsSaveInterceptor( } } - appLogsStore.saveLogMessage(startMessage + "\n" + message) + val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) + + val spaceBeforeResponseMessage = if (response.message.isEmpty()) "" else ' ' + response.message + + appLogsStore.saveLogMessage( + "<-- ${response.code}", + spaceBeforeResponseMessage, + response.message, + " ${response.request.url} (${tookMs}ms)\n", + message, + ) } private fun bodyHasUnknownEncoding(headers: Headers): Boolean { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index f13343fc36..f0c510874b 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -5,7 +5,7 @@ }, { "name": "LOCAL_USER_LOGS_ENABLED", - "version": "undefined" + "version": "5.14.0" }, { "name": "GENERATE_XPUB_ENABLED", diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt index 26278b5877..4b018346c2 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt @@ -6,48 +6,43 @@ import android.os.Build import com.tangem.blockchain.common.Blockchain import com.tangem.data.feedback.converters.BlockchainInfoConverter import com.tangem.data.feedback.converters.CardInfoConverter -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getObjectMapSync +import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore -import com.tangem.domain.feedback.models.* +import com.tangem.domain.feedback.models.BlockchainErrorInfo +import com.tangem.domain.feedback.models.BlockchainInfo +import com.tangem.domain.feedback.models.PhoneInfo +import com.tangem.domain.feedback.models.UserWalletsInfo import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import timber.log.Timber import java.io.File -import java.io.FileWriter -import java.io.StringWriter /** * Implementation of [FeedbackRepository] * - * @property appPreferencesStore application preferences store + * @property appLogsStore app logs store * @property userWalletsListManager user wallets list manager * @property walletManagersStore wallet managers store * @property context context for getting app version - * @property dispatchers coroutine dispatchers provider * [REDACTED_AUTHOR] */ internal class DefaultFeedbackRepository( - private val appPreferencesStore: AppPreferencesStore, + private val appLogsStore: AppLogsStore, private val userWalletsListManager: UserWalletsListManager, private val walletManagersStore: WalletManagersStore, private val context: Context, - private val dispatchers: CoroutineDispatcherProvider, ) : FeedbackRepository { private val blockchainsErrors = MutableStateFlow>(emptyMap()) - override suspend fun getCardInfo(scanResponse: ScanResponse) = CardInfoConverter.convert(value = scanResponse) + override fun getCardInfo(scanResponse: ScanResponse) = CardInfoConverter.convert(value = scanResponse) - override suspend fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo { + override fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo { return UserWalletsInfo( selectedUserWalletId = userWalletId?.stringValue ?: "card isn't activated", totalUserWallets = userWalletsListManager.walletsCount, @@ -92,38 +87,13 @@ internal class DefaultFeedbackRepository( } } - override suspend fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo? { + override fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo? { return blockchainsErrors.value[userWalletId].also { if (it == null) Timber.e("Blockchain error info is null for $userWalletId") } } - override suspend fun getAppLogs(): List { - return appPreferencesStore.getObjectMapSync(key = PreferencesKeys.APP_LOGS_KEY) - .map { AppLogModel(timestamp = it.key.toLong(), message = it.value) } - .sortedBy(AppLogModel::timestamp) - } - - override suspend fun createLogFile(logs: String): File? { - return runCatching(dispatchers.io) { - val file = File(context.filesDir, LOGS_FILE) - file.delete() - file.createNewFile() - - val stringWriter = StringWriter() - - stringWriter.append(logs) - - val fileWriter = FileWriter(file) - fileWriter.write(stringWriter.toString()) - fileWriter.close() - - file - }.getOrElse { - Timber.e(it, "Logs file isn't created") - null - } - } + override fun getLogFile(): File? = appLogsStore.getFile() private fun getAppVersion(): String { return runCatching { context.packageManager.getPackageInfo(context.packageName, 0) } @@ -135,8 +105,4 @@ internal class DefaultFeedbackRepository( }, ) } - - private companion object { - const val LOGS_FILE = "logs.txt" - } } \ No newline at end of file diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt index 393a30cf25..2b377e8455 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt @@ -2,11 +2,10 @@ package com.tangem.data.feedback.di import android.content.Context import com.tangem.data.feedback.DefaultFeedbackRepository -import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -21,18 +20,16 @@ internal object FeedbackRepositoryModule { @Provides @Singleton fun provideFeedbackRepository( - appPreferencesStore: AppPreferencesStore, + appLogsStore: AppLogsStore, userWalletsListManager: UserWalletsListManager, walletManagersStore: WalletManagersStore, @ApplicationContext context: Context, - dispatchers: CoroutineDispatcherProvider, ): FeedbackRepository { return DefaultFeedbackRepository( - appPreferencesStore = appPreferencesStore, + appLogsStore = appLogsStore, userWalletsListManager = userWalletsListManager, walletManagersStore = walletManagersStore, context = context, - dispatchers = dispatchers, ) } } \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index 6ff9910d3e..0fd87454fa 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -37,10 +37,6 @@ internal class DefaultSettingsRepository( ) } - override fun saveLogMessage(message: String) { - appLogsStore.saveLogMessage(message) - } - override fun deleteDeprecatedLogs(maxSize: Int) { appLogsStore.deleteDeprecatedLogs(maxSize) } diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt index acdcc492b7..ddf8cfb767 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/GetFeedbackEmailUseCase.kt @@ -25,13 +25,11 @@ class GetFeedbackEmailUseCase( private val emailMessageBodyResolver = EmailMessageBodyResolver(feedbackRepository) suspend operator fun invoke(type: FeedbackEmailType): FeedbackEmail { - val formattedLogs = AppLogsFormatter().format(appLogs = feedbackRepository.getAppLogs()) - return FeedbackEmail( address = getAddress(type.cardInfo), subject = emailSubjectResolver.resolve(type), message = createMessage(type), - file = feedbackRepository.createLogFile(logs = formattedLogs), + file = feedbackRepository.getLogFile(), ) } diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt index 1b5de9c70c..25ebed4d9d 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt @@ -7,9 +7,9 @@ import java.io.File interface FeedbackRepository { - suspend fun getCardInfo(scanResponse: ScanResponse): CardInfo + fun getCardInfo(scanResponse: ScanResponse): CardInfo - suspend fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo + fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo suspend fun getBlockchainInfoList(userWalletId: UserWalletId): List @@ -23,9 +23,7 @@ interface FeedbackRepository { fun saveBlockchainErrorInfo(error: BlockchainErrorInfo) - suspend fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo? + fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo? - suspend fun getAppLogs(): List - - suspend fun createLogFile(logs: String): File? + fun getLogFile(): File? } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 06fd81fde6..50b3e3624d 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -10,8 +10,6 @@ interface SettingsRepository { suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean) - fun saveLogMessage(message: String) - fun deleteDeprecatedLogs(maxSize: Int) suspend fun isSendTapHelpPreviewEnabled(): Boolean