feat: Attach Rust logs file if app has crashed.

This commit is contained in:
Mateusz Armatys
2025-07-16 16:30:06 +02:00
committed by MargeBot
parent 926045c683
commit afe4d4e025
2 changed files with 72 additions and 5 deletions
@@ -21,6 +21,7 @@ package ch.protonmail.android.initializer
import android.content.Context
import androidx.startup.Initializer
import ch.protonmail.android.BuildConfig
import ch.protonmail.android.logging.RustLogsAttachmentProcessor
import ch.protonmail.android.logging.SentryUserObserver
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
@@ -35,6 +36,11 @@ import me.proton.core.configuration.EnvironmentConfigurationDefaults
class SentryInitializer : Initializer<Unit> {
override fun create(context: Context) {
val entryPoint = EntryPointAccessors.fromApplication(
context.applicationContext,
SentryInitializerEntryPoint::class.java
)
SentryAndroid.init(context.applicationContext) { options: SentryOptions ->
options.dsn = BuildConfig.SENTRY_DSN
options.release = BuildConfig.VERSION_NAME
@@ -45,14 +51,10 @@ class SentryInitializer : Initializer<Unit> {
minBreadcrumbLevel = SentryLevel.INFO
)
)
options.addEventProcessor(entryPoint.rustLogsAttachmentProcessor())
}
val entryPoint = EntryPointAccessors.fromApplication(
context.applicationContext,
SentryInitializerEntryPoint::class.java
)
entryPoint.observer().start()
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
@@ -60,6 +62,8 @@ class SentryInitializer : Initializer<Unit> {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface SentryInitializerEntryPoint {
fun observer(): SentryUserObserver
fun rustLogsAttachmentProcessor(): RustLogsAttachmentProcessor
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2025 Proton Technologies AG
* This file is part of Proton Technologies AG and Proton Mail.
*
* Proton Mail 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.
*
* Proton Mail 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 Proton Mail. If not, see <https://www.gnu.org/licenses/>.
*/
package ch.protonmail.android.logging
import java.io.File
import java.io.RandomAccessFile
import ch.protonmail.android.mailbugreport.domain.LogsFileHandler
import ch.protonmail.android.mailbugreport.domain.annotations.RustLogsFileHandler
import io.sentry.Attachment
import io.sentry.EventProcessor
import io.sentry.Hint
import io.sentry.SentryEvent
import javax.inject.Inject
private const val MAX_BYTES = 16 * 1024
class RustLogsAttachmentProcessor @Inject constructor(
@RustLogsFileHandler
private val logsFileHandler: LogsFileHandler
) : EventProcessor {
override fun process(event: SentryEvent, hint: Hint): SentryEvent {
if (event.isCrashed) {
getRustLogsAttachment()?.let { logs ->
hint.addAttachment(logs)
}
}
return event
}
private fun getRustLogsAttachment(maxBytes: Int = MAX_BYTES): Attachment? = runCatching {
logsFileHandler.getLastLogFile()?.let { file ->
val bytes = readFileTail(file, maxBytes)
Attachment(bytes, file.name)
}
}.getOrNull()
private fun readFileTail(file: File, maxBytes: Int): ByteArray = RandomAccessFile(file, "r").use { raf ->
val fileLen = raf.length()
val bytesToRead = minOf(fileLen, maxBytes.toLong())
raf.seek(maxOf(fileLen - bytesToRead, 0))
val bytes = ByteArray(bytesToRead.toInt())
raf.readFully(bytes)
bytes
}
}