Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add logger with userId and clientId #2100

Merged
merged 7 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions cli/src/commonMain/kotlin/com/wire/kalium/cli/CLIApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.CoreLogger
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.featureFlags.KaliumConfigs
Expand All @@ -50,11 +51,12 @@ class CLIApplication : CliktCommand(allowMultipleSubcommands = true) {
)
}

if (logOutputFile != null) {
CoreLogger.setLoggingLevel(logLevel, fileLogger)
} else {
CoreLogger.setLoggingLevel(logLevel)
}
CoreLogger.init(
KaliumLogger.Config(
logLevel,
if (logOutputFile != null) listOf(fileLogger) else emptyList(),
)
)

currentContext.findObject<CoreLogic>()?.updateApiVersionsScheduler?.scheduleImmediateApiVersionUpdate()
Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,12 @@

package com.wire.kalium.cryptography

import co.touchlab.kermit.LogWriter
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.KaliumLogger

internal var kaliumLogger = KaliumLogger.disabled()

object CryptographyLogger {
fun setLoggingLevel(level: KaliumLogLevel, vararg logWriters: LogWriter = arrayOf()) {
kaliumLogger = KaliumLogger(
config = KaliumLogger.Config(
severity = level,
tag = "Crypto"
),
logWriters = logWriters
)
fun init(config: KaliumLogger.Config) {
kaliumLogger = KaliumLogger(config = config, tag = "Crypto")
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ core-crypto = "0.8.2"
core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1"
completeKotlin = "1.1.0"
desugar-jdk = "1.1.5"
kermit = "1.2.2"
kermit = "2.0.1"
detekt = "1.19.0"
agp = "7.4.1"
dokka = "1.8.20"
Expand Down
208 changes: 146 additions & 62 deletions logger/src/commonMain/kotlin/com/wire/kalium/logger/KaliumLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
package com.wire.kalium.logger

import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.LoggerConfig
import co.touchlab.kermit.MutableLoggerConfig
import co.touchlab.kermit.Severity
import co.touchlab.kermit.StaticConfig
import co.touchlab.kermit.platformLogWriter
import co.touchlab.kermit.Logger as KermitLogger

Expand Down Expand Up @@ -61,96 +62,179 @@ enum class KaliumLogLevel {
DISABLED
}

fun KaliumLogLevel.toMinSeverity(): Severity = when (this) {
KaliumLogLevel.VERBOSE -> Severity.Verbose
KaliumLogLevel.DEBUG -> Severity.Debug
KaliumLogLevel.INFO -> Severity.Info
KaliumLogLevel.WARN -> Severity.Warn
KaliumLogLevel.ERROR -> Severity.Error
KaliumLogLevel.DISABLED -> Severity.Assert
}

fun Severity.toKaliumLogLevel(): KaliumLogLevel = when (this) {
Severity.Verbose -> KaliumLogLevel.VERBOSE
Severity.Debug -> KaliumLogLevel.DEBUG
Severity.Info -> KaliumLogLevel.INFO
Severity.Warn -> KaliumLogLevel.WARN
Severity.Error -> KaliumLogLevel.ERROR
Severity.Assert -> KaliumLogLevel.DISABLED
}

/**
* the logWriter is to create a custom writer other than the existing log writers from kermit to intercept the logs
* in the android case we use it to write the logs on file
*
* Custom logger writer which uses multiplatform [KermitLogger] underneath to allow to customize log message or tag.
* @param config the [Config] object which contains the configuration of the logger
* @param tag the [Tag] object which identifies the source of the log message. Can combine multiple data and turns it
* into structured String used by the [KermitLogger] so that it can be parsed back again to the [Tag] object.
* To know more how it behaves and what are the possibilities, take a look at the [Tag] sealed class and its subtypes.
*/
class KaliumLogger(private val config: Config, vararg logWriters: LogWriter = arrayOf()) {
class KaliumLogger(
private val config: Config = Config.DEFAULT,
private val tag: Tag = Tag.Text("KaliumLogger")
saleniuk marked this conversation as resolved.
Show resolved Hide resolved
) {

private var kermitLogger: KermitLogger
constructor(
config: Config = Config.DEFAULT,
tag: String = "KaliumLogger"
) : this(config, Tag.Text(tag))
private val kermitLogger: KermitLogger = KermitLogger(
config = config.kermitConfig()
)

init {
kermitLogger = if (logWriters.isEmpty()) {
KermitLogger(
config = StaticConfig(
minSeverity = config.severityLevel, listOf(platformLogWriter())
),
tag = config.tag
)
} else {
KermitLogger(
config = StaticConfig(
minSeverity = config.severityLevel, logWriters.asList()
),
tag = config.tag
)
}
}
private fun tag(): String = tag.tagString()

val severity = config.severity
fun logLevel(): KaliumLogLevel = config.logLevel()

/**
* Creates a new logger with custom tag that replaces the old tag and allows to specify which specific app flow,
* one of [ApplicationFlow], the logs sent by this logger relate to.
* When the logger already contains [Tag.UserClientText] type of logs, then user-related tag data will still be included,
* and this featureId tag part will be added as a prefix, to keep the standard pattern of the tag: "tag[userId|clientId]".
* In this case it will become "featureId:featureName[userId|clientId]".
* When current type of tag is [Tag.Text], then it will just replace it with the new one: "featureId:featureName".
*/
@Suppress("unused")
fun withFeatureId(featureId: ApplicationFlow): KaliumLogger {
val currentLogger = this
currentLogger.kermitLogger = kermitLogger.withTag("featureId:${featureId.name.lowercase()}")
return currentLogger
}
fun withFeatureId(featureId: ApplicationFlow): KaliumLogger = KaliumLogger(
saleniuk marked this conversation as resolved.
Show resolved Hide resolved
config = config,
tag = "featureId:${featureId.name.lowercase()}".let {
when (tag) {
is Tag.Text -> Tag.Text(it)
is Tag.UserClientText -> Tag.UserClientText(it, tag.data)
}
}
)

/**
* Creates a new logger with custom tag that replaces the old tag and allows to add user-related data to the tag.
* When the logger already contains [Tag.UserClientText] type of tag, then user-related tag data part will be replaced,
* and if it contained already some text tag prefix part, then the same prefix will be also included in the new one,
* to keep the standard pattern of the tag: "tag[userId|clientId]".
*/
@Suppress("unused")
fun withUserDeviceData(data: () -> UserClientData): KaliumLogger = KaliumLogger(
saleniuk marked this conversation as resolved.
Show resolved Hide resolved
config = config,
tag = when (tag) {
is Tag.Text -> Tag.UserClientText(tag.text, data)
is Tag.UserClientText -> Tag.UserClientText(tag.prefix, data)
}
)

@Suppress("unused")
fun v(message: String, throwable: Throwable? = null) =
throwable?.let {
kermitLogger.v(message, throwable)
} ?: kermitLogger.v(message)
fun v(message: String, throwable: Throwable? = null, tag: String = this.tag()) =
kermitLogger.v(message, throwable, tag)

@Suppress("unused")
fun d(message: String, throwable: Throwable? = null) =
throwable?.let {
kermitLogger.d(message, throwable)
} ?: kermitLogger.d(message)
fun d(message: String, throwable: Throwable? = null, tag: String = this.tag()) =
kermitLogger.d(message, throwable, tag)

@Suppress("unused")
fun i(message: String, throwable: Throwable? = null) =
throwable?.let {
kermitLogger.i(message, throwable)
} ?: kermitLogger.i(message)
fun i(message: String, throwable: Throwable? = null, tag: String = this.tag()) =
kermitLogger.i(message, throwable, tag)

@Suppress("unused")
fun w(message: String, throwable: Throwable? = null) =
throwable?.let {
kermitLogger.w(message, throwable)
} ?: kermitLogger.w(message)
fun w(message: String, throwable: Throwable? = null, tag: String = this.tag()) =
kermitLogger.w(message, throwable, tag)

@Suppress("unused")
fun e(message: String, throwable: Throwable? = null) =
throwable?.let {
kermitLogger.e(message, throwable)
} ?: kermitLogger.e(message)
fun e(message: String, throwable: Throwable? = null, tag: String = this.tag()) =
kermitLogger.e(message, throwable, tag)

class Config(
val severity: KaliumLogLevel,
val tag: String
val initialLevel: KaliumLogLevel,
val initialLogWriterList: List<LogWriter> = listOf(platformLogWriter())
) {
val severityLevel: Severity = when (severity) {
KaliumLogLevel.VERBOSE -> Severity.Verbose
KaliumLogLevel.DEBUG -> Severity.Debug
KaliumLogLevel.INFO -> Severity.Info
KaliumLogLevel.WARN -> Severity.Warn
KaliumLogLevel.ERROR -> Severity.Error
KaliumLogLevel.DISABLED -> Severity.Assert
private val mutableKermitConfig = object : MutableLoggerConfig {
override var logWriterList: List<LogWriter> = initialLogWriterList
override var minSeverity: Severity = initialLevel.toMinSeverity()
}

fun logLevel(): KaliumLogLevel = mutableKermitConfig.minSeverity.toKaliumLogLevel()

fun kermitConfig(): LoggerConfig = mutableKermitConfig

@Suppress("unused")
fun setLogLevel(level: KaliumLogLevel) {
mutableKermitConfig.minSeverity = level.toMinSeverity()
}

@Suppress("unused")
fun setLogWriterList(logWriterList: List<LogWriter>) {
mutableKermitConfig.logWriterList = logWriterList
}

companion object {
val DISABLED = Config(
severity = KaliumLogLevel.DISABLED,
tag = "KaliumLogger"
val DEFAULT = disabled()
fun disabled(): Config = Config(
initialLevel = KaliumLogLevel.DISABLED,
initialLogWriterList = listOf(platformLogWriter()),
)
}
}

/**
* Defined types of tags that can be provided to the [KaliumLogger] as a String text.
*/
sealed class Tag {
abstract fun tagString(): String

/**
* Simple String text tag.
*/
data class Text(val text: String) : Tag() {
override fun tagString(): String = text
}

/**
* User-related data tag. Contains String text prefix and [UserClientData] (userId and clientId).
* It will be added to the tag in the standard pattern: "tag[userId|clientId]",
* so it can be combined with a [Tag.Text] type by adding the tag text as a prefix in this one.
*/
data class UserClientText(val prefix: String, val data: () -> UserClientData) : Tag() {
override fun tagString(): String = data().let { "$prefix[${it.userId}|${it.clientId}]" }
}
}

data class UserClientData(val userId: String, val clientId: String) {

companion object {
private val regex = Regex("^.*\\[.+\\|.+\\]\$")

/**
* Parses the user-related data from the String tag in the standard pattern: "tag[userId|clientId]".
* Returns null if the tag doesn't match the pattern, which means it does not contain user-related data.
*/
@Suppress("unused")
fun getFromTag(tag: String): UserClientData? =
if (tag.matches(regex)) {
tag.substringAfterLast("[").substringBefore("]").split("|")
.let { data -> UserClientData(data[0], data[1]) }
} else null
}
}

companion object {
fun disabled(): KaliumLogger = KaliumLogger(
config = Config.DISABLED
config = Config.disabled(),
tag = "KaliumLogger"
)

enum class ApplicationFlow {
Expand Down
33 changes: 12 additions & 21 deletions logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,29 @@

package com.wire.kalium.logic

import co.touchlab.kermit.LogWriter
import com.wire.kalium.cryptography.CryptographyLogger
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.network.NetworkLogger
import com.wire.kalium.network.NetworkUtilLogger
import com.wire.kalium.persistence.PersistenceLogger

private var kaliumLoggerConfig = KaliumLogger.Config.disabled()
internal var kaliumLogger = KaliumLogger.disabled()
internal var callingLogger = KaliumLogger.disabled()

object CoreLogger {
fun setLoggingLevel(level: KaliumLogLevel, vararg logWriters: LogWriter = arrayOf()) {
kaliumLogger = KaliumLogger(
config = KaliumLogger.Config(
severity = level,
tag = "CoreLogic"
),
logWriters = logWriters
)

callingLogger = KaliumLogger(
config = KaliumLogger.Config(
severity = level,
tag = "Calling"
),
logWriters = logWriters
)

NetworkLogger.setLoggingLevel(level = level, logWriters = logWriters)
NetworkUtilLogger.setLoggingLevel(level = level, logWriters = logWriters)
CryptographyLogger.setLoggingLevel(level = level, logWriters = logWriters)
PersistenceLogger.setLoggingLevel(level = level, logWriters = logWriters)
fun init(config: KaliumLogger.Config) {
kaliumLoggerConfig = config
kaliumLogger = KaliumLogger(config = kaliumLoggerConfig, tag = "CoreLogic")
callingLogger = KaliumLogger(config = kaliumLoggerConfig, tag = "Calling")
NetworkLogger.init(config = config)
NetworkUtilLogger.init(config = config)
CryptographyLogger.init(config = config)
PersistenceLogger.init(config = config)
}
fun setLoggingLevel(level: KaliumLogLevel) {
kaliumLoggerConfig.setLogLevel(level)
}
}
Loading
Loading