Implement logging module
This commit is contained in:
@@ -68,6 +68,7 @@ dependencies {
|
||||
|
||||
implementation(project(":database"))
|
||||
implementation(project(":datastore"))
|
||||
implementation(project(":logging"))
|
||||
|
||||
implementation(libs.android.material.material)
|
||||
|
||||
|
||||
@@ -4,22 +4,23 @@ import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
||||
import timber.log.Timber
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AddRecipeDataSourceImpl @Inject constructor(
|
||||
private val addRecipeServiceFactory: ServiceFactory<AddRecipeService>,
|
||||
private val logger: Logger,
|
||||
) : AddRecipeDataSource {
|
||||
|
||||
override suspend fun addRecipe(recipe: AddRecipeRequest): String {
|
||||
Timber.v("addRecipe() called with: recipe = $recipe")
|
||||
logger.v { "addRecipe() called with: recipe = $recipe" }
|
||||
val service = addRecipeServiceFactory.provideService()
|
||||
val response = logAndMapErrors(
|
||||
block = { service.addRecipe(recipe) }, logProvider = { "addRecipe: can't add recipe" }
|
||||
)
|
||||
Timber.v("addRecipe() response = $response")
|
||||
logger.v { "addRecipe() response = $response" }
|
||||
return response
|
||||
}
|
||||
}
|
||||
1
logging/.gitignore
vendored
Normal file
1
logging/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
14
logging/build.gradle.kts
Normal file
14
logging/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
id("gq.kirmanak.mealient.library")
|
||||
id("dagger.hilt.android.plugin")
|
||||
id("kotlin-kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "gq.kirmanak.mealient.logging"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.google.dagger.hiltAndroid)
|
||||
kapt(libs.google.dagger.hiltCompiler)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
interface Appender {
|
||||
|
||||
fun isLoggable(logLevel: LogLevel): Boolean
|
||||
|
||||
fun isLoggable(logLevel: LogLevel, tag: String): Boolean
|
||||
|
||||
fun log(logLevel: LogLevel, tag: String, message: String)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
enum class LogLevel { VERBOSE, DEBUG, INFO, WARNING, ERROR }
|
||||
@@ -0,0 +1,59 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LogcatAppender @Inject constructor() : Appender {
|
||||
|
||||
private val isLoggable: Boolean by lazy { BuildConfig.DEBUG }
|
||||
|
||||
override fun isLoggable(logLevel: LogLevel): Boolean = isLoggable
|
||||
|
||||
override fun isLoggable(logLevel: LogLevel, tag: String): Boolean = isLoggable
|
||||
|
||||
override fun log(logLevel: LogLevel, tag: String, message: String) {
|
||||
// Tag length limit was removed in API 26.
|
||||
val logTag = if (tag.length <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= 26) {
|
||||
tag
|
||||
} else {
|
||||
tag.substring(0, MAX_TAG_LENGTH)
|
||||
}
|
||||
|
||||
if (message.length < MAX_LOG_LENGTH) {
|
||||
Log.println(logLevel.priority, logTag, message)
|
||||
return
|
||||
}
|
||||
|
||||
// Split by line, then ensure each line can fit into Log's maximum length.
|
||||
var i = 0
|
||||
val length = message.length
|
||||
while (i < length) {
|
||||
var newline = message.indexOf('\n', i)
|
||||
newline = if (newline != -1) newline else length
|
||||
do {
|
||||
val end = newline.coerceAtMost(i + MAX_LOG_LENGTH)
|
||||
val part = message.substring(i, end)
|
||||
Log.println(logLevel.priority, logTag, part)
|
||||
i = end
|
||||
} while (i < newline)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_LOG_LENGTH = 4000
|
||||
private const val MAX_TAG_LENGTH = 23
|
||||
}
|
||||
}
|
||||
|
||||
private val LogLevel.priority: Int
|
||||
get() = when (this) {
|
||||
LogLevel.VERBOSE -> Log.VERBOSE
|
||||
LogLevel.DEBUG -> Log.DEBUG
|
||||
LogLevel.INFO -> Log.INFO
|
||||
LogLevel.WARNING -> Log.WARN
|
||||
LogLevel.ERROR -> Log.ERROR
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
typealias MessageSupplier = () -> String
|
||||
|
||||
interface Logger {
|
||||
|
||||
fun v(throwable: Throwable? = null, tag: String? = null, messageSupplier: MessageSupplier)
|
||||
|
||||
fun d(throwable: Throwable? = null, tag: String? = null, messageSupplier: MessageSupplier)
|
||||
|
||||
fun i(throwable: Throwable? = null, tag: String? = null, messageSupplier: MessageSupplier)
|
||||
|
||||
fun w(throwable: Throwable? = null, tag: String? = null, messageSupplier: MessageSupplier)
|
||||
|
||||
fun e(throwable: Throwable? = null, tag: String? = null, messageSupplier: MessageSupplier)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
import android.util.Log
|
||||
import java.util.regex.Pattern
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LoggerImpl @Inject constructor(
|
||||
private val appenders: Set<@JvmSuppressWildcards Appender>,
|
||||
) : Logger {
|
||||
|
||||
override fun v(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) {
|
||||
log(LogLevel.VERBOSE, tag, messageSupplier, throwable)
|
||||
}
|
||||
|
||||
override fun d(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) {
|
||||
log(LogLevel.DEBUG, tag, messageSupplier, throwable)
|
||||
}
|
||||
|
||||
override fun i(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) {
|
||||
log(LogLevel.INFO, tag, messageSupplier, throwable)
|
||||
}
|
||||
|
||||
override fun w(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) {
|
||||
log(LogLevel.WARNING, tag, messageSupplier, throwable)
|
||||
}
|
||||
|
||||
override fun e(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) {
|
||||
log(LogLevel.ERROR, tag, messageSupplier, throwable)
|
||||
}
|
||||
|
||||
private fun log(
|
||||
logLevel: LogLevel,
|
||||
tag: String?,
|
||||
messageSupplier: MessageSupplier,
|
||||
t: Throwable?
|
||||
) {
|
||||
var logTag: String? = null
|
||||
var message: String? = null
|
||||
for (appender in appenders) {
|
||||
if (appender.isLoggable(logLevel).not()) continue
|
||||
|
||||
logTag = logTag ?: tag ?: Throwable().stackTrace
|
||||
.first { element -> !IGNORED_CLASSES.any { element.className.contains(it) } }
|
||||
.let(::createStackElementTag)
|
||||
|
||||
if (appender.isLoggable(logLevel, logTag).not()) continue
|
||||
|
||||
message = message ?: (messageSupplier() + createStackTrace(t))
|
||||
|
||||
appender.log(logLevel, logTag, message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStackTrace(throwable: Throwable?): String =
|
||||
throwable?.let { Log.getStackTraceString(it) }
|
||||
?.takeUnless { it.isBlank() }
|
||||
?.let { "\n" + it }
|
||||
.orEmpty()
|
||||
|
||||
private fun createStackElementTag(element: StackTraceElement): String {
|
||||
var tag = element.className.substringAfterLast('.')
|
||||
val m = ANONYMOUS_CLASS.matcher(tag)
|
||||
if (m.find()) {
|
||||
tag = m.replaceAll("")
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IGNORED_CLASSES = listOf(Logger::class.java.name, LoggerImpl::class.java.name)
|
||||
private val ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package gq.kirmanak.mealient.logging
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface LoggingModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindLogger(loggerImpl: LoggerImpl): Logger
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
@IntoSet
|
||||
fun bindLogcatAppender(logcatAppender: LogcatAppender): Appender
|
||||
}
|
||||
@@ -22,3 +22,4 @@ rootProject.name = "Mealient"
|
||||
include(":app")
|
||||
include(":database")
|
||||
include(":datastore")
|
||||
include(":logging")
|
||||
|
||||
Reference in New Issue
Block a user