mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 10:55:55 +03:00
Basic sentry e2e reporting for rust + decrypt trust
This commit is contained in:
parent
2ae4b87f2f
commit
ae9711b7d1
8 changed files with 254 additions and 37 deletions
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api
|
||||||
import okhttp3.ConnectionSpec
|
import okhttp3.ConnectionSpec
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
|
import org.matrix.android.sdk.api.metrics.CryptoMetricPlugin
|
||||||
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
|
@ -80,5 +81,7 @@ data class MatrixConfiguration(
|
||||||
/**
|
/**
|
||||||
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
||||||
*/
|
*/
|
||||||
val metricPlugins: List<MetricPlugin> = emptyList()
|
val metricPlugins: List<MetricPlugin> = emptyList(),
|
||||||
|
|
||||||
|
val cryptoAnalyticsPlugin: CryptoMetricPlugin? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.metrics
|
||||||
|
|
||||||
|
import android.util.LruCache
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
|
|
||||||
|
sealed class CryptoEvent {
|
||||||
|
|
||||||
|
data class FailedToDecryptToDevice(
|
||||||
|
val error: String?
|
||||||
|
) : CryptoEvent()
|
||||||
|
|
||||||
|
data class FailedToSendToDevice(val eventTye: String) : CryptoEvent()
|
||||||
|
|
||||||
|
data class UnableToDecryptRoomMessage(
|
||||||
|
val sessionId: String,
|
||||||
|
val error: String?
|
||||||
|
) : CryptoEvent()
|
||||||
|
|
||||||
|
data class LateDecryptRoomMessage(val sessionId: String, val source: String) : CryptoEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CryptoMetricPlugin {
|
||||||
|
|
||||||
|
internal sealed class Report {
|
||||||
|
data class RoomE2EEReport(val error: MXCryptoError.Base, val sessionId: String) : Report()
|
||||||
|
data class ToDeviceDecryptReport(val error: Throwable) : Report()
|
||||||
|
data class ToDeviceSendReport(val error: Throwable) : Report()
|
||||||
|
data class OnRoomKeyImported(val sessionId: String, val source: String) : Report()
|
||||||
|
}
|
||||||
|
|
||||||
|
// should I scope that to some parent job?
|
||||||
|
val scope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
|
private val channel = Channel<Report>(capacity = Channel.UNLIMITED)
|
||||||
|
|
||||||
|
// Basic to avoid double reporting for same session and detect late reception
|
||||||
|
private val uisiCache = LruCache<String, Unit>(200)
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
for (ev in channel) {
|
||||||
|
handleEvent(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleEvent(ev: Report) {
|
||||||
|
when (ev) {
|
||||||
|
is Report.RoomE2EEReport -> {
|
||||||
|
if (uisiCache.get(ev.sessionId) == null) {
|
||||||
|
uisiCache.put(ev.sessionId, Unit)
|
||||||
|
captureEvent(
|
||||||
|
CryptoEvent.UnableToDecryptRoomMessage(
|
||||||
|
sessionId = ev.sessionId,
|
||||||
|
error = ev.error.errorType.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Report.ToDeviceDecryptReport -> {
|
||||||
|
captureEvent(CryptoEvent.FailedToDecryptToDevice(ev.error.message.toString()))
|
||||||
|
}
|
||||||
|
is Report.ToDeviceSendReport -> {
|
||||||
|
captureEvent(CryptoEvent.FailedToSendToDevice(ev.error.message.orEmpty()))
|
||||||
|
}
|
||||||
|
is Report.OnRoomKeyImported -> {
|
||||||
|
if (uisiCache.get(ev.sessionId) != null) {
|
||||||
|
// ok we have an uisi for this session
|
||||||
|
captureEvent(
|
||||||
|
CryptoEvent.LateDecryptRoomMessage(
|
||||||
|
sessionId = ev.sessionId,
|
||||||
|
source = ev.source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFailedToDecryptRoomMessage(error: MXCryptoError.Base, sessionId: String) {
|
||||||
|
channel.trySend(
|
||||||
|
Report.RoomE2EEReport(error, sessionId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFailToSendToDevice(failure: Throwable) {
|
||||||
|
channel.trySend(
|
||||||
|
Report.ToDeviceSendReport(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fun onFailToDecryptToDevice(failure: Throwable) {
|
||||||
|
channel.trySend(
|
||||||
|
Report.ToDeviceDecryptReport(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRoomKeyImported(sessionId: String, source: String) {
|
||||||
|
channel.trySend(
|
||||||
|
Report.OnRoomKeyImported(sessionId = sessionId, source = source)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun captureEvent(cryptoEvent: CryptoEvent)
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
|
@ -77,6 +78,7 @@ import org.matrix.rustcomponents.sdk.crypto.MegolmV1BackupKey
|
||||||
import org.matrix.rustcomponents.sdk.crypto.Request
|
import org.matrix.rustcomponents.sdk.crypto.Request
|
||||||
import org.matrix.rustcomponents.sdk.crypto.RequestType
|
import org.matrix.rustcomponents.sdk.crypto.RequestType
|
||||||
import org.matrix.rustcomponents.sdk.crypto.RoomKeyCounts
|
import org.matrix.rustcomponents.sdk.crypto.RoomKeyCounts
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.VerificationState
|
||||||
import org.matrix.rustcomponents.sdk.crypto.setLogger
|
import org.matrix.rustcomponents.sdk.crypto.setLogger
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -121,17 +123,22 @@ internal class OlmMachine @Inject constructor(
|
||||||
@SessionFilesDirectory path: File,
|
@SessionFilesDirectory path: File,
|
||||||
private val requestSender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val moshi: Moshi,
|
baseMoshi: Moshi,
|
||||||
private val verificationsProvider: VerificationsProvider,
|
private val verificationsProvider: VerificationsProvider,
|
||||||
private val deviceFactory: Device.Factory,
|
private val deviceFactory: Device.Factory,
|
||||||
private val getUserIdentity: GetUserIdentityUseCase,
|
private val getUserIdentity: GetUserIdentityUseCase,
|
||||||
private val ensureUsersKeys: EnsureUsersKeysUseCase,
|
private val ensureUsersKeys: EnsureUsersKeysUseCase,
|
||||||
|
private val matrixConfiguration: MatrixConfiguration,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val inner: InnerMachine = InnerMachine(userId, deviceId, path.toString(), null)
|
private val inner: InnerMachine = InnerMachine(userId, deviceId, path.toString(), null)
|
||||||
|
|
||||||
private val flowCollectors = FlowCollectors()
|
private val flowCollectors = FlowCollectors()
|
||||||
|
|
||||||
|
private val moshi = baseMoshi.newBuilder()
|
||||||
|
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||||
|
.build()
|
||||||
|
|
||||||
/** Get our own user ID. */
|
/** Get our own user ID. */
|
||||||
fun userId(): String {
|
fun userId(): String {
|
||||||
return inner.userId()
|
return inner.userId()
|
||||||
|
@ -431,18 +438,31 @@ internal class OlmMachine @Inject constructor(
|
||||||
senderCurve25519Key = decrypted.senderCurve25519Key,
|
senderCurve25519Key = decrypted.senderCurve25519Key,
|
||||||
claimedEd25519Key = decrypted.claimedEd25519Key,
|
claimedEd25519Key = decrypted.claimedEd25519Key,
|
||||||
forwardingCurve25519KeyChain = decrypted.forwardingCurve25519Chain,
|
forwardingCurve25519KeyChain = decrypted.forwardingCurve25519Chain,
|
||||||
// TODO how to get key safety? need to add binding to
|
isSafe = decrypted.verificationState == VerificationState.TRUSTED,
|
||||||
// get_verification_state
|
|
||||||
isSafe = true,
|
|
||||||
)
|
)
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
val reason =
|
val reThrow = when (throwable) {
|
||||||
String.format(
|
is DecryptionException.Megolm -> {
|
||||||
|
// TODO more bindings for missing room key
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, throwable.message.orEmpty())
|
||||||
|
}
|
||||||
|
is DecryptionException.Identifier -> {
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val reason = String.format(
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT_REASON,
|
MXCryptoError.UNABLE_TO_DECRYPT_REASON,
|
||||||
throwable.message,
|
throwable.message,
|
||||||
"m.megolm.v1.aes-sha2"
|
"m.megolm.v1.aes-sha2"
|
||||||
)
|
)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matrixConfiguration.cryptoAnalyticsPlugin?.onFailedToDecryptRoomMessage(
|
||||||
|
reThrow,
|
||||||
|
(event.content?.get("session_id") as? String) ?: ""
|
||||||
|
)
|
||||||
|
throw reThrow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.cancelChildren
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
@ -130,6 +131,7 @@ internal class RustCryptoService @Inject constructor(
|
||||||
private val encryptEventContent: EncryptEventContentUseCase,
|
private val encryptEventContent: EncryptEventContentUseCase,
|
||||||
private val getRoomUserIds: GetRoomUserIdsUseCase,
|
private val getRoomUserIds: GetRoomUserIdsUseCase,
|
||||||
private val outgoingRequestsProcessor: OutgoingRequestsProcessor,
|
private val outgoingRequestsProcessor: OutgoingRequestsProcessor,
|
||||||
|
private val matrixConfiguration: MatrixConfiguration,
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
|
@ -586,38 +588,44 @@ internal class RustCryptoService @Inject constructor(
|
||||||
val toDeviceEvents = this.olmMachine.receiveSyncChanges(toDevice, deviceChanges, keyCounts)
|
val toDeviceEvents = this.olmMachine.receiveSyncChanges(toDevice, deviceChanges, keyCounts)
|
||||||
|
|
||||||
// Notify the our listeners about room keys so decryption is retried.
|
// Notify the our listeners about room keys so decryption is retried.
|
||||||
if (toDeviceEvents.events != null) {
|
toDeviceEvents.events.orEmpty().forEach { event ->
|
||||||
toDeviceEvents.events.forEach { event ->
|
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
when (event.type) {
|
// rust failed to decrypt it
|
||||||
EventType.ROOM_KEY -> {
|
matrixConfiguration.cryptoAnalyticsPlugin?.onFailToDecryptToDevice(
|
||||||
val content = event.getClearContent().toModel<RoomKeyContent>() ?: return@forEach
|
Throwable("receiveSyncChanges")
|
||||||
content.sessionKey
|
)
|
||||||
val roomId = content.sessionId ?: return@forEach
|
}
|
||||||
val sessionId = content.sessionId
|
when (event.type) {
|
||||||
|
EventType.ROOM_KEY -> {
|
||||||
|
val content = event.getClearContent().toModel<RoomKeyContent>() ?: return@forEach
|
||||||
|
content.sessionKey
|
||||||
|
val roomId = content.sessionId ?: return@forEach
|
||||||
|
val sessionId = content.sessionId
|
||||||
|
|
||||||
notifyRoomKeyReceived(roomId, sessionId)
|
notifyRoomKeyReceived(roomId, sessionId)
|
||||||
}
|
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY)
|
||||||
EventType.FORWARDED_ROOM_KEY -> {
|
|
||||||
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach
|
|
||||||
|
|
||||||
val roomId = content.sessionId ?: return@forEach
|
|
||||||
val sessionId = content.sessionId
|
|
||||||
|
|
||||||
notifyRoomKeyReceived(roomId, sessionId)
|
|
||||||
}
|
|
||||||
EventType.SEND_SECRET -> {
|
|
||||||
// The rust-sdk will clear this event if it's invalid, this will produce an invalid base64 error
|
|
||||||
// when we try to construct the recovery key.
|
|
||||||
val secretContent = event.getClearContent().toModel<SecretSendEventContent>() ?: return@forEach
|
|
||||||
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
this.verificationService.onEvent(null, event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
EventType.FORWARDED_ROOM_KEY -> {
|
||||||
|
val content = event.getClearContent().toModel<ForwardedRoomKeyContent>() ?: return@forEach
|
||||||
|
|
||||||
|
val roomId = content.sessionId ?: return@forEach
|
||||||
|
val sessionId = content.sessionId
|
||||||
|
|
||||||
|
notifyRoomKeyReceived(roomId, sessionId)
|
||||||
|
matrixConfiguration.cryptoAnalyticsPlugin?.onRoomKeyImported(sessionId, EventType.FORWARDED_ROOM_KEY)
|
||||||
|
}
|
||||||
|
EventType.SEND_SECRET -> {
|
||||||
|
// The rust-sdk will clear this event if it's invalid, this will produce an invalid base64 error
|
||||||
|
// when we try to construct the recovery key.
|
||||||
|
val secretContent = event.getClearContent().toModel<SecretSendEventContent>() ?: return@forEach
|
||||||
|
this.keysBackupService.onSecretKeyGossip(secretContent.secretValue)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
this.verificationService.onEvent(null, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
liveEventManager.get().dispatchOnLiveToDevice(event)
|
liveEventManager.get().dispatchOnLiveToDevice(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,7 @@ import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.internal.crypto.ComputeShieldForGroupUseCase
|
import org.matrix.android.sdk.internal.crypto.ComputeShieldForGroupUseCase
|
||||||
|
@ -41,7 +42,8 @@ internal class OutgoingRequestsProcessor @Inject constructor(
|
||||||
private val requestSender: RequestSender,
|
private val requestSender: RequestSender,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val computeShieldForGroup: ComputeShieldForGroupUseCase
|
private val computeShieldForGroup: ComputeShieldForGroupUseCase,
|
||||||
|
private val matrixConfiguration: MatrixConfiguration,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val lock: Mutex = Mutex()
|
private val lock: Mutex = Mutex()
|
||||||
|
@ -156,6 +158,7 @@ internal class OutgoingRequestsProcessor @Inject constructor(
|
||||||
true
|
true
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
Timber.tag(loggerTag.value).e(throwable, "## sendToDevice(): error")
|
Timber.tag(loggerTag.value).e(throwable, "## sendToDevice(): error")
|
||||||
|
matrixConfiguration.cryptoAnalyticsPlugin?.onFailToSendToDevice(throwable)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,7 @@ import javax.inject.Singleton
|
||||||
flipperProxy.networkInterceptor(),
|
flipperProxy.networkInterceptor(),
|
||||||
),
|
),
|
||||||
metricPlugins = vectorPlugins.plugins(),
|
metricPlugins = vectorPlugins.plugins(),
|
||||||
|
cryptoAnalyticsPlugin = vectorPlugins.cryptoMetricPlugin,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.analytics.metrics
|
package im.vector.app.features.analytics.metrics
|
||||||
|
|
||||||
|
import im.vector.app.features.analytics.metrics.sentry.SentryCryptoAnalytics
|
||||||
import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics
|
import im.vector.app.features.analytics.metrics.sentry.SentryDownloadDeviceKeysMetrics
|
||||||
import im.vector.app.features.analytics.metrics.sentry.SentrySyncDurationMetrics
|
import im.vector.app.features.analytics.metrics.sentry.SentrySyncDurationMetrics
|
||||||
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
||||||
|
@ -29,6 +30,7 @@ import javax.inject.Singleton
|
||||||
data class VectorPlugins @Inject constructor(
|
data class VectorPlugins @Inject constructor(
|
||||||
val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics,
|
val sentryDownloadDeviceKeysMetrics: SentryDownloadDeviceKeysMetrics,
|
||||||
val sentrySyncDurationMetrics: SentrySyncDurationMetrics,
|
val sentrySyncDurationMetrics: SentrySyncDurationMetrics,
|
||||||
|
val cryptoMetricPlugin: SentryCryptoAnalytics
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Returns [List] of all [MetricPlugin] hold by this class.
|
* Returns [List] of all [MetricPlugin] hold by this class.
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.analytics.metrics.sentry
|
||||||
|
|
||||||
|
import im.vector.app.BuildConfig
|
||||||
|
import io.sentry.Sentry
|
||||||
|
import io.sentry.SentryEvent
|
||||||
|
import io.sentry.protocol.Message
|
||||||
|
import org.matrix.android.sdk.api.metrics.CryptoEvent
|
||||||
|
import org.matrix.android.sdk.api.metrics.CryptoMetricPlugin
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SentryCryptoAnalytics @Inject constructor() : CryptoMetricPlugin() {
|
||||||
|
|
||||||
|
override fun captureEvent(cryptoEvent: CryptoEvent) {
|
||||||
|
if (!Sentry.isEnabled()) return
|
||||||
|
val event = SentryEvent()
|
||||||
|
event.setTag("e2eFlavor", BuildConfig.FLAVOR)
|
||||||
|
event.setTag("e2eType", "crypto")
|
||||||
|
when (cryptoEvent) {
|
||||||
|
is CryptoEvent.FailedToDecryptToDevice -> {
|
||||||
|
event.message = Message().apply { message = "FailedToDecryptToDevice" }
|
||||||
|
event.setExtra("e2eOlmError", cryptoEvent.error ?: "Unknown")
|
||||||
|
}
|
||||||
|
is CryptoEvent.FailedToSendToDevice -> {
|
||||||
|
event.message = Message().apply { message = "FailedToSendToDevice" }
|
||||||
|
event.setExtra("e2eEventType", cryptoEvent.eventTye)
|
||||||
|
}
|
||||||
|
is CryptoEvent.LateDecryptRoomMessage -> {
|
||||||
|
event.message = Message().apply { message = "LateDecryptRoomMessage" }
|
||||||
|
event.setTag("e2eSource", cryptoEvent.source)
|
||||||
|
event.setExtra("e2eSessionId", cryptoEvent.sessionId)
|
||||||
|
}
|
||||||
|
is CryptoEvent.UnableToDecryptRoomMessage -> {
|
||||||
|
event.message = Message().apply { message = "UnableToDecryptRoomMessage" }
|
||||||
|
event.setExtra("e2eSessionId", cryptoEvent.sessionId)
|
||||||
|
event.setTag("e2eMegolmError", cryptoEvent.error.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sentry.captureEvent(event)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue