From ba5f7322ab58af6439d0f9d8818d5d26e77a2a51 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 5 Aug 2022 18:58:21 +0200 Subject: [PATCH] Implement logging module --- app/build.gradle.kts | 1 + .../data/add/impl/AddRecipeDataSourceImpl.kt | 7 +- logging/.gitignore | 1 + logging/build.gradle.kts | 14 ++++ .../gq/kirmanak/mealient/logging/Appender.kt | 11 +++ .../gq/kirmanak/mealient/logging/LogLevel.kt | 3 + .../mealient/logging/LogcatAppender.kt | 59 +++++++++++++++ .../gq/kirmanak/mealient/logging/Logger.kt | 16 ++++ .../kirmanak/mealient/logging/LoggerImpl.kt | 75 +++++++++++++++++++ .../mealient/logging/LoggingModule.kt | 22 ++++++ settings.gradle.kts | 1 + 11 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 logging/.gitignore create mode 100644 logging/build.gradle.kts create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/Appender.kt create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogLevel.kt create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogcatAppender.kt create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/Logger.kt create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggerImpl.kt create mode 100644 logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggingModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3c580bc..5e1b57e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(project(":database")) implementation(project(":datastore")) + implementation(project(":logging")) implementation(libs.android.material.material) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeDataSourceImpl.kt index 53ca6b7..a3b063e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeDataSourceImpl.kt @@ -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, + 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 } } \ No newline at end of file diff --git a/logging/.gitignore b/logging/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/logging/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts new file mode 100644 index 0000000..518b450 --- /dev/null +++ b/logging/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Appender.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Appender.kt new file mode 100644 index 0000000..ea590bd --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Appender.kt @@ -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) + +} \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogLevel.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogLevel.kt new file mode 100644 index 0000000..824c69e --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogLevel.kt @@ -0,0 +1,3 @@ +package gq.kirmanak.mealient.logging + +enum class LogLevel { VERBOSE, DEBUG, INFO, WARNING, ERROR } \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogcatAppender.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogcatAppender.kt new file mode 100644 index 0000000..638e1d3 --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LogcatAppender.kt @@ -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 + } \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Logger.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Logger.kt new file mode 100644 index 0000000..66b8314 --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/Logger.kt @@ -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) +} \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggerImpl.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggerImpl.kt new file mode 100644 index 0000000..831f3a8 --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggerImpl.kt @@ -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+)+$") + } +} \ No newline at end of file diff --git a/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggingModule.kt b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggingModule.kt new file mode 100644 index 0000000..ac258d4 --- /dev/null +++ b/logging/src/main/kotlin/gq/kirmanak/mealient/logging/LoggingModule.kt @@ -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 +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 9b81aeb..a45a879 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ rootProject.name = "Mealient" include(":app") include(":database") include(":datastore") +include(":logging")