Updated on 2026-08-14
This commit is contained in:
parent
91c2fb2d58
commit
d718cf557d
18 changed files with 1135 additions and 280 deletions
|
|
@ -23,10 +23,6 @@ dependencies {
|
|||
implementation(deps.jodatime)
|
||||
// endregion
|
||||
|
||||
// region Logging
|
||||
implementation(deps.kermit)
|
||||
// endregion
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
/**
|
||||
* Common contract for application loggers.
|
||||
*/
|
||||
internal interface BaseLogger {
|
||||
fun v(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
fun d(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
fun i(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
fun w(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
fun e(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
fun a(messageString: String, throwable: Throwable? = null, shouldSanitize: Boolean = true)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Resolves a log tag from the call site's class name when the caller didn't supply one
|
||||
* via [TangemLogger.withTag]. Used by [TangemLogger.write] before dispatching to writers.
|
||||
*/
|
||||
internal object LogTagResolver {
|
||||
|
||||
private const val FALLBACK_TAG = "TangemAppLogger"
|
||||
|
||||
private val ANONYMOUS_CLASS_REGEX: Pattern = Pattern.compile("(\\$\\d+)+$")
|
||||
|
||||
private val FQCN_IGNORE = setOf(
|
||||
LogTagResolver::class.java.name,
|
||||
TangemLogger::class.java.name,
|
||||
TangemLogger.TaggedLogger::class.java.name,
|
||||
// Synthetic class generated for BaseLogger's default-arg trampolines (d$default, etc.).
|
||||
// Without this, every call that omits default args resolves to BaseLogger.DefaultImpls.
|
||||
"${BaseLogger::class.java.name}\$DefaultImpls",
|
||||
)
|
||||
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
fun resolveTag(): String {
|
||||
val element = Throwable().stackTrace.firstOrNull { it.className !in FQCN_IGNORE }
|
||||
?: return FALLBACK_TAG
|
||||
var tag = element.className.substringAfterLast('.')
|
||||
val matcher = ANONYMOUS_CLASS_REGEX.matcher(tag)
|
||||
if (matcher.find()) {
|
||||
tag = matcher.replaceAll("")
|
||||
}
|
||||
return tag
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
enum class Severity {
|
||||
Verbose,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Assert,
|
||||
}
|
||||
|
|
@ -1,63 +1,173 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
* Application-level logger that wraps Kermit [Logger] with the same API.
|
||||
* All modules should use [TangemLogger] instead of importing Kermit directly.
|
||||
* Application-level logger
|
||||
*/
|
||||
object TangemLogger {
|
||||
object TangemLogger : BaseLogger {
|
||||
|
||||
fun v(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.v(messageString, throwable)
|
||||
private val logWriters = CopyOnWriteArrayList<LogWriter>()
|
||||
|
||||
fun setLogWriters(writers: List<LogWriter>) {
|
||||
logWriters.clear()
|
||||
logWriters.addAll(writers)
|
||||
}
|
||||
|
||||
fun d(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.d(messageString, throwable)
|
||||
fun addLogWriter(writer: LogWriter) {
|
||||
logWriters.add(writer)
|
||||
}
|
||||
|
||||
fun i(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.i(messageString, throwable)
|
||||
override fun v(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Verbose,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
fun w(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.w(messageString, throwable)
|
||||
override fun d(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Debug,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
fun e(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.e(messageString, throwable)
|
||||
override fun i(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Info,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
fun a(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.a(messageString, throwable)
|
||||
override fun w(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Warn,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun e(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Error,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun a(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Assert,
|
||||
tag = null,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
fun withTag(tag: String): TaggedLogger = TaggedLogger(tag)
|
||||
|
||||
class TaggedLogger internal constructor(private val tag: String) {
|
||||
|
||||
fun v(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).v(messageString, throwable)
|
||||
}
|
||||
|
||||
fun d(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).d(messageString, throwable)
|
||||
}
|
||||
|
||||
fun i(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).i(messageString, throwable)
|
||||
}
|
||||
|
||||
fun w(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).w(messageString, throwable)
|
||||
}
|
||||
|
||||
fun e(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).e(messageString, throwable)
|
||||
}
|
||||
|
||||
fun a(messageString: String, throwable: Throwable? = null) {
|
||||
Logger.withTag(tag).a(messageString, throwable)
|
||||
private fun write(
|
||||
severity: Severity,
|
||||
tag: String?,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
shouldSanitize: Boolean,
|
||||
) {
|
||||
val resolvedTag = tag ?: LogTagResolver.resolveTag()
|
||||
logWriters.forEach { writer ->
|
||||
if (writer.isLoggable(severity, resolvedTag)) {
|
||||
writer.write(
|
||||
severity = severity,
|
||||
tag = resolvedTag,
|
||||
message = message,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TaggedLogger internal constructor(private val tag: String) : BaseLogger {
|
||||
|
||||
override fun v(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Verbose,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun d(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Debug,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun i(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Info,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun w(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Warn,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun e(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Error,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
|
||||
override fun a(messageString: String, throwable: Throwable?, shouldSanitize: Boolean) {
|
||||
write(
|
||||
severity = Severity.Assert,
|
||||
tag = tag,
|
||||
message = messageString,
|
||||
throwable = throwable,
|
||||
shouldSanitize = shouldSanitize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface LogWriter {
|
||||
|
||||
fun isLoggable(severity: Severity, tag: String): Boolean = true
|
||||
|
||||
fun write(severity: Severity, tag: String, message: String, throwable: Throwable?, shouldSanitize: Boolean)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class LogTagResolverTest {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
// Clean up shared TangemLogger state used in some cases
|
||||
TangemLogger.setLogWriters(emptyList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag returns the simple class name of the direct caller`() {
|
||||
// Act
|
||||
val tag = LogTagResolver.resolveTag()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(tag).isEqualTo("LogTagResolverTest")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag does not include package qualifier`() {
|
||||
// Act
|
||||
val tag = LogTagResolver.resolveTag()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(tag).doesNotContain(".")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag never returns its own class name`() {
|
||||
// Act
|
||||
val tag = LogTagResolver.resolveTag()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(tag).isNotEqualTo("LogTagResolver")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag skips TangemLogger frames when invoked through it`() {
|
||||
// Arrange
|
||||
var capturedTag: String? = null
|
||||
val writer = object : TangemLogger.LogWriter {
|
||||
override fun write(
|
||||
severity: Severity,
|
||||
tag: String,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
shouldSanitize: Boolean,
|
||||
) {
|
||||
capturedTag = tag
|
||||
}
|
||||
}
|
||||
TangemLogger.setLogWriters(listOf(writer))
|
||||
|
||||
// Act
|
||||
TangemLogger.d("via TangemLogger")
|
||||
|
||||
// Assert — TangemLogger and LogTagResolver are filtered, leaving the test class
|
||||
Truth.assertThat(capturedTag).isEqualTo("LogTagResolverTest")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag is bypassed by TaggedLogger when an explicit tag is supplied`() {
|
||||
// Arrange
|
||||
var capturedTag: String? = null
|
||||
val writer = object : TangemLogger.LogWriter {
|
||||
override fun write(
|
||||
severity: Severity,
|
||||
tag: String,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
shouldSanitize: Boolean,
|
||||
) {
|
||||
capturedTag = tag
|
||||
}
|
||||
}
|
||||
TangemLogger.setLogWriters(listOf(writer))
|
||||
|
||||
// Act
|
||||
TangemLogger.withTag("ExplicitTag").d("hi")
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(capturedTag).isEqualTo("ExplicitTag")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolveTag returns a non-empty string`() {
|
||||
// Act
|
||||
val tag = LogTagResolver.resolveTag()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(tag).isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
package com.tangem.utils.logging
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TangemLoggerTest {
|
||||
|
||||
private lateinit var writer: TangemLogger.LogWriter
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
writer = mockk(relaxed = true)
|
||||
every { writer.isLoggable(any(), any()) } returns true
|
||||
TangemLogger.setLogWriters(listOf(writer))
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
// Reset singleton state to avoid cross-test pollution
|
||||
TangemLogger.setLogWriters(emptyList())
|
||||
}
|
||||
|
||||
// region Severity dispatch
|
||||
|
||||
@Test
|
||||
fun `v dispatches Verbose severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.v("verbose message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Verbose, any(), "verbose message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `d dispatches Debug severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.d("debug message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Debug, any(), "debug message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `i dispatches Info severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.i("info message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Info, any(), "info message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `w dispatches Warn severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.w("warn message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Warn, any(), "warn message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `e dispatches Error severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.e("error message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Error, any(), "error message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a dispatches Assert severity to writer`() {
|
||||
// Act
|
||||
TangemLogger.a("assert message")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Assert, any(), "assert message", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Throwable & shouldSanitize propagation
|
||||
|
||||
@Test
|
||||
fun `throwable parameter is forwarded to writer`() {
|
||||
// Arrange
|
||||
val throwable = IllegalStateException("boom")
|
||||
|
||||
// Act
|
||||
TangemLogger.e("error", throwable)
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Error, any(), "error", throwable, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shouldSanitize flag is forwarded to writer`() {
|
||||
// Act
|
||||
TangemLogger.w("not checked", shouldSanitize = false)
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Warn, any(), "not checked", null, false)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region setLogWriters / addLogWriter
|
||||
|
||||
@Test
|
||||
fun `setLogWriters replaces previously registered writers`() {
|
||||
// Arrange
|
||||
val previous: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { previous.isLoggable(any(), any()) } returns true
|
||||
val replacement: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { replacement.isLoggable(any(), any()) } returns true
|
||||
|
||||
TangemLogger.setLogWriters(listOf(previous))
|
||||
TangemLogger.setLogWriters(listOf(replacement))
|
||||
|
||||
// Act
|
||||
TangemLogger.i("after replace")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 0) { previous.write(any(), any(), any(), any(), any()) }
|
||||
verify(exactly = 1) {
|
||||
replacement.write(Severity.Info, any(), "after replace", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `addLogWriter appends without removing existing writers`() {
|
||||
// Arrange
|
||||
val first: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { first.isLoggable(any(), any()) } returns true
|
||||
val second: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { second.isLoggable(any(), any()) } returns true
|
||||
|
||||
TangemLogger.setLogWriters(listOf(first))
|
||||
TangemLogger.addLogWriter(second)
|
||||
|
||||
// Act
|
||||
TangemLogger.d("broadcast")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) { first.write(Severity.Debug, any(), "broadcast", null, true) }
|
||||
verify(exactly = 1) { second.write(Severity.Debug, any(), "broadcast", null, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setLogWriters with empty list silences all output`() {
|
||||
// Arrange
|
||||
TangemLogger.setLogWriters(emptyList())
|
||||
|
||||
// Act
|
||||
TangemLogger.i("nobody listening")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 0) { writer.write(any(), any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region isLoggable filtering
|
||||
|
||||
@Test
|
||||
fun `write is skipped when isLoggable returns false`() {
|
||||
// Arrange
|
||||
every { writer.isLoggable(any(), any()) } returns false
|
||||
|
||||
// Act
|
||||
TangemLogger.w("filtered out")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) { writer.isLoggable(Severity.Warn, any()) }
|
||||
verify(exactly = 0) { writer.write(any(), any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `each writer is filtered independently by its own isLoggable`() {
|
||||
// Arrange
|
||||
val accepting: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { accepting.isLoggable(any(), any()) } returns true
|
||||
val rejecting: TangemLogger.LogWriter = mockk(relaxed = true)
|
||||
every { rejecting.isLoggable(any(), any()) } returns false
|
||||
|
||||
TangemLogger.setLogWriters(listOf(accepting, rejecting))
|
||||
|
||||
// Act
|
||||
TangemLogger.i("partial")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) { accepting.write(Severity.Info, any(), "partial", null, true) }
|
||||
verify(exactly = 0) { rejecting.write(any(), any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LogWriter isLoggable defaults to true`() {
|
||||
// Arrange
|
||||
val realWriter = object : TangemLogger.LogWriter {
|
||||
override fun write(
|
||||
severity: Severity,
|
||||
tag: String,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
shouldSanitize: Boolean,
|
||||
) = Unit
|
||||
}
|
||||
|
||||
// Act + Assert
|
||||
Severity.entries.forEach { severity ->
|
||||
Truth.assertThat(realWriter.isLoggable(severity, "anyTag")).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Tag resolution
|
||||
|
||||
@Test
|
||||
fun `resolved tag falls back to caller class name when no tag is provided`() {
|
||||
// Act
|
||||
TangemLogger.d("no tag")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Debug, "TangemLoggerTest", "no tag", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `withTag returns a TaggedLogger that uses the supplied tag`() {
|
||||
// Arrange
|
||||
val tagged = TangemLogger.withTag("MyFeature")
|
||||
|
||||
// Act
|
||||
tagged.i("hello")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) {
|
||||
writer.write(Severity.Info, "MyFeature", "hello", null, true)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region TaggedLogger
|
||||
|
||||
@Test
|
||||
fun `TaggedLogger dispatches each severity with its tag, throwable and shouldSanitize flag`() {
|
||||
// Arrange
|
||||
val tagged = TangemLogger.withTag("Tag")
|
||||
val throwable = RuntimeException("oops")
|
||||
|
||||
// Act
|
||||
tagged.v("v")
|
||||
tagged.d("d")
|
||||
tagged.i("i")
|
||||
tagged.w("w")
|
||||
tagged.e("e", throwable)
|
||||
tagged.a("a", shouldSanitize = false)
|
||||
|
||||
// Assert
|
||||
verifyOrder {
|
||||
writer.write(Severity.Verbose, "Tag", "v", null, true)
|
||||
writer.write(Severity.Debug, "Tag", "d", null, true)
|
||||
writer.write(Severity.Info, "Tag", "i", null, true)
|
||||
writer.write(Severity.Warn, "Tag", "w", null, true)
|
||||
writer.write(Severity.Error, "Tag", "e", throwable, true)
|
||||
writer.write(Severity.Assert, "Tag", "a", null, false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TaggedLogger respects writer isLoggable filtering`() {
|
||||
// Arrange
|
||||
every { writer.isLoggable(any(), any()) } returns false
|
||||
val tagged = TangemLogger.withTag("Filtered")
|
||||
|
||||
// Act
|
||||
tagged.e("ignored")
|
||||
|
||||
// Assert
|
||||
verify(exactly = 1) { writer.isLoggable(Severity.Error, "Filtered") }
|
||||
verify(exactly = 0) { writer.write(any(), any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue