Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
32
core/logging/impl-console/build.gradle.kts
Normal file
32
core/logging/impl-console/build.gradle.kts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
|
||||
plugins {
|
||||
id(ThunderbirdPlugins.Library.kmp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.thunderbird.core.logging.console"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
applyDefaultHierarchyTemplate {
|
||||
common {
|
||||
group("commonJvm") {
|
||||
withAndroidTarget()
|
||||
withJvm()
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
val commonJvmMain by getting
|
||||
|
||||
commonMain.dependencies {
|
||||
implementation(projects.core.logging.api)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.timber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import net.thunderbird.core.logging.LogEvent
|
||||
import net.thunderbird.core.logging.LogLevel
|
||||
import timber.log.Timber
|
||||
|
||||
actual fun ConsoleLogSink(level: LogLevel): ConsoleLogSink = AndroidConsoleLogSink(level)
|
||||
|
||||
private class AndroidConsoleLogSink(
|
||||
override val level: LogLevel,
|
||||
) : ConsoleLogSink {
|
||||
|
||||
override fun log(event: LogEvent) {
|
||||
val timber = event.tag
|
||||
?.let { Timber.tag(it) }
|
||||
?: Timber.tag(event.composeTag(ignoredClasses = IGNORE_CLASSES) ?: this::class.java.name)
|
||||
|
||||
when (event.level) {
|
||||
LogLevel.VERBOSE -> timber.v(event.throwable, event.message)
|
||||
LogLevel.DEBUG -> timber.d(event.throwable, event.message)
|
||||
LogLevel.INFO -> timber.i(event.throwable, event.message)
|
||||
LogLevel.WARN -> timber.w(event.throwable, event.message)
|
||||
LogLevel.ERROR -> timber.e(event.throwable, event.message)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IGNORE_CLASSES = setOf(
|
||||
Timber::class.java.name,
|
||||
Timber.Forest::class.java.name,
|
||||
Timber.Tree::class.java.name,
|
||||
Timber.DebugTree::class.java.name,
|
||||
AndroidConsoleLogSink::class.java.name,
|
||||
// Add other classes to ignore if needed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import android.util.Log
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlin.test.Test
|
||||
import net.thunderbird.core.logging.LogEvent
|
||||
import net.thunderbird.core.logging.LogLevel
|
||||
import timber.log.Timber
|
||||
|
||||
class ConsoleLogSinkTest {
|
||||
|
||||
@Test
|
||||
fun shouldHaveCorrectLogLevel() {
|
||||
// Arrange
|
||||
val testSubject = ConsoleLogSink(LogLevel.INFO)
|
||||
|
||||
// Act & Assert
|
||||
assertThat(testSubject.level).isEqualTo(LogLevel.INFO)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldLogMessages() {
|
||||
// Arrange
|
||||
val testTree = TestTree()
|
||||
Timber.plant(testTree)
|
||||
val eventVerbose = LogEvent(
|
||||
level = LogLevel.VERBOSE,
|
||||
tag = "TestTag",
|
||||
message = "This is a verbose message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
val eventDebug = LogEvent(
|
||||
level = LogLevel.DEBUG,
|
||||
tag = "TestTag",
|
||||
message = "This is a debug message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
val eventInfo = LogEvent(
|
||||
level = LogLevel.INFO,
|
||||
tag = "TestTag",
|
||||
message = "This is a info message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
val eventWarn = LogEvent(
|
||||
level = LogLevel.WARN,
|
||||
tag = "TestTag",
|
||||
message = "This is a warning message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
val eventError = LogEvent(
|
||||
level = LogLevel.ERROR,
|
||||
tag = "TestTag",
|
||||
message = "This is an error message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
|
||||
val testSubject = ConsoleLogSink(LogLevel.VERBOSE)
|
||||
|
||||
// Act
|
||||
testSubject.log(eventVerbose)
|
||||
testSubject.log(eventDebug)
|
||||
testSubject.log(eventInfo)
|
||||
testSubject.log(eventWarn)
|
||||
testSubject.log(eventError)
|
||||
|
||||
// Assert
|
||||
assertThat(testTree.events).hasSize(5)
|
||||
assertThat(testTree.events[0]).isEqualTo(eventVerbose)
|
||||
assertThat(testTree.events[1]).isEqualTo(eventDebug)
|
||||
assertThat(testTree.events[2]).isEqualTo(eventInfo)
|
||||
assertThat(testTree.events[3]).isEqualTo(eventWarn)
|
||||
assertThat(testTree.events[4]).isEqualTo(eventError)
|
||||
}
|
||||
|
||||
class TestTree : Timber.DebugTree() {
|
||||
|
||||
val events = mutableListOf<LogEvent>()
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
events.add(LogEvent(mapPriorityToLogLevel(priority), tag, message, t, 0L))
|
||||
}
|
||||
|
||||
private fun mapPriorityToLogLevel(priority: Int): LogLevel {
|
||||
return when (priority) {
|
||||
Log.VERBOSE -> LogLevel.VERBOSE
|
||||
Log.DEBUG -> LogLevel.DEBUG
|
||||
Log.INFO -> LogLevel.INFO
|
||||
Log.WARN -> LogLevel.WARN
|
||||
Log.ERROR -> LogLevel.ERROR
|
||||
else -> throw IllegalArgumentException("Unknown log priority: $priority")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import net.thunderbird.core.logging.DefaultLogger
|
||||
import net.thunderbird.core.logging.LogEvent
|
||||
import net.thunderbird.core.logging.Logger
|
||||
|
||||
/**
|
||||
* Composes a tag for the given [LogEvent].
|
||||
*
|
||||
* If the event has a tag, it is used; otherwise, a tag is extracted from the stack trace.
|
||||
* The tag is processed using the [processTag] method before being returned.
|
||||
*
|
||||
* @receiver The [LogEvent] to compose a tag for.
|
||||
* @param ignoredClasses The set of Class full name to be ignored.
|
||||
* @param processTag Processes a tag before it is used for logging.
|
||||
* @return The composed tag, or null if no tag could be determined.
|
||||
*/
|
||||
internal fun LogEvent.composeTag(
|
||||
ignoredClasses: Set<String>,
|
||||
processTag: (String) -> String? = { it },
|
||||
): String? {
|
||||
// If a tag is provided, use it; otherwise, extract it from the stack trace
|
||||
val rawTag = tag ?: extractTagFromStackTrace(ignoredClasses)
|
||||
// Process the tag before returning it
|
||||
return rawTag?.let { processTag(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a tag from the stack trace.
|
||||
*
|
||||
* @return The extracted tag, or null if no suitable tag could be found.
|
||||
*/
|
||||
private fun extractTagFromStackTrace(ignoredClasses: Set<String>): String? {
|
||||
// Some classes are not available to this module, and we don't want
|
||||
// to add the dependency just for class filtering.
|
||||
val ignoredClasses = ignoredClasses + setOf(
|
||||
"net.thunderbird.core.logging.console.ComposeLogTagKt",
|
||||
"net.thunderbird.core.logging.composite.DefaultCompositeLogSink",
|
||||
"net.thunderbird.core.logging.legacy.Log",
|
||||
Logger::class.java.name,
|
||||
DefaultLogger::class.java.name,
|
||||
)
|
||||
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
val stackTrace = Throwable().stackTrace
|
||||
|
||||
return stackTrace
|
||||
.firstOrNull { element ->
|
||||
ignoredClasses.none { element.className.startsWith(it) }
|
||||
}
|
||||
?.let(::createStackElementTag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tag from a stack trace element.
|
||||
*
|
||||
* @param element The stack trace element to create a tag from.
|
||||
* @return The created tag.
|
||||
*/
|
||||
private fun createStackElementTag(element: StackTraceElement): String {
|
||||
var tag = element.className.substringAfterLast('.')
|
||||
val regex = "(\\$\\d+)+$".toRegex()
|
||||
if (regex.containsMatchIn(input = tag)) {
|
||||
tag = regex.replace(input = tag, replacement = "")
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import net.thunderbird.core.logging.LogLevel
|
||||
import net.thunderbird.core.logging.LogSink
|
||||
|
||||
/**
|
||||
* A [LogSink] implementation that logs messages to the console.
|
||||
*
|
||||
* This sink uses the platform-specific implementations to handle logging.
|
||||
*
|
||||
* @param level The minimum [LogLevel] for messages to be logged.
|
||||
*/
|
||||
interface ConsoleLogSink : LogSink
|
||||
|
||||
/**
|
||||
* Creates a [ConsoleLogSink] with the specified log level.
|
||||
*
|
||||
* @param level The minimum [LogLevel] for messages to be logged.
|
||||
* @return A new instance of [ConsoleLogSink].
|
||||
*/
|
||||
expect fun ConsoleLogSink(
|
||||
level: LogLevel = LogLevel.INFO,
|
||||
): ConsoleLogSink
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import net.thunderbird.core.logging.LogEvent
|
||||
import net.thunderbird.core.logging.LogLevel
|
||||
|
||||
actual fun ConsoleLogSink(level: LogLevel): ConsoleLogSink = JvmConsoleLogSink(level)
|
||||
|
||||
private class JvmConsoleLogSink(
|
||||
override val level: LogLevel,
|
||||
) : ConsoleLogSink {
|
||||
|
||||
override fun log(event: LogEvent) {
|
||||
println("[$level] ${composeMessage(event)}")
|
||||
event.throwable?.printStackTrace()
|
||||
}
|
||||
|
||||
private fun composeMessage(event: LogEvent): String {
|
||||
val tag = event.tag ?: event.composeTag(ignoredClasses = IGNORE_CLASSES)
|
||||
return if (tag != null) {
|
||||
"[$tag] ${event.message}"
|
||||
} else {
|
||||
event.message
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IGNORE_CLASSES = setOf(
|
||||
JvmConsoleLogSink::class.java.name,
|
||||
// Add other classes to ignore if needed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package net.thunderbird.core.logging.console
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import net.thunderbird.core.logging.LogEvent
|
||||
import net.thunderbird.core.logging.LogLevel
|
||||
|
||||
class ConsoleLogSinkTest {
|
||||
|
||||
@Test
|
||||
fun shouldHaveCorrectLogLevel() {
|
||||
// Arrange
|
||||
val testSubject = ConsoleLogSink(LogLevel.INFO)
|
||||
|
||||
// Act & Assert
|
||||
assertEquals(LogLevel.INFO, testSubject.level)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldLogMessages() {
|
||||
// Arrange
|
||||
val originalOut = System.out
|
||||
val outContent = ByteArrayOutputStream()
|
||||
System.setOut(PrintStream(outContent))
|
||||
|
||||
try {
|
||||
val eventInfo = LogEvent(
|
||||
level = LogLevel.INFO,
|
||||
tag = "TestTag",
|
||||
message = "This is an info message",
|
||||
throwable = null,
|
||||
timestamp = 0L,
|
||||
)
|
||||
|
||||
val testSubject = ConsoleLogSink(LogLevel.VERBOSE)
|
||||
|
||||
// Act
|
||||
testSubject.log(eventInfo)
|
||||
|
||||
// Assert
|
||||
val output = outContent.toString().trim()
|
||||
println("[DEBUG_LOG] Actual output: '$output'")
|
||||
|
||||
// The expected format is: [VERBOSE] [TestTag] This is an info message
|
||||
// Note: The log level in the output is the sink's level (VERBOSE), not the event's level (INFO)
|
||||
val expectedOutput = "[VERBOSE] [TestTag] This is an info message"
|
||||
println("[DEBUG_LOG] Expected output: '$expectedOutput'")
|
||||
|
||||
assertEquals(expectedOutput, output)
|
||||
} finally {
|
||||
System.setOut(originalOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue