Implement logging module
This commit is contained in:
@@ -68,6 +68,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(project(":database"))
|
implementation(project(":database"))
|
||||||
implementation(project(":datastore"))
|
implementation(project(":datastore"))
|
||||||
|
implementation(project(":logging"))
|
||||||
|
|
||||||
implementation(libs.android.material.material)
|
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.add.models.AddRecipeRequest
|
||||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||||
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
||||||
import timber.log.Timber
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AddRecipeDataSourceImpl @Inject constructor(
|
class AddRecipeDataSourceImpl @Inject constructor(
|
||||||
private val addRecipeServiceFactory: ServiceFactory<AddRecipeService>,
|
private val addRecipeServiceFactory: ServiceFactory<AddRecipeService>,
|
||||||
|
private val logger: Logger,
|
||||||
) : AddRecipeDataSource {
|
) : AddRecipeDataSource {
|
||||||
|
|
||||||
override suspend fun addRecipe(recipe: AddRecipeRequest): String {
|
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 service = addRecipeServiceFactory.provideService()
|
||||||
val response = logAndMapErrors(
|
val response = logAndMapErrors(
|
||||||
block = { service.addRecipe(recipe) }, logProvider = { "addRecipe: can't add recipe" }
|
block = { service.addRecipe(recipe) }, logProvider = { "addRecipe: can't add recipe" }
|
||||||
)
|
)
|
||||||
Timber.v("addRecipe() response = $response")
|
logger.v { "addRecipe() response = $response" }
|
||||||
return 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(":app")
|
||||||
include(":database")
|
include(":database")
|
||||||
include(":datastore")
|
include(":datastore")
|
||||||
|
include(":logging")
|
||||||
|
|||||||
Reference in New Issue
Block a user