Add file logger

main
Alex Baker 2 days ago
parent ed1f584daf
commit 31afc842bd

@ -14,6 +14,7 @@ import com.facebook.soloader.SoLoader
import com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo import com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo
import com.todoroo.andlib.utility.AndroidUtilities.atLeastQ import com.todoroo.andlib.utility.AndroidUtilities.atLeastQ
import leakcanary.AppWatcher import leakcanary.AppWatcher
import org.tasks.logging.FileLogger
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import timber.log.Timber import timber.log.Timber
import timber.log.Timber.DebugTree import timber.log.Timber.DebugTree
@ -21,10 +22,11 @@ import javax.inject.Inject
class BuildSetup @Inject constructor( class BuildSetup @Inject constructor(
private val context: Application, private val context: Application,
private val preferences: Preferences) { private val preferences: Preferences
) {
fun setup() { fun setup() {
Timber.plant(DebugTree()) Timber.plant(DebugTree())
Timber.plant(FileLogger(context))
SoLoader.init(context, false) SoLoader.init(context, false)
if (preferences.getBoolean(R.string.p_leakcanary, false)) { if (preferences.getBoolean(R.string.p_leakcanary, false)) {
AppWatcher.manualInstall(context) AppWatcher.manualInstall(context)
@ -63,4 +65,4 @@ class BuildSetup @Inject constructor(
StrictMode.setVmPolicy(builder.build()) StrictMode.setVmPolicy(builder.build())
} }
} }
} }

@ -0,0 +1,67 @@
package org.tasks.logging
import android.content.Context
import android.os.Process
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR
import timber.log.Timber
import java.io.File
import java.util.logging.FileHandler
import java.util.logging.Logger
class FileLogger(
context: Context,
) : Timber.DebugTree() {
val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val fileHandler: FileHandler = FileHandler(
"${logDirectory.absolutePath}/log.%g.txt",
20 * 1024 * 1024,
10
)
private val logger = Logger.getLogger(context.packageName)
init {
fileHandler.formatter = LogFormatter()
logger.addHandler(fileHandler)
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority < Log.DEBUG) {
return
}
val threadId = Process.myTid()
scope.launch {
t?.let { logger.info(it.stackTrace.joinToString(LINE_SEPARATOR)) }
logger.info("${Process.myPid()}-${threadId} ${(tag ?: "").truncateOrPad()} ${levels[priority] ?: priority} $message")
}
}
fun flush() {
fileHandler.flush()
}
companion object {
private const val MAX_LENGTH = 23
private const val TAG_PART = (MAX_LENGTH - 3) / 2
private val levels = mapOf(
Log.VERBOSE to "V",
Log.DEBUG to "D",
Log.INFO to "I",
Log.WARN to "W",
Log.ERROR to "E",
)
private fun String.truncateOrPad(): String =
when {
length == MAX_LENGTH -> this
length < MAX_LENGTH -> padEnd(MAX_LENGTH, ' ')
else -> "${substring(0, TAG_PART)}...${substring(length - TAG_PART)}"
}
}
}

@ -0,0 +1,20 @@
package org.tasks.logging
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.logging.Formatter
import java.util.logging.LogRecord
class LogFormatter : Formatter() {
override fun format(r: LogRecord): String =
"${DATE_FORMAT.format(Date(r.millis))}Z ${r.message}$LINE_SEPARATOR${r.thrown?.stackTrace?.joinToString(LINE_SEPARATOR) ?: ""}"
companion object {
val LINE_SEPARATOR = System.lineSeparator() ?: "\n"
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
}
}

@ -1,12 +1,19 @@
package org.tasks package org.tasks
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application
import android.util.Log import android.util.Log
import org.tasks.logging.FileLogger
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class BuildSetup @Inject constructor() { class BuildSetup @Inject constructor(
fun setup() = Timber.plant(ErrorReportingTree()) private val context: Application,
) {
fun setup() {
Timber.plant(ErrorReportingTree())
Timber.plant(FileLogger(context))
}
private class ErrorReportingTree : Timber.Tree() { private class ErrorReportingTree : Timber.Tree() {
@SuppressLint("LogNotTimber") @SuppressLint("LogNotTimber")

Loading…
Cancel
Save