소스 검색

Replace Timber with Square Logcat and make logging configurable (#6062)

* Replace Timber with Square Logcat

* Configurable logger
Ivan Iskandar 3 년 전
부모
커밋
2e127dff1f
52개의 변경된 파일223개의 추가작업 그리고 142개의 파일을 삭제
  1. 1 1
      app/build.gradle.kts
  2. 9 2
      app/src/main/java/eu/kanade/tachiyomi/App.kt
  3. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
  4. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
  5. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
  6. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
  7. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
  8. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
  9. 6 5
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
  10. 2 0
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
  11. 2 0
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  12. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt
  13. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt
  14. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt
  15. 3 2
      app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt
  16. 4 3
      app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt
  17. 5 4
      app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt
  18. 3 2
      app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt
  19. 3 2
      app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
  20. 6 5
      app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt
  21. 1 2
      app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
  22. 3 2
      app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
  23. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
  24. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt
  25. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
  26. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
  27. 4 3
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt
  28. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt
  29. 4 3
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  30. 11 8
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
  31. 8 5
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
  32. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
  33. 5 4
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
  34. 8 7
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
  35. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
  36. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
  37. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt
  38. 9 9
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt
  39. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt
  40. 10 10
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt
  41. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt
  42. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt
  43. 4 3
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt
  44. 3 2
      app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt
  45. 12 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
  46. 3 2
      app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt
  47. 2 2
      app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
  48. 18 0
      app/src/main/java/eu/kanade/tachiyomi/util/system/LogcatExtensions.kt
  49. 2 2
      app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt
  50. 2 2
      app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
  51. 3 2
      app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt
  52. 2 0
      app/src/main/res/values/strings.xml

+ 1 - 1
app/build.gradle.kts

@@ -252,7 +252,7 @@ dependencies {
     implementation("io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbindingVersion")
 
     // Logging
-    implementation("com.jakewharton.timber:timber:5.0.1")
+    implementation("com.squareup.logcat:logcat:0.1")
 
     // Crash reports/analytics
     implementation("ch.acra:acra-http:5.8.1")

+ 9 - 2
app/src/main/java/eu/kanade/tachiyomi/App.kt

@@ -20,6 +20,7 @@ import coil.ImageLoader
 import coil.ImageLoaderFactory
 import coil.decode.GifDecoder
 import coil.decode.ImageDecoderDecoder
+import coil.util.DebugLogger
 import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher
 import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
 import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
@@ -34,11 +35,13 @@ import eu.kanade.tachiyomi.util.system.animatorDurationScale
 import eu.kanade.tachiyomi.util.system.notification
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import logcat.AndroidLogcatLogger
+import logcat.LogPriority
+import logcat.LogcatLogger
 import org.acra.config.httpSender
 import org.acra.ktx.initAcra
 import org.acra.sender.HttpSender
 import org.conscrypt.Conscrypt
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
@@ -52,7 +55,6 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
 
     override fun onCreate() {
         super<Application>.onCreate()
-        if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
 
         // TLS 1.3 support for Android < 10
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
@@ -110,6 +112,10 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
                     }
                 )
             }.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
+
+        if (!LogcatLogger.isInstalled && preferences.verboseLogging()) {
+            LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
+        }
     }
 
     override fun newImageLoader(): ImageLoader {
@@ -127,6 +133,7 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
             okHttpClient(Injekt.get<NetworkHelper>().coilClient)
             crossfade((300 * [email protected]).toInt())
             allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
+            if (preferences.verboseLogging()) logger(DebugLogger())
         }.build()
     }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt

@@ -13,13 +13,14 @@ import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestore
 import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
-import timber.log.Timber
+import logcat.LogPriority
 
 /**
  * Restores backup.
@@ -128,7 +129,7 @@ class BackupRestoreService : Service() {
         }
 
         val handler = CoroutineExceptionHandler { _, exception ->
-            Timber.e(exception)
+            logcat(LogPriority.ERROR, exception)
             backupRestore?.writeErrorLog()
 
             notifier.showRestoreError(exception.message)

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt

@@ -26,11 +26,12 @@ import eu.kanade.tachiyomi.data.database.models.History
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.database.models.MangaCategory
 import eu.kanade.tachiyomi.data.database.models.Track
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.serialization.protobuf.ProtoBuf
+import logcat.LogPriority
 import okio.buffer
 import okio.gzip
 import okio.sink
-import timber.log.Timber
 import kotlin.math.max
 
 class FullBackupManager(context: Context) : AbstractBackupManager(context) {
@@ -86,7 +87,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
             file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) }
             return file.uri.toString()
         } catch (e: Exception) {
-            Timber.e(e)
+            logcat(LogPriority.ERROR, e)
             throw e
         }
     }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt

@@ -14,8 +14,9 @@ import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.util.lang.launchIO
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Observable
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
@@ -338,7 +339,7 @@ class DownloadManager(
             cache.removeChapter(oldChapter, manga)
             cache.addChapter(newName, mangaDir, manga)
         } else {
-            Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
+            logcat(LogPriority.ERROR) { "Could not rename downloaded chapter: ${oldNames.joinToString()}." }
         }
     }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt

@@ -9,10 +9,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.util.storage.DiskUtil
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -54,7 +55,7 @@ class DownloadProvider(private val context: Context) {
                 .createDirectory(getSourceDirName(source))
                 .createDirectory(getMangaDirName(manga))
         } catch (e: Throwable) {
-            Timber.e(e, "Invalid download directory")
+            logcat(LogPriority.ERROR, e) { "Invalid download directory" }
             throw Exception(context.getString(R.string.invalid_download_dir))
         }
     }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt

@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isConnectedToWifi
 import eu.kanade.tachiyomi.util.system.isOnline
 import eu.kanade.tachiyomi.util.system.isServiceRunning
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.notification
 import eu.kanade.tachiyomi.util.system.toast
 import kotlinx.coroutines.CoroutineScope
@@ -27,9 +28,9 @@ import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import logcat.LogPriority
 import ru.beryukhov.reactivenetwork.ReactiveNetwork
 import rx.subscriptions.CompositeSubscription
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -143,7 +144,7 @@ class DownloadService : Service() {
             }
             .catch { error ->
                 withUIContext {
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                     toast(R.string.download_queue_error)
                     stopSelf()
                 }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt

@@ -22,13 +22,14 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.saveTo
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.async
+import logcat.LogPriority
 import okhttp3.Response
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import rx.subscriptions.CompositeSubscription
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 
@@ -209,7 +210,7 @@ class Downloader(
                 },
                 { error ->
                     DownloadService.stop(context)
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                     notifier.onError(error.message)
                 }
             )

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.createFileInCacheDir
 import eu.kanade.tachiyomi.util.system.isServiceRunning
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -50,7 +51,7 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.supervisorScope
 import kotlinx.coroutines.sync.Semaphore
 import kotlinx.coroutines.sync.withPermit
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -212,7 +213,7 @@ class LibraryUpdateService(
 
         // Destroy service when completed or in case of an error.
         val handler = CoroutineExceptionHandler { _, exception ->
-            Timber.e(exception)
+            logcat(LogPriority.ERROR, exception)
             stopSelf(startId)
         }
         updateJob = ioScope.launch(handler) {
@@ -377,7 +378,7 @@ class LibraryUpdateService(
         // Update manga details metadata in the background
         if (preferences.autoUpdateMetadata()) {
             val handler = CoroutineExceptionHandler { _, exception ->
-                Timber.e(exception)
+                logcat(LogPriority.ERROR, exception)
             }
             GlobalScope.launch(Dispatchers.IO + handler) {
                 val updatedManga = source.getMangaDetails(manga.toMangaInfo())
@@ -433,7 +434,7 @@ class LibraryUpdateService(
                                             }
                                         } catch (e: Throwable) {
                                             // Ignore errors and continue
-                                            Timber.e(e)
+                                            logcat(LogPriority.ERROR, e)
                                         }
                                     }
 
@@ -494,7 +495,7 @@ class LibraryUpdateService(
                                 }
                             } catch (e: Throwable) {
                                 // Ignore errors and continue
-                                Timber.e(e)
+                                logcat(LogPriority.ERROR, e)
                             }
                         }
                     }

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -227,6 +227,8 @@ object PreferenceKeys {
 
     const val extensionInstaller = "extension_installer"
 
+    const val verboseLogging = "verbose_logging"
+
     fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
 
     fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -331,6 +331,8 @@ class PreferencesHelper(val context: Context) {
         if (MiuiUtil.isMiui()) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER
     )
 
+    fun verboseLogging() = prefs.getBoolean(Keys.verboseLogging, false)
+
     fun setChapterSettingsDefault(manga: Manga) {
         prefs.edit {
             putInt(Keys.defaultChapterFilterByRead, manga.readFilter)

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt

@@ -3,7 +3,8 @@ package eu.kanade.tachiyomi.data.track.job
 import android.content.Context
 import androidx.core.content.edit
 import eu.kanade.tachiyomi.data.database.models.Track
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 
 class DelayedTrackingStore(context: Context) {
 
@@ -17,7 +18,7 @@ class DelayedTrackingStore(context: Context) {
         val (_, lastChapterRead) = preferences.getString(trackId, "0:0.0")!!.split(":")
         if (track.last_chapter_read > lastChapterRead.toFloat()) {
             val value = "${track.manga_id}:${track.last_chapter_read}"
-            Timber.i("Queuing track item: $trackId, $value")
+            logcat(LogPriority.INFO) { "Queuing track item: $trackId, $value" }
             preferences.edit {
                 putString(trackId, value)
             }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt

@@ -11,9 +11,10 @@ import androidx.work.WorkManager
 import androidx.work.WorkerParameters
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.util.concurrent.TimeUnit
@@ -44,7 +45,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
                         db.insertTrack(track).executeAsBlocking()
                     }
                 } catch (e: Exception) {
-                    Timber.e(e)
+                    logcat(LogPriority.ERROR, e)
                 }
             }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt

@@ -7,13 +7,14 @@ import eu.kanade.tachiyomi.network.GET
 import eu.kanade.tachiyomi.network.await
 import eu.kanade.tachiyomi.network.parseAs
 import eu.kanade.tachiyomi.util.lang.withIOContext
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
+import logcat.LogPriority
 import okhttp3.MediaType.Companion.toMediaType
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.RequestBody.Companion.toRequestBody
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 const val READLIST_API = "/api/v1/readlists"
@@ -56,7 +57,7 @@ class KomgaApi(private val client: OkHttpClient) {
                     last_chapter_read = progress.lastReadContinuousNumberSort
                 }
             } catch (e: Exception) {
-                Timber.w(e, "Could not get item: $url")
+                logcat(LogPriority.WARN, e) { "Could not get item: $url" }
                 throw e
             }
         }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt

@@ -20,7 +20,8 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.storage.saveTo
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 
@@ -121,7 +122,7 @@ class UpdaterService : Service() {
             }
             notifier.onDownloadFinished(apkFile.getUriCompat(this))
         } catch (error: Exception) {
-            Timber.e(error)
+            logcat(LogPriority.ERROR, error)
             notifier.onDownloadError(url)
         }
     }

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt

@@ -11,7 +11,8 @@ import android.os.Build
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.util.lang.use
 import eu.kanade.tachiyomi.util.system.getUriSize
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 
 class PackageInstallerInstaller(private val service: Service) : Installer(service) {
 
@@ -23,7 +24,7 @@ class PackageInstallerInstaller(private val service: Service) : Installer(servic
                 PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                     val userAction = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
                     if (userAction == null) {
-                        Timber.e("Fatal error for $intent")
+                        logcat(LogPriority.ERROR) { "Fatal error for $intent" }
                         continueQueue(InstallStep.Error)
                         return
                     }
@@ -74,7 +75,7 @@ class PackageInstallerInstaller(private val service: Service) : Installer(servic
                 session.commit(intentSender)
             }
         } catch (e: Exception) {
-            Timber.e(e, "Failed to install extension ${entry.downloadId} ${entry.uri}")
+            logcat(LogPriority.ERROR) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
             activeSession?.let { (_, sessionId) ->
                 packageInstaller.abandonSession(sessionId)
             }

+ 5 - 4
app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt

@@ -6,14 +6,15 @@ import android.os.Build
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.util.system.getUriSize
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
+import logcat.LogPriority
 import rikka.shizuku.Shizuku
-import timber.log.Timber
 import java.io.BufferedReader
 import java.io.InputStream
 
@@ -22,7 +23,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
     private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
 
     private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
-        Timber.e("Shizuku was killed prematurely")
+        logcat { "Shizuku was killed prematurely" }
         service.stopSelf()
     }
 
@@ -72,7 +73,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
                     continueQueue(InstallStep.Installed)
                 }
             } catch (e: Exception) {
-                Timber.e(e, "Failed to install extension ${entry.downloadId} ${entry.uri}")
+                logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
                 if (sessionId != null) {
                     exec("pm install-abandon $sessionId")
                 }
@@ -115,7 +116,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
                 false
             }
         } else {
-            Timber.e("Shizuku is not ready to use.")
+            logcat(LogPriority.ERROR) { "Shizuku is not ready to use." }
             service.toast(R.string.ext_installer_shizuku_stopped)
             service.stopSelf()
             false

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt

@@ -12,8 +12,9 @@ import eu.kanade.tachiyomi.extension.installer.Installer
 import eu.kanade.tachiyomi.extension.installer.PackageInstallerInstaller
 import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
 import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.notificationBuilder
-import timber.log.Timber
+import logcat.LogPriority
 
 class ExtensionInstallService : Service() {
 
@@ -46,7 +47,7 @@ class ExtensionInstallService : Service() {
                 PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstaller(this)
                 PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstaller(this)
                 else -> {
-                    Timber.e("Not implemented for installer $installerUsed")
+                    logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
                     stopSelf()
                     return START_NOT_STICKY
                 }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt

@@ -17,9 +17,10 @@ import eu.kanade.tachiyomi.extension.installer.Installer
 import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.util.storage.getUriCompat
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -239,7 +240,7 @@ internal class ExtensionInstaller(private val context: Context) {
 
             // Set next installation step
             if (uri == null) {
-                Timber.e("Couldn't locate downloaded APK")
+                logcat(LogPriority.ERROR) { "Couldn't locate downloaded APK" }
                 downloadsRelay.call(id to InstallStep.Error)
                 return
             }

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt

@@ -13,9 +13,10 @@ import eu.kanade.tachiyomi.source.CatalogueSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.SourceFactory
 import eu.kanade.tachiyomi.util.lang.Hash
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.async
 import kotlinx.coroutines.runBlocking
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -107,7 +108,7 @@ internal object ExtensionLoader {
 
         if (versionName.isNullOrEmpty()) {
             val exception = Exception("Missing versionName for extension $extName")
-            Timber.w(exception)
+            logcat(LogPriority.WARN, exception)
             return LoadResult.Error(exception)
         }
 
@@ -118,7 +119,7 @@ internal object ExtensionLoader {
                 "Lib version is $libVersion, while only versions " +
                     "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
             )
-            Timber.w(exception)
+            logcat(LogPriority.WARN, exception)
             return LoadResult.Error(exception)
         }
 
@@ -128,7 +129,7 @@ internal object ExtensionLoader {
             return LoadResult.Error("Package $pkgName isn't signed")
         } else if (signatureHash !in trustedSignatures) {
             val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash)
-            Timber.w("Extension $pkgName isn't trusted")
+            logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
             return LoadResult.Untrusted(extension)
         }
 
@@ -157,7 +158,7 @@ internal object ExtensionLoader {
                         else -> throw Exception("Unknown source class type! ${obj.javaClass}")
                     }
                 } catch (e: Throwable) {
-                    Timber.e(e, "Extension load error: $extName ($it)")
+                    logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" }
                     return LoadResult.Error(e)
                 }
             }

+ 1 - 2
app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt

@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.network
 
 import android.content.Context
 import coil.util.CoilUtils
-import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
 import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
@@ -31,7 +30,7 @@ class NetworkHelper(context: Context) {
                 .readTimeout(30, TimeUnit.SECONDS)
                 .addInterceptor(UserAgentInterceptor())
 
-            if (BuildConfig.DEBUG) {
+            if (preferences.verboseLogging()) {
                 val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
                     level = HttpLoggingInterceptor.Level.HEADERS
                 }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt

@@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.EpubFile
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.contentOrNull
@@ -21,8 +22,8 @@ import kotlinx.serialization.json.decodeFromStream
 import kotlinx.serialization.json.intOrNull
 import kotlinx.serialization.json.jsonArray
 import kotlinx.serialization.json.jsonPrimitive
+import logcat.LogPriority
 import rx.Observable
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.io.FileInputStream
@@ -147,7 +148,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
                             val dest = updateCover(chapter, this)
                             thumbnail_url = dest?.absolutePath
                         } catch (e: Exception) {
-                            Timber.e(e)
+                            logcat(LogPriority.ERROR, e)
                         }
                     }
                 }

+ 5 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt

@@ -10,10 +10,10 @@ import androidx.viewbinding.ViewBinding
 import com.bluelinelabs.conductor.Controller
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.cancel
-import timber.log.Timber
 
 abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : Controller(bundle) {
 
@@ -31,20 +31,20 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : Contro
 
                 override fun preCreateView(controller: Controller) {
                     viewScope = MainScope()
-                    Timber.d("Create view for ${controller.instance()}")
+                    logcat { "Create view for ${controller.instance()}" }
                 }
 
                 override fun preAttach(controller: Controller, view: View) {
-                    Timber.d("Attach view for ${controller.instance()}")
+                    logcat { "Attach view for ${controller.instance()}" }
                 }
 
                 override fun preDetach(controller: Controller, view: View) {
-                    Timber.d("Detach view for ${controller.instance()}")
+                    logcat { "Detach view for ${controller.instance()}" }
                 }
 
                 override fun preDestroyView(controller: Controller, view: View) {
                     viewScope.cancel()
-                    Timber.d("Destroy view for ${controller.instance()}")
+                    logcat { "Destroy view for ${controller.instance()}" }
                 }
             }
         )

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt

@@ -27,8 +27,9 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.getPreferenceKey
 import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
-import timber.log.Timber
+import logcat.LogPriority
 
 @SuppressLint("RestrictedApi")
 class SourcePreferencesController(bundle: Bundle? = null) :
@@ -77,7 +78,7 @@ class SourcePreferencesController(bundle: Bundle? = null) :
         try {
             addPreferencesForSource(screen, source)
         } catch (e: AbstractMethodError) {
-            Timber.e("Source did not implement [addPreferencesForSource]: ${source.name}")
+            logcat(LogPriority.ERROR) { "Source did not implement [addPreferencesForSource]: ${source.name}" }
         }
 
         manager.setPreferences(screen)

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt

@@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.more.MoreController
 import eu.kanade.tachiyomi.ui.webview.WebViewActivity
 import eu.kanade.tachiyomi.util.system.connectivityManager
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.openInBrowser
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.inflate
@@ -54,7 +55,7 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -405,7 +406,7 @@ open class BrowseSourceController(bundle: Bundle) :
      * @param error the error received.
      */
     fun onAddPageError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
         val adapter = adapter ?: return
         adapter.onLoadMoreComplete(null)
         hideProgressBar()

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt

@@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.withUIContext
 import eu.kanade.tachiyomi.util.removeCovers
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.asFlow
 import kotlinx.coroutines.flow.catch
@@ -44,10 +45,10 @@ import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
+import logcat.LogPriority
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.util.Date
@@ -155,7 +156,7 @@ open class BrowseSourcePresenter(
                     view.onAddPage(page, mangas)
                 },
                 { _, error ->
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                 }
             )
 
@@ -221,7 +222,7 @@ open class BrowseSourcePresenter(
                         view?.onMangaInitialized(it)
                     }
                 }
-                .catch { e -> Timber.e(e) }
+                .catch { e -> logcat(LogPriority.ERROR, e) }
                 .collect()
         }
     }
@@ -239,7 +240,7 @@ open class BrowseSourcePresenter(
             manga.initialized = true
             db.insertManga(manga).executeAsBlocking()
         } catch (e: Exception) {
-            Timber.e(e)
+            logcat(LogPriority.ERROR, e)
         }
         return manga
     }
@@ -282,7 +283,7 @@ open class BrowseSourcePresenter(
                             syncChaptersWithTrackServiceTwoWay(db, db.getChapters(manga).executeAsBlocking(), track, service as TrackService)
                         }
                     } catch (e: Exception) {
-                        Timber.w(e, "Could not match manga: ${manga.title} with service $service")
+                        logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }
                     }
                 }
             }

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt

@@ -16,12 +16,13 @@ import eu.kanade.tachiyomi.source.model.toSManga
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
 import eu.kanade.tachiyomi.util.lang.runAsObservable
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import rx.subjects.PublishSubject
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
@@ -198,7 +199,7 @@ open class GlobalSearchPresenter(
                     view.setItems(manga)
                 },
                 { _, error ->
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                 }
             )
     }
@@ -233,7 +234,7 @@ open class GlobalSearchPresenter(
                     view?.onMangaInitialized(source, manga)
                 },
                 { error ->
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                 }
             )
     }

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt

@@ -5,9 +5,10 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.data.download.model.DownloadQueue
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -30,7 +31,7 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
             .observeOn(AndroidSchedulers.mainThread())
             .map { it.map(::DownloadItem) }
             .subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
-                Timber.e(error)
+                logcat(LogPriority.ERROR, error)
             }
     }
 

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -61,13 +61,14 @@ import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.launchUI
 import eu.kanade.tachiyomi.util.system.dpToPx
 import eu.kanade.tachiyomi.util.system.isTablet
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import timber.log.Timber
+import logcat.LogPriority
 import java.util.Date
 import java.util.concurrent.TimeUnit
 
@@ -340,7 +341,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
                     NewUpdateDialogController(result).showDialog(router)
                 }
             } catch (e: Exception) {
-                Timber.e(e)
+                logcat(LogPriority.ERROR, e)
             }
         }
     }
@@ -356,7 +357,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
                 val pendingUpdates = ExtensionGithubApi().checkForUpdates(this@MainActivity)
                 preferences.extensionUpdatesCount().set(pendingUpdates.size)
             } catch (e: Exception) {
-                Timber.e(e)
+                logcat(LogPriority.ERROR, e)
             }
         }
     }

+ 11 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -87,6 +87,7 @@ import eu.kanade.tachiyomi.util.hasCustomCover
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.launchUI
 import eu.kanade.tachiyomi.util.storage.getUriCompat
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toShareIntent
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.getCoordinates
@@ -95,9 +96,9 @@ import eu.kanade.tachiyomi.util.view.snack
 import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import logcat.LogPriority
 import reactivecircus.flowbinding.recyclerview.scrollStateChanges
 import reactivecircus.flowbinding.swiperefreshlayout.refreshes
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
@@ -595,7 +596,9 @@ class MangaController :
                                 presenter.registerTracking(track, service as TrackService)
                             }
                         } catch (e: Exception) {
-                            Timber.w(e, "Could not match manga: ${manga.title} with service $service")
+                            logcat(LogPriority.WARN, e) {
+                                "Could not match manga: ${manga.title} with service $service"
+                            }
                         }
                     }
                 }
@@ -767,7 +770,7 @@ class MangaController :
                 startActivity(uri.toShareIntent(activity))
             }
         } catch (e: Exception) {
-            Timber.e(e)
+            logcat(LogPriority.ERROR, e)
             activity?.toast(R.string.error_sharing_cover)
         }
     }
@@ -780,7 +783,7 @@ class MangaController :
                 activity.toast(R.string.cover_saved)
             }
         } catch (e: Exception) {
-            Timber.e(e)
+            logcat(LogPriority.ERROR, e)
             activity?.toast(R.string.error_saving_cover)
         }
     }
@@ -836,7 +839,7 @@ class MangaController :
 
     fun onSetCoverError(error: Throwable) {
         activity?.toast(R.string.notification_cover_update_failed)
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
     }
 
     /**
@@ -1177,7 +1180,7 @@ class MangaController :
     }
 
     fun onChaptersDeletedError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
     }
 
     override fun startDownloadNow(position: Int) {
@@ -1231,7 +1234,7 @@ class MangaController :
     }
 
     fun onTrackingRefreshError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
         activity?.toast(error.message)
     }
 
@@ -1240,7 +1243,7 @@ class MangaController :
     }
 
     fun onTrackingSearchResultsError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
         getTrackingSearchDialog()?.onSearchResultsError(error.message)
     }
 

+ 8 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.getPicturesDir
 import eu.kanade.tachiyomi.util.storage.getTempShareDir
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.updateCoverLastModified
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
@@ -50,11 +51,11 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.supervisorScope
+import logcat.LogPriority
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -128,7 +129,9 @@ class MangaPresenter(
 
         getTrackingObservable()
             .observeOn(AndroidSchedulers.mainThread())
-            .subscribeLatestCache(MangaController::onTrackingCount) { _, error -> Timber.e(error) }
+            .subscribeLatestCache(MangaController::onTrackingCount) { _, error ->
+                logcat(LogPriority.ERROR, error)
+            }
 
         // Prepare the relay.
         chaptersRelay.flatMap { applyChapterFilters(it) }
@@ -138,7 +141,7 @@ class MangaPresenter(
                     filteredAndSortedChapters = chapters
                     view?.onNextChapters(chapters)
                 },
-                { _, error -> Timber.e(error) }
+                { _, error -> logcat(LogPriority.ERROR, error) }
             )
 
         // Manga info - end
@@ -428,7 +431,7 @@ class MangaPresenter(
                     view.onChapterDownloadUpdate(it)
                 },
                 { _, error ->
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                 }
             )
 
@@ -439,7 +442,7 @@ class MangaPresenter(
             .filter { download -> download.manga.id == manga.id }
             .observeOn(AndroidSchedulers.mainThread())
             .subscribeLatestCache(MangaController::onChapterDownloadUpdate) { _, error ->
-                Timber.e(error)
+                logcat(LogPriority.ERROR, error)
             }
     }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt

@@ -18,8 +18,9 @@ import eu.kanade.tachiyomi.util.preference.onClick
 import eu.kanade.tachiyomi.util.preference.preference
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.copyToClipboard
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
-import timber.log.Timber
+import logcat.LogPriority
 import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.Locale
@@ -112,7 +113,7 @@ class AboutController : SettingsController(), NoAppBarElevationController {
                 }
             } catch (error: Exception) {
                 activity?.toast(error.message)
-                Timber.e(error)
+                logcat(LogPriority.ERROR, error)
             }
         }
     }

+ 5 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -70,6 +70,7 @@ import eu.kanade.tachiyomi.util.system.createReaderThemeContext
 import eu.kanade.tachiyomi.util.system.getThemeColor
 import eu.kanade.tachiyomi.util.system.hasDisplayCutout
 import eu.kanade.tachiyomi.util.system.isNightMode
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.popupMenu
 import eu.kanade.tachiyomi.util.view.setTooltip
@@ -79,8 +80,8 @@ import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.sample
+import logcat.LogPriority
 import nucleus.factory.RequiresPresenter
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 import kotlin.math.abs
@@ -620,7 +621,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
             readingModeToast?.cancel()
             readingModeToast = toast(strings[mode])
         } catch (e: ArrayIndexOutOfBoundsException) {
-            Timber.e("Unknown reading mode: $mode")
+            logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
         }
     }
 
@@ -660,7 +661,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
      * this case the activity is closed and a toast is shown to the user.
      */
     fun setInitialChapterError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
         finish()
         toast(error.message)
     }
@@ -822,7 +823,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
                 toast(R.string.picture_saved)
             }
             is ReaderPresenter.SaveImageResult.Error -> {
-                Timber.e(result.error)
+                logcat(LogPriority.ERROR, result.error)
             }
         }
     }

+ 8 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt

@@ -34,14 +34,15 @@ import eu.kanade.tachiyomi.util.storage.getPicturesDir
 import eu.kanade.tachiyomi.util.storage.getTempShareDir
 import eu.kanade.tachiyomi.util.system.ImageUtil
 import eu.kanade.tachiyomi.util.system.isOnline
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.updateCoverLastModified
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
+import logcat.LogPriority
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -301,7 +302,7 @@ class ReaderPresenter(
     private fun loadNewChapter(chapter: ReaderChapter) {
         val loader = loader ?: return
 
-        Timber.d("Loading ${chapter.chapter.url}")
+        logcat { "Loading ${chapter.chapter.url}" }
 
         activeChapterSubscription?.unsubscribe()
         activeChapterSubscription = getLoadObservable(loader, chapter)
@@ -319,7 +320,7 @@ class ReaderPresenter(
     private fun loadAdjacent(chapter: ReaderChapter) {
         val loader = loader ?: return
 
-        Timber.d("Loading adjacent ${chapter.chapter.url}")
+        logcat { "Loading adjacent ${chapter.chapter.url}" }
 
         activeChapterSubscription?.unsubscribe()
         activeChapterSubscription = getLoadObservable(loader, chapter)
@@ -344,7 +345,7 @@ class ReaderPresenter(
             return
         }
 
-        Timber.d("Preloading ${chapter.chapter.url}")
+        logcat { "Preloading ${chapter.chapter.url}" }
 
         val loader = loader ?: return
 
@@ -383,7 +384,7 @@ class ReaderPresenter(
         }
 
         if (selectedChapter != currentChapters.currChapter) {
-            Timber.d("Setting ${selectedChapter.chapter.url} as active")
+            logcat { "Setting ${selectedChapter.chapter.url} as active" }
             onChapterChanged(currentChapters.currChapter)
             loadNewChapter(selectedChapter)
         }
@@ -549,7 +550,7 @@ class ReaderPresenter(
         manga.orientationType = rotationType
         db.updateViewerFlags(manga).executeAsBlocking()
 
-        Timber.i("Manga orientation is ${manga.orientationType}")
+        logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" }
 
         Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
             .subscribeFirst({ view, _ ->
@@ -734,7 +735,7 @@ class ReaderPresenter(
                 }
                 .awaitAll()
                 .mapNotNull { it.exceptionOrNull() }
-                .forEach { Timber.w(it) }
+                .forEach { logcat(LogPriority.INFO, it) }
         }
     }
 

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt

@@ -8,11 +8,11 @@ import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
+import eu.kanade.tachiyomi.util.system.logcat
 import rx.Completable
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import timber.log.Timber
 
 /**
  * Loader used to retrieve the [PageLoader] for a given chapter.
@@ -37,7 +37,7 @@ class ChapterLoader(
             .doOnNext { chapter.state = ReaderChapter.State.Loading }
             .observeOn(Schedulers.io())
             .flatMap { readerChapter ->
-                Timber.d("Loading pages for ${chapter.chapter.name}")
+                logcat { "Loading pages for ${chapter.chapter.name}" }
 
                 val loader = getPageLoader(readerChapter)
                 chapter.pageLoader = loader

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt

@@ -6,13 +6,14 @@ import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.lang.plusAssign
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Completable
 import rx.Observable
 import rx.schedulers.Schedulers
 import rx.subjects.PublishSubject
 import rx.subjects.SerializedSubject
 import rx.subscriptions.CompositeSubscription
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.util.concurrent.PriorityBlockingQueue
@@ -51,7 +52,7 @@ class HttpPageLoader(
                 },
                 { error ->
                     if (error !is InterruptedException) {
-                        Timber.e(error)
+                        logcat(LogPriority.ERROR, error)
                     }
                 }
             )

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt

@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.model
 import com.jakewharton.rxrelay.BehaviorRelay
 import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.ui.reader.loader.PageLoader
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
 
 data class ReaderChapter(val chapter: Chapter) {
 
@@ -36,7 +36,7 @@ data class ReaderChapter(val chapter: Chapter) {
         references--
         if (references == 0) {
             if (pageLoader != null) {
-                Timber.d("Recycling chapter ${chapter.name}")
+                logcat { "Recycling chapter ${chapter.name}" }
             }
             pageLoader?.recycle()
             pageLoader = null

+ 9 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt

@@ -17,9 +17,9 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
 import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.cancel
-import timber.log.Timber
 import kotlin.math.min
 
 /**
@@ -194,7 +194,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
      */
     private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) {
         val pages = page.chapter.pages ?: return
-        Timber.d("onReaderPageSelected: ${page.number}/${pages.size}")
+        logcat { "onReaderPageSelected: ${page.number}/${pages.size}" }
         activity.onPageSelected(page)
 
         // Skip preload on inserts it causes unwanted page jumping
@@ -205,7 +205,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
         // Preload next chapter once we're within the last 5 pages of the current chapter
         val inPreloadRange = pages.size - page.number < 5
         if (inPreloadRange && allowPreload && page.chapter == adapter.currentChapter) {
-            Timber.d("Request preload next chapter because we're at page ${page.number} of ${pages.size}")
+            logcat { "Request preload next chapter because we're at page ${page.number} of ${pages.size}" }
             adapter.nextTransition?.to?.let {
                 activity.requestPreloadChapter(it)
             }
@@ -217,10 +217,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
      * preload of the destination chapter of the transition.
      */
     private fun onTransitionSelected(transition: ChapterTransition) {
-        Timber.d("onTransitionSelected: $transition")
+        logcat { "onTransitionSelected: $transition" }
         val toChapter = transition.to
         if (toChapter != null) {
-            Timber.d("Request preload destination chapter because we're on the transition")
+            logcat { "Request preload destination chapter because we're on the transition" }
             activity.requestPreloadChapter(toChapter)
         } else if (transition is ChapterTransition.Next) {
             // No more chapters, show menu because the user is probably going to close the reader
@@ -244,13 +244,13 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
      * Sets the active [chapters] on this pager.
      */
     private fun setChaptersInternal(chapters: ViewerChapters) {
-        Timber.d("setChaptersInternal")
+        logcat { "setChaptersInternal" }
         val forceTransition = config.alwaysShowChapterTransition || adapter.items.getOrNull(pager.currentItem) is ChapterTransition
         adapter.setChapters(chapters, forceTransition)
 
         // Layout the pager once a chapter is being set
         if (pager.isGone) {
-            Timber.d("Pager first layout")
+            logcat { "Pager first layout" }
             val pages = chapters.currChapter.pages ?: return
             moveToPage(pages[min(chapters.currChapter.requestedPage, pages.lastIndex)])
             pager.isVisible = true
@@ -261,7 +261,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
      * Tells this viewer to move to the given [page].
      */
     override fun moveToPage(page: ReaderPage) {
-        Timber.d("moveToPage ${page.number}")
+        logcat { "moveToPage ${page.number}" }
         val position = adapter.items.indexOf(page)
         if (position != -1) {
             val currentPosition = pager.currentItem
@@ -271,7 +271,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
                 onPageChange(position)
             }
         } else {
-            Timber.d("Page $page not found in adapter")
+            logcat { "Page $page not found in adapter" }
         }
     }
 

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt

@@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters
 import eu.kanade.tachiyomi.util.system.createReaderThemeContext
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.widget.ViewPagerAdapter
-import timber.log.Timber
 
 /**
  * Pager adapter used by this [viewer] to where [ViewerChapters] updates are posted.
@@ -152,7 +152,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
             if (position != -1) {
                 return position
             } else {
-                Timber.d("Position for ${view.item} not found")
+                logcat { "Position for ${view.item} not found" }
             }
         }
         return POSITION_NONE

+ 10 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt

@@ -18,10 +18,10 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
 import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
+import eu.kanade.tachiyomi.util.system.logcat
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.cancel
 import rx.subscriptions.CompositeSubscription
-import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import kotlin.math.max
@@ -195,17 +195,17 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
      */
     private fun onPageSelected(page: ReaderPage, allowPreload: Boolean) {
         val pages = page.chapter.pages ?: return
-        Timber.d("onPageSelected: ${page.number}/${pages.size}")
+        logcat { "onPageSelected: ${page.number}/${pages.size}" }
         activity.onPageSelected(page)
 
         // Preload next chapter once we're within the last 5 pages of the current chapter
         val inPreloadRange = pages.size - page.number < 5
         if (inPreloadRange && allowPreload && page.chapter == adapter.currentChapter) {
-            Timber.d("Request preload next chapter because we're at page ${page.number} of ${pages.size}")
+            logcat { "Request preload next chapter because we're at page ${page.number} of ${pages.size}" }
             val nextItem = adapter.items.getOrNull(adapter.items.size - 1)
             val transitionChapter = (nextItem as? ChapterTransition.Next)?.to ?: (nextItem as?ReaderPage)?.chapter
             if (transitionChapter != null) {
-                Timber.d("Requesting to preload chapter ${transitionChapter.chapter.chapter_number}")
+                logcat { "Requesting to preload chapter ${transitionChapter.chapter.chapter_number}" }
                 activity.requestPreloadChapter(transitionChapter)
             }
         }
@@ -216,10 +216,10 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
      * preload of the destination chapter of the transition.
      */
     private fun onTransitionSelected(transition: ChapterTransition) {
-        Timber.d("onTransitionSelected: $transition")
+        logcat { "onTransitionSelected: $transition" }
         val toChapter = transition.to
         if (toChapter != null) {
-            Timber.d("Request preload destination chapter because we're on the transition")
+            logcat { "Request preload destination chapter because we're on the transition" }
             activity.requestPreloadChapter(toChapter)
         } else if (transition is ChapterTransition.Next) {
             // No more chapters, show menu because the user is probably going to close the reader
@@ -231,12 +231,12 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
      * Tells this viewer to set the given [chapters] as active.
      */
     override fun setChapters(chapters: ViewerChapters) {
-        Timber.d("setChapters")
+        logcat { "setChapters" }
         val forceTransition = config.alwaysShowChapterTransition || currentPage is ChapterTransition
         adapter.setChapters(chapters, forceTransition)
 
         if (recycler.isGone) {
-            Timber.d("Recycler first layout")
+            logcat { "Recycler first layout" }
             val pages = chapters.currChapter.pages ?: return
             moveToPage(pages[min(chapters.currChapter.requestedPage, pages.lastIndex)])
             recycler.isVisible = true
@@ -247,7 +247,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
      * Tells this viewer to move to the given [page].
      */
     override fun moveToPage(page: ReaderPage) {
-        Timber.d("moveToPage")
+        logcat { "moveToPage" }
         val position = adapter.items.indexOf(page)
         if (position != -1) {
             recycler.scrollToPosition(position)
@@ -255,7 +255,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
                 onScrolled(pos = position)
             }
         } else {
-            Timber.d("Page $page not found in adapter")
+            logcat { "Page $page not found in adapter" }
         }
     }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt

@@ -26,13 +26,14 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.onAnimationsFinished
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import logcat.LogPriority
 import reactivecircus.flowbinding.appcompat.queryTextChanges
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 /**
@@ -123,7 +124,7 @@ class HistoryController :
     fun onAddPageError(error: Throwable) {
         adapter?.onLoadMoreComplete(null)
         adapter?.endlessTargetCount = 1
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
     }
 
     override fun onUpdateEmptyView(size: Int) {

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt

@@ -25,14 +25,15 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity
+import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.notificationManager
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.onAnimationsFinished
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import logcat.LogPriority
 import reactivecircus.flowbinding.recyclerview.scrollStateChanges
 import reactivecircus.flowbinding.swiperefreshlayout.refreshes
-import timber.log.Timber
 
 /**
  * Fragment that shows recent chapters.
@@ -300,7 +301,7 @@ class UpdatesController :
      * @param error error message
      */
     fun onChaptersDeletedError(error: Throwable) {
-        Timber.e(error)
+        logcat(LogPriority.ERROR, error)
     }
 
     override fun downloadChapter(position: Int) {

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt

@@ -11,10 +11,11 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 import eu.kanade.tachiyomi.ui.recent.DateSectionItem
 import eu.kanade.tachiyomi.util.lang.toDateKey
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.text.DateFormat
 import java.util.Calendar
@@ -53,7 +54,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
                     view.onChapterDownloadUpdate(it)
                 },
                 { _, error ->
-                    Timber.e(error)
+                    logcat(LogPriority.ERROR, error)
                 }
             )
 
@@ -62,7 +63,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
             .onBackpressureBuffer()
             .observeOn(AndroidSchedulers.mainThread())
             .subscribeLatestCache(UpdatesController::onChapterDownloadUpdate) { _, error ->
-                Timber.e(error)
+                logcat(LogPriority.ERROR, error)
             }
     }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/security/UnlockActivity.kt

@@ -7,7 +7,8 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 import java.util.Date
 
 /**
@@ -27,7 +28,7 @@ class UnlockActivity : BaseThemedActivity() {
                     errString: CharSequence
                 ) {
                     super.onAuthenticationError(activity, errorCode, errString)
-                    Timber.e(errString.toString())
+                    logcat(LogPriority.ERROR) { errString.toString() }
                     finishAffinity()
                 }
 

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt

@@ -74,6 +74,18 @@ class SettingsAdvancedController : SettingsController() {
             }
         }
 
+        switchPreference {
+            key = Keys.verboseLogging
+            titleRes = R.string.pref_verbose_logging
+            summaryRes = R.string.pref_verbose_logging_summary
+            defaultValue = false
+
+            onChange {
+                activity?.toast(R.string.requires_app_restart)
+                true
+            }
+        }
+
         preferenceCategory {
             titleRes = R.string.label_background_activity
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt

@@ -5,7 +5,8 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.util.lang.launchIO
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 
 /**
  * Helper method for syncing a remote track with the local chapters, and back
@@ -33,7 +34,7 @@ fun syncChaptersWithTrackServiceTwoWay(db: DatabaseHelper, chapters: List<Chapte
             service.update(remoteTrack)
             db.insertTrack(remoteTrack).executeAsBlocking()
         } catch (e: Throwable) {
-            Timber.w(e)
+            logcat(LogPriority.WARN, e)
         }
     }
 }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt

@@ -47,7 +47,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceValues
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
 import eu.kanade.tachiyomi.util.lang.truncateCenter
-import timber.log.Timber
+import logcat.LogPriority
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -93,7 +93,7 @@ fun Context.copyToClipboard(label: String, content: String) {
 
         toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
     } catch (e: Throwable) {
-        Timber.e(e)
+        logcat(LogPriority.ERROR, e)
         toast(R.string.clipboard_copy_error)
     }
 }

+ 18 - 0
app/src/main/java/eu/kanade/tachiyomi/util/system/LogcatExtensions.kt

@@ -0,0 +1,18 @@
+package eu.kanade.tachiyomi.util.system
+
+import logcat.LogPriority
+import logcat.asLog
+import logcat.logcat
+
+inline fun Any.logcat(
+    priority: LogPriority = LogPriority.DEBUG,
+    throwable: Throwable? = null,
+    message: () -> String = { "" }
+) = logcat(priority = priority) {
+    var msg = message()
+    if (throwable != null) {
+        if (msg.isNotBlank()) msg += "\n"
+        msg += throwable.asLog()
+    }
+    msg
+}

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt

@@ -1,7 +1,7 @@
 package eu.kanade.tachiyomi.util.system
 
 import android.annotation.SuppressLint
-import timber.log.Timber
+import logcat.LogPriority
 
 object MiuiUtil {
 
@@ -32,7 +32,7 @@ object MiuiUtil {
                 .getDeclaredMethod("get", String::class.java)
                 .invoke(null, key) as String
         } catch (e: Exception) {
-            Timber.w(e, "Unable to use SystemProperties.get")
+            logcat(LogPriority.WARN, e) { "Unable to use SystemProperties.get()" }
             null
         }
     }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt

@@ -6,7 +6,7 @@ import android.content.pm.PackageManager
 import android.webkit.CookieManager
 import android.webkit.WebSettings
 import android.webkit.WebView
-import timber.log.Timber
+import logcat.LogPriority
 
 object WebViewUtil {
     const val REQUESTED_WITH = "com.android.browser"
@@ -19,7 +19,7 @@ object WebViewUtil {
             // is not installed
             CookieManager.getInstance()
         } catch (e: Throwable) {
-            Timber.e(e)
+            logcat(LogPriority.ERROR, e)
             return false
         }
 

+ 3 - 2
app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt

@@ -8,7 +8,8 @@ import android.view.View
 import android.widget.LinearLayout
 import androidx.core.widget.doOnTextChanged
 import eu.kanade.tachiyomi.databinding.DownloadCustomAmountBinding
-import timber.log.Timber
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
 
 /**
  * Custom dialog to select how many chapters to download.
@@ -71,7 +72,7 @@ class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs
                 amount = getAmount(text.toString().toInt())
             } catch (error: NumberFormatException) {
                 // Catch NumberFormatException to prevent parse exception when input is empty.
-                Timber.e(error)
+                logcat(LogPriority.ERROR, error)
             }
         }
     }

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -476,6 +476,8 @@
     <string name="battery_optimization_setting_activity_not_found">Couldn\'t open device settings</string>
     <string name="about_dont_kill_my_app">Some manufacturers have additional app restrictions that kill background services. This website has more info on how to fix it.</string>
     <string name="pref_tablet_ui_mode">Force tablet UI</string>
+    <string name="pref_verbose_logging">Verbose logging</string>
+    <string name="pref_verbose_logging_summary">Print verbose logs to system log (reduces app performance)</string>
 
       <!-- About section -->
     <string name="website">Website</string>