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

Add initial Sentry setup for crashes and perf tracking #7141

Merged
merged 10 commits into from
Oct 5, 2022
1 change: 1 addition & 0 deletions changelog.d/7076.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add basic integration of Sentry to capture errors and crashes if user has given consent.
5 changes: 5 additions & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def jjwt = "0.11.5"
// the whole commit which set version 0.16.0-SNAPSHOT
def vanniktechEmoji = "0.16.0-SNAPSHOT"

def sentry = "6.4.1"

def fragment = "1.5.3"

// Testing
Expand Down Expand Up @@ -164,6 +166,9 @@ ext.libs = [
apache : [
'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator"
],
sentry: [
'sentryAndroid' : "io.sentry:sentry-android:$sentry"
],
tests : [
'kluent' : "org.amshove.kluent:kluent-android:1.68",
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
Expand Down
1 change: 1 addition & 0 deletions dependencies_groups.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ ext.groups = [
'io.opencensus',
'io.reactivex.rxjava2',
'io.realm',
'io.sentry',
'it.unimi.dsi',
'jakarta.activation',
'jakarta.xml.bind',
Expand Down
1 change: 1 addition & 0 deletions tools/danger/dangerfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const signOff = "Signed-off-by:"

// Please add new names following the alphabetical order.
const allowList = [
"amitkma",
"aringenbach",
"BillCarsonFr",
"bmarty",
Expand Down
14 changes: 12 additions & 2 deletions vector-config/src/main/java/im/vector/app/config/Analytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ sealed interface Analytics {
object Disabled : Analytics

/**
* Analytics integration via PostHog.
* Analytics integration via PostHog and Sentry.
*/
data class PostHog(
data class Enabled(
/**
* The PostHog instance url.
*/
Expand All @@ -44,5 +44,15 @@ sealed interface Analytics {
* A URL to more information about the analytics collection.
*/
val policyLink: String,

/**
* The Sentry DSN url.
*/
val sentryDSN: String,

/**
* Environment for Sentry.
*/
val sentryEnvironment: String
) : Analytics
}
10 changes: 7 additions & 3 deletions vector-config/src/main/java/im/vector/app/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,29 @@ object Config {
* The analytics configuration to use for the Debug build type.
* Can be disabled by providing Analytics.Disabled
*/
val DEBUG_ANALYTICS_CONFIG = Analytics.PostHog(
val DEBUG_ANALYTICS_CONFIG = Analytics.Enabled(
postHogHost = "https://posthog.element.dev",
postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
policyLink = "https://element.io/cookie-policy",
sentryDSN = "https://f6acc9cfc2024641b28c87ad95e73e66@sentry.tools.element.io/49",
sentryEnvironment = "DEBUG"
)

/**
* The analytics configuration to use for the Release build type.
* Can be disabled by providing Analytics.Disabled
*/
val RELEASE_ANALYTICS_CONFIG = Analytics.PostHog(
val RELEASE_ANALYTICS_CONFIG = Analytics.Enabled(
postHogHost = "https://posthog.hss.element.io",
postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
policyLink = "https://element.io/cookie-policy",
sentryDSN = "https://f6acc9cfc2024641b28c87ad95e73e66@sentry.tools.element.io/49",
sentryEnvironment = "RELEASE"
)

/**
* The analytics configuration to use for the Nightly build type.
* Can be disabled by providing Analytics.Disabled
*/
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "NIGHTLY")
}
1 change: 1 addition & 0 deletions vector/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ dependencies {
implementation('com.posthog.android:posthog:1.1.2') {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation libs.sentry.sentryAndroid

// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.1.0'
Expand Down
3 changes: 3 additions & 0 deletions vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@

<application android:supportsRtl="true">

<!-- Sentry auto-initialization disable -->
<meta-data android:name="io.sentry.auto-init" android:value="false" />

<!-- No limit for screen ratio: avoid black strips -->
<meta-data
android:name="android.max_aspect"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ object ConfigurationModule {
else -> throw IllegalStateException("Unhandled build type: ${BuildConfig.BUILD_TYPE}")
}
return when (config) {
Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "")
is Analytics.PostHog -> AnalyticsConfig(
Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "", "", "")
is Analytics.Enabled -> AnalyticsConfig(
isEnabled = true,
postHogHost = config.postHogHost,
postHogApiKey = config.postHogApiKey,
policyLink = config.policyLink
policyLink = config.policyLink,
sentryDSN = config.sentryDSN,
sentryEnvironment = config.sentryEnvironment
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ data class AnalyticsConfig(
val postHogHost: String,
val postHogApiKey: String,
val policyLink: String,
val sentryDSN: String,
val sentryEnvironment: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private val IGNORED_OPTIONS: Options? = null
@Singleton
class DefaultVectorAnalytics @Inject constructor(
postHogFactory: PostHogFactory,
private val sentryFactory: SentryFactory,
analyticsConfig: AnalyticsConfig,
private val analyticsStore: AnalyticsStore,
private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
Expand Down Expand Up @@ -94,6 +95,9 @@ class DefaultVectorAnalytics @Inject constructor(
override suspend fun onSignOut() {
// reset the analyticsId
setAnalyticsId("")

// Close Sentry SDK.
sentryFactory.stopSentry()
}

private fun observeAnalyticsId() {
Expand Down Expand Up @@ -123,10 +127,20 @@ class DefaultVectorAnalytics @Inject constructor(
Timber.tag(analyticsTag.value).d("User consent updated to $consent")
userConsent = consent
optOutPostHog()
initOrStopSentry()
}
.launchIn(globalScope)
}

private fun initOrStopSentry() {
userConsent?.let {
when (it) {
true -> sentryFactory.initSentry()
false -> sentryFactory.stopSentry()
}
}
}

private fun optOutPostHog() {
userConsent?.let { posthog?.optOut(!it) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.impl

import android.content.Context
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.log.analyticsTag
import io.sentry.Sentry
import io.sentry.SentryOptions
import io.sentry.android.core.SentryAndroid
import timber.log.Timber
import javax.inject.Inject

class SentryFactory @Inject constructor(
private val context: Context,
private val analyticsConfig: AnalyticsConfig,
) {

fun initSentry() {
Timber.tag(analyticsTag.value).d("Initializing Sentry")
if (Sentry.isEnabled()) return
SentryAndroid.init(context) { options ->
options.dsn = analyticsConfig.sentryDSN
options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> event }
options.tracesSampleRate = 1.0
options.isEnableUserInteractionTracing = true
amitkma marked this conversation as resolved.
Show resolved Hide resolved
options.environment = analyticsConfig.sentryEnvironment
options.diagnosticLevel
}
}

fun stopSentry() {
Timber.tag(analyticsTag.value).d("Stopping Sentry")
Sentry.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import im.vector.app.test.fakes.FakeAnalyticsStore
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
import im.vector.app.test.fakes.FakePostHog
import im.vector.app.test.fakes.FakePostHogFactory
import im.vector.app.test.fakes.FakeSentryFactory
import im.vector.app.test.fixtures.AnalyticsConfigFixture.anAnalyticsConfig
import im.vector.app.test.fixtures.aUserProperties
import im.vector.app.test.fixtures.aVectorAnalyticsEvent
Expand All @@ -45,9 +46,11 @@ class DefaultVectorAnalyticsTest {
private val fakePostHog = FakePostHog()
private val fakeAnalyticsStore = FakeAnalyticsStore()
private val fakeLateInitUserPropertiesFactory = FakeLateInitUserPropertiesFactory()
private val fakeSentryFactory = FakeSentryFactory()

private val defaultVectorAnalytics = DefaultVectorAnalytics(
postHogFactory = FakePostHogFactory(fakePostHog.instance).instance,
sentryFactory = fakeSentryFactory.instance,
analyticsStore = fakeAnalyticsStore.instance,
globalScope = CoroutineScope(Dispatchers.Unconfined),
analyticsConfig = anAnalyticsConfig(isEnabled = true),
Expand All @@ -67,17 +70,21 @@ class DefaultVectorAnalyticsTest {
}

@Test
fun `when consenting to analytics then updates posthog opt out to false`() = runTest {
fun `when consenting to analytics then updates posthog opt out to false and initialize Sentry`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = true)

fakePostHog.verifyOptOutStatus(optedOut = false)

fakeSentryFactory.verifySentryInit()
}

@Test
fun `when revoking consent to analytics then updates posthog opt out to true`() = runTest {
fun `when revoking consent to analytics then updates posthog opt out to true and closes Sentry`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = false)

fakePostHog.verifyOptOutStatus(optedOut = true)

fakeSentryFactory.verifySentryClose()
}

@Test
Expand All @@ -97,12 +104,14 @@ class DefaultVectorAnalyticsTest {
}

@Test
fun `when signing out then resets posthog`() = runTest {
fun `when signing out then resets posthog and closes Sentry`() = runTest {
fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()

defaultVectorAnalytics.onSignOut()

fakePostHog.verifyReset()

fakeSentryFactory.verifySentryClose()
}

@Test
Expand Down
44 changes: 44 additions & 0 deletions vector/src/test/java/im/vector/app/test/fakes/FakeSentryFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.test.fakes

import im.vector.app.features.analytics.impl.SentryFactory
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify

class FakeSentryFactory {
private var isSentryEnabled = false

val instance = mockk<SentryFactory>().also {
every { it.initSentry() } answers {
isSentryEnabled = true
}

every { it.stopSentry() } answers {
isSentryEnabled = false
}
}

fun verifySentryInit() {
verify { instance.initSentry() }
}

fun verifySentryClose() {
verify { instance.stopSentry() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ object AnalyticsConfigFixture {
isEnabled: Boolean = false,
postHogHost: String = "http://posthog.url",
postHogApiKey: String = "api-key",
policyLink: String = "http://policy.link"
) = AnalyticsConfig(isEnabled, postHogHost, postHogApiKey, policyLink)
policyLink: String = "http://policy.link",
sentryDSN: String = "http://sentry.dsn",
sentryEnvironment: String = "sentry-env"
) = AnalyticsConfig(isEnabled, postHogHost, postHogApiKey, policyLink, sentryDSN, sentryEnvironment)
}