Skip to content
Draft
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
48 changes: 35 additions & 13 deletions app/src/main/kotlin/com/wire/android/WireApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
import androidx.work.WorkManager
import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.platformLogWriter
import com.wire.android.analytics.ObserveCurrentSessionAnalyticsUseCase
import com.wire.android.datastore.GlobalDataStore
Expand Down Expand Up @@ -178,7 +179,7 @@
}

private fun initializeMinimalLogging() {
val config = minimalLoggerConfig()
val config = minimalLoggerConfig(listOf(logFileWriter.value.logWriter))

Check warning on line 182 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L182

Added line #L182 was not covered by tests
AppLogger.init(config)
CoreLogger.init(config)
}
Expand Down Expand Up @@ -346,15 +347,19 @@
ExternalLoggerManager.initDatadogLogger(applicationContext)

val isLoggingEnabled = globalDataStore.value.isLoggingEnabled().firstOrNull() == true
val config = fullLoggerConfig(isLoggingEnabled)

AppLogger.init(config)
CoreLogger.init(config)
val fileWriter = logFileWriter.value

Check warning on line 350 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L350

Added line #L350 was not covered by tests

if (isLoggingEnabled) {
logFileWriter.value.start()
fileWriter.start()

Check warning on line 353 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L353

Added line #L353 was not covered by tests
}

val config = fullLoggerConfig(
isLoggingEnabled = isLoggingEnabled,
additionalLogWriters = listOf(fileWriter.logWriter)

Check warning on line 358 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L356-L358

Added lines #L356 - L358 were not covered by tests
)
AppLogger.init(config)
CoreLogger.init(config)

Check warning on line 361 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L360-L361

Added lines #L360 - L361 were not covered by tests

appLogger.i("Logger initialized after splash")
logDeviceInformation()
initializeAnonymousAnalytics()
Expand Down Expand Up @@ -450,25 +455,42 @@

override fun onLowMemory() {
super.onLowMemory()
appLogger.w("onLowMemory called - Stopping logging, buckling the seatbelt and hoping for the best!")
appLogger.w("onLowMemory called - Flushing pending logs")

Check warning on line 458 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L458

Added line #L458 was not covered by tests
globalAppScope.launch {
logFileWriter.value.stop()
flushLogsAfterLowMemory(logFileWriter.value) { error ->
Log.e(TAG, "Failed to flush logs after onLowMemory", error)

Check warning on line 461 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L460-L461

Added lines #L460 - L461 were not covered by tests
}
}
}

internal companion object {
fun minimalLoggerConfig() = KaliumLogger.Config(
@Suppress("TooGenericExceptionCaught")
suspend fun flushLogsAfterLowMemory(
logFileWriter: LogFileWriter,
reportFailure: (Exception) -> Unit
) {
try {
logFileWriter.forceFlush()
} catch (e: Exception) {
reportFailure(e)
}
}

fun minimalLoggerConfig(additionalLogWriters: List<LogWriter> = emptyList()) = KaliumLogger.Config(
KaliumLogLevel.WARN,
listOf(platformLogWriter())
listOf(platformLogWriter()) + additionalLogWriters
)

fun fullLoggerConfig(isLoggingEnabled: Boolean) = if (isLoggingEnabled) {
fun fullLoggerConfig(
isLoggingEnabled: Boolean,
additionalLogWriters: List<LogWriter> = emptyList()
) = if (isLoggingEnabled) {
KaliumLogger.Config(
KaliumLogLevel.VERBOSE,
listOf(DataDogLogger, platformLogWriter())
listOf(DataDogLogger, platformLogWriter()) + additionalLogWriters
)
} else {
minimalLoggerConfig()
minimalLoggerConfig(additionalLogWriters)
}

enum class MemoryLevel(val level: Int) {
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/kotlin/com/wire/android/di/LogWriterModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
package com.wire.android.di

import android.content.Context
import com.wire.android.BuildConfig
import com.wire.android.util.logging.LogFileWriter
import com.wire.android.util.logging.LogFileWriterImpl
import com.wire.android.util.logging.LogFileWriterFactory
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.SingleIn

@BindingContainer
Expand All @@ -33,6 +34,9 @@ class LogWriterModule {
@Provides
fun provideKaliumFileWriter(@ApplicationContext context: Context): LogFileWriter {
val logsDirectory = LogFileWriter.logsDirectory(context)
return LogFileWriterImpl(logsDirectory)
return LogFileWriterFactory.create(
logsDirectory = logsDirectory,
usePlatformIndependentFileLogger = BuildConfig.USE_PLATFORM_INDEPENDENT_FILE_LOGGER
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@
}

fun deleteLogs() {
logFileWriter.deleteAllLogFiles()
viewModelScope.launch {
logFileWriter.deleteAllLogFiles()

Check warning on line 66 in app/src/main/kotlin/com/wire/android/ui/debug/LogManagementViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/LogManagementViewModel.kt#L65-L66

Added lines #L65 - L66 were not covered by tests
}
}

fun flushLogs(): Deferred<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@
}

fun deleteLogs() {
logFileWriter.deleteAllLogFiles()
viewModelScope.launch {
logFileWriter.deleteAllLogFiles()

Check warning on line 85 in app/src/main/kotlin/com/wire/android/ui/debug/UserDebugViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/UserDebugViewModel.kt#L84-L85

Added lines #L84 - L85 were not covered by tests
}
}

fun flushLogs(): Deferred<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.wire.android.util.logging

import android.content.Context
import co.touchlab.kermit.LogWriter
import java.io.File

/**
Expand All @@ -32,6 +33,11 @@ interface LogFileWriter {
*/
val activeLoggingFile: File

/**
* Kermit sink that writes log entries into [activeLoggingFile] while this writer is started.
*/
val logWriter: LogWriter

/**
* Starts the log collection system
*/
Expand All @@ -51,7 +57,7 @@ interface LogFileWriter {
* Deletes all log files including active and compressed files
*
*/
fun deleteAllLogFiles()
suspend fun deleteAllLogFiles()

companion object {
fun logsDirectory(context: Context) = File(context.cacheDir, "logs")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

package com.wire.android.util.logging

import java.io.File

object LogFileWriterFactory {
fun create(logsDirectory: File, usePlatformIndependentFileLogger: Boolean): LogFileWriter =
if (usePlatformIndependentFileLogger) {
PlatformIndependentLogFileWriter(logsDirectory)
} else {
LogFileWriterImpl(logsDirectory)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package com.wire.android.util.logging

import android.util.Log
import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity
import com.wire.android.appLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -73,6 +75,11 @@
// Process management
private var logcatProcess: Process? = null

override val logWriter: LogWriter = object : LogWriter() {
@Suppress("UnusedParameter")
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) = Unit

Check warning on line 80 in app/src/main/kotlin/com/wire/android/util/logging/LogFileWriterImpl.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/util/logging/LogFileWriterImpl.kt#L80

Added line #L80 was not covered by tests
}

/**
* Initializes logging, waiting until the logger is actually initialized before returning.
* ```kotlin
Expand Down Expand Up @@ -324,11 +331,13 @@
}
}

override fun deleteAllLogFiles() {
clearActiveLoggingFileContent()
logsDirectory.listFiles()?.filter {
it.extension.lowercase(Locale.ROOT) == LOG_COMPRESSED_FILE_EXTENSION
}?.forEach { it.delete() }
override suspend fun deleteAllLogFiles() {
withContext(Dispatchers.IO) {
clearActiveLoggingFileContent()
logsDirectory.listFiles()?.filter {
it.extension.lowercase(Locale.ROOT) == LOG_COMPRESSED_FILE_EXTENSION
}?.forEach { it.delete() }
}
}

private fun getCompressedFilesList() = (logsDirectory.listFiles() ?: emptyArray()).filter {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.util.logging

import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

internal class LogLineTimestampFormatter(
private val currentTimeMillis: () -> Long = System::currentTimeMillis
) {
private val dateFormat = SimpleDateFormat(TIMESTAMP_PATTERN, Locale.US)
private val date = Date(0L)
private var cachedEpochSecond = Long.MIN_VALUE
private var cachedPrefix = ""

fun now(): String = format(currentTimeMillis())

fun format(timestampMillis: Long): String {
val epochSecond = timestampMillis / MILLIS_IN_SECOND
if (epochSecond != cachedEpochSecond) {
date.time = epochSecond * MILLIS_IN_SECOND
cachedPrefix = dateFormat.format(date)
cachedEpochSecond = epochSecond
}

val millis = (timestampMillis - epochSecond * MILLIS_IN_SECOND).toInt()
return buildString(capacity = cachedPrefix.length + MILLIS_SUFFIX_LENGTH) {
append(cachedPrefix)
append('.')
appendPaddedMillis(millis)
}
}

private fun StringBuilder.appendPaddedMillis(millis: Int) {
if (millis < HUNDRED_MILLIS) append('0')
if (millis < TEN_MILLIS) append('0')
append(millis)
}

private companion object {
const val TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss"
const val MILLIS_IN_SECOND = 1000L
const val MILLIS_SUFFIX_LENGTH = 4
const val HUNDRED_MILLIS = 100
const val TEN_MILLIS = 10
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part need carfull review in case i missed something
it does call System::currentTimeMillis and cache the the prefix and for each log line calculate only the ms
meaning the parsing for the full date is only one time per second and the rest only MS is added to the end
the next second it will parsed again

if there is a better and more efficient way to do so let me know

}
Loading
Loading