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

Fix #1104: Offline Exception Logging #1500

Merged
merged 34 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3a5134a
proto structure.
Sarthak2601 Jul 22, 2020
f1ff8d3
controller.
Sarthak2601 Jul 22, 2020
df0d256
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 22, 2020
2059c90
code replacement logger -> controller (Domain)
Sarthak2601 Jul 22, 2020
2f01174
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 22, 2020
3b8174d
app module test fixes.
Sarthak2601 Jul 23, 2020
3a745d5
nits | changes | tests.
Sarthak2601 Jul 25, 2020
b4fc381
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 25, 2020
cb7424e
lint corrections.
Sarthak2601 Jul 25, 2020
48de325
more lint fixes.
Sarthak2601 Jul 25, 2020
c6b9d73
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 25, 2020
d8c09da
fixes.
Sarthak2601 Jul 26, 2020
06c60a5
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 26, 2020
36237d8
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 27, 2020
8af1f11
nits.
Sarthak2601 Jul 29, 2020
c284d59
nits.
Sarthak2601 Jul 29, 2020
d5698f8
helper class.
Sarthak2601 Jul 30, 2020
34f1b9f
Merge branch 'develop' into exception-log-offline
Sarthak2601 Jul 30, 2020
1e1a758
fixes.
Sarthak2601 Jul 31, 2020
509d306
kdoc.
Sarthak2601 Jul 31, 2020
99d0a23
nit.
Sarthak2601 Aug 4, 2020
07119de
additions.
Sarthak2601 Aug 5, 2020
d3ee04f
lint corrections.
Sarthak2601 Aug 5, 2020
4ce4ebf
Merge branch 'develop' into exception-log-offline
Sarthak2601 Aug 5, 2020
fd1371d
Merge branch 'develop' into exception-log-offline
Sarthak2601 Aug 5, 2020
76b675a
in progress.
Sarthak2601 Aug 6, 2020
713546f
in progress.
Sarthak2601 Aug 9, 2020
a5b1b5a
getExceptionLogStore
Sarthak2601 Aug 10, 2020
9646b51
lint fix.
Sarthak2601 Aug 10, 2020
d31adcc
Merge branch 'develop' into exception-log-offline
Sarthak2601 Aug 10, 2020
50ade6b
fix.
Sarthak2601 Aug 11, 2020
e4cab13
minor fix.
Sarthak2601 Aug 11, 2020
b51f787
lint fix.
Sarthak2601 Aug 11, 2020
cc74a1c
Merge branch 'develop' into exception-log-offline
Sarthak2601 Aug 11, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import org.oppia.app.model.ExceptionLog.ExceptionType
import org.oppia.app.model.OppiaExceptionLogs
import org.oppia.data.persistence.PersistentCacheStore
import org.oppia.domain.oppialogger.ExceptionLogStorageCacheSize
import org.oppia.util.data.AsyncResult
import org.oppia.util.data.DataProviders
import org.oppia.util.data.DataProvider
import org.oppia.util.logging.ConsoleLogger
import org.oppia.util.logging.ExceptionLogger
import org.oppia.util.networking.NetworkConnectionUtil
Expand All @@ -18,7 +17,6 @@ private const val EXCEPTIONS_CONTROLLER = "Exceptions Controller"
/** Controller for handling exception logging. */
class ExceptionsController @Inject constructor(
private val exceptionLogger: ExceptionLogger,
private val dataProviders: DataProviders,
cacheStoreFactory: PersistentCacheStore.Factory,
private val consoleLogger: ConsoleLogger,
private val networkConnectionUtil: NetworkConnectionUtil,
Expand Down Expand Up @@ -63,8 +61,7 @@ class ExceptionsController @Inject constructor(
when (networkConnectionUtil.getCurrentConnectionStatus()) {
NetworkConnectionUtil.ConnectionStatus.NONE ->
cacheExceptionLog(
convertExceptionToExceptionLog(
exception,
exception.toExceptionLog(
timestampInMillis,
exceptionType
)
Expand All @@ -74,22 +71,22 @@ class ExceptionsController @Inject constructor(
}

/** Returns an [ExceptionLog] from a [throwable]. */
private fun convertExceptionToExceptionLog(
throwable: Throwable,
private fun Throwable.toExceptionLog(
timestampInMillis: Long,
exceptionType: ExceptionType
): ExceptionLog {
val exceptionLogBuilder = ExceptionLog.newBuilder()
throwable.message?.let {
this.message?.let {
exceptionLogBuilder.message = it
}
exceptionLogBuilder.timestampInMillis = timestampInMillis
throwable.cause?.let {
exceptionLogBuilder.cause =
convertExceptionToExceptionLog(it, timestampInMillis, exceptionType)
this.cause?.let {
exceptionLogBuilder.cause = it.toExceptionLog(timestampInMillis, exceptionType)
}
throwable.stackTrace?.let {
exceptionLogBuilder.addAllStacktraceElement(it.map(this::convertStackTraceElementToLog))
this.stackTrace?.let {
exceptionLogBuilder.addAllStacktraceElement(
it.map(this@ExceptionsController::convertStackTraceElementToLog)
)
}
exceptionLogBuilder.exceptionType = exceptionType
return exceptionLogBuilder.build()
Expand Down Expand Up @@ -166,8 +163,8 @@ class ExceptionsController @Inject constructor(
* Returns a [LiveData] result which can be used to get [OppiaExceptionLogs]
* for the purpose of uploading in the presence of network connectivity.
*/
fun getExceptionLogs(): LiveData<AsyncResult<OppiaExceptionLogs>> {
return dataProviders.convertToLiveData(exceptionLogStore)
fun getExceptionLogStore(): DataProvider<OppiaExceptionLogs> {
return exceptionLogStore
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand All @@ -31,6 +34,7 @@ import org.oppia.testing.TestCoroutineDispatchers
import org.oppia.testing.TestDispatcherModule
import org.oppia.testing.TestLogReportingModule
import org.oppia.util.data.AsyncResult
import org.oppia.util.data.DataProviders
import org.oppia.util.logging.EnableConsoleLog
import org.oppia.util.logging.EnableFileLog
import org.oppia.util.logging.GlobalLogLevel
Expand All @@ -54,6 +58,9 @@ class ExceptionsControllerTest {
@JvmField
val mockitoRule: MockitoRule = MockitoJUnit.rule()

@Inject
lateinit var dataProviders: DataProviders

@Inject
lateinit var exceptionsController: ExceptionsController

Expand All @@ -74,6 +81,8 @@ class ExceptionsControllerTest {
lateinit var oppiaExceptionLogsResultCaptor: ArgumentCaptor<AsyncResult<OppiaExceptionLogs>>

@Before
@ExperimentalCoroutinesApi
@ObsoleteCoroutinesApi
fun setUp() {
networkConnectionUtil = NetworkConnectionUtil(ApplicationProvider.getApplicationContext())
setUpTestApplicationComponent()
Expand Down Expand Up @@ -105,16 +114,19 @@ class ExceptionsControllerTest {
@InternalCoroutinesApi
@Test
fun testController_logException_nonFatal_withNoNetwork_logsToCacheStore() {
networkConnectionUtil.setCurrentConnectionStatus(NetworkConnectionUtil.ConnectionStatus.NONE)
val exceptionThrown = Exception("TEST MESSAGE", Throwable("TEST CAUSE"))
networkConnectionUtil.setCurrentConnectionStatus(NetworkConnectionUtil.ConnectionStatus.NONE)
exceptionsController.logNonFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

verify(mockOppiaExceptionLogsObserver, atLeastOnce())
.onChanged(oppiaExceptionLogsResultCaptor.capture())
verify(
mockOppiaExceptionLogsObserver,
atLeastOnce()
).onChanged(oppiaExceptionLogsResultCaptor.capture())

val exceptionLog = oppiaExceptionLogsResultCaptor.value.getOrThrow().getExceptionLog(0)
val exception = exceptionLog.toException()
Expand All @@ -133,7 +145,8 @@ class ExceptionsControllerTest {
val exceptionThrown = Exception("TEST MESSAGE", Throwable("TEST"))
exceptionsController.logFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

Expand Down Expand Up @@ -172,7 +185,8 @@ class ExceptionsControllerTest {
TEST_TIMESTAMP_IN_MILLIS_FOUR
)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

Expand All @@ -182,7 +196,8 @@ class ExceptionsControllerTest {
val exceptionOne = oppiaExceptionLogsResultCaptor.value.getOrThrow().getExceptionLog(0)
val exceptionTwo = oppiaExceptionLogsResultCaptor.value.getOrThrow().getExceptionLog(1)

// In this case, 3 fatal and 1 non-fatal exceptions were logged. So while pruning, none of the retained logs should have non-fatal exception type.
// In this case, 3 fatal and 1 non-fatal exceptions were logged. The order of logging was fatal->non-fatal->fatal->fatal.
// So while pruning, none of the retained logs should have non-fatal exception type.
assertThat(exceptionOne.exceptionType).isNotEqualTo(ExceptionType.NON_FATAL)
BenHenning marked this conversation as resolved.
Show resolved Hide resolved
assertThat(exceptionTwo.exceptionType).isNotEqualTo(ExceptionType.NON_FATAL)
// If we analyse the order of logging of exceptions, we can see that record pruning will begin from the logging of the third record.
Expand Down Expand Up @@ -214,7 +229,8 @@ class ExceptionsControllerTest {
TEST_TIMESTAMP_IN_MILLIS_THREE
)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

Expand All @@ -234,7 +250,8 @@ class ExceptionsControllerTest {
networkConnectionUtil.setCurrentConnectionStatus(NetworkConnectionUtil.ConnectionStatus.NONE)
exceptionsController.logFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

Expand Down Expand Up @@ -262,7 +279,8 @@ class ExceptionsControllerTest {
exceptionsController.logNonFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)
exceptionsController.logFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions = exceptionsController.getExceptionLogs()
val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

Expand All @@ -274,6 +292,52 @@ class ExceptionsControllerTest {
assertThat(exceptionTwo.exceptionType).isEqualTo(ExceptionType.FATAL)
}

@ExperimentalCoroutinesApi
@InternalCoroutinesApi
@Test
fun testExtension_logEmptyException_withNoNetwork_verifyRecreationOfLogs() {
networkConnectionUtil.setCurrentConnectionStatus(NetworkConnectionUtil.ConnectionStatus.NONE)
val exceptionThrown = Exception()
exceptionsController.logNonFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

verify(mockOppiaExceptionLogsObserver, atLeastOnce())
.onChanged(oppiaExceptionLogsResultCaptor.capture())
val exceptionLog = oppiaExceptionLogsResultCaptor.value.getOrThrow().getExceptionLog(0)
val exception = exceptionLog.toException()

assertThat(exception.message).isEqualTo(null)
assertThat(exception.stackTrace).isEqualTo(exceptionThrown.stackTrace)
assertThat(exception.cause).isEqualTo(null)
}

@ExperimentalCoroutinesApi
@InternalCoroutinesApi
@Test
fun testExtension_logException_withNoCause_withNoNetwork_verifyRecreationOfLogs() {
networkConnectionUtil.setCurrentConnectionStatus(NetworkConnectionUtil.ConnectionStatus.NONE)
val exceptionThrown = Exception("TEST")
exceptionsController.logNonFatalException(exceptionThrown, TEST_TIMESTAMP_IN_MILLIS_ONE)

val cachedExceptions =
dataProviders.convertToLiveData(exceptionsController.getExceptionLogStore())
cachedExceptions.observeForever(mockOppiaExceptionLogsObserver)
testCoroutineDispatchers.advanceUntilIdle()

verify(mockOppiaExceptionLogsObserver, atLeastOnce())
.onChanged(oppiaExceptionLogsResultCaptor.capture())
val exceptionLog = oppiaExceptionLogsResultCaptor.value.getOrThrow().getExceptionLog(0)
val exception = exceptionLog.toException()

assertThat(exception.message).isEqualTo("TEST")
assertThat(exception.stackTrace).isEqualTo(exceptionThrown.stackTrace)
assertThat(exception.cause).isEqualTo(null)
}

private fun setUpTestApplicationComponent() {
DaggerExceptionsControllerTest_TestApplicationComponent.builder()
.setApplication(ApplicationProvider.getApplicationContext())
Expand All @@ -293,6 +357,14 @@ class ExceptionsControllerTest {
return application
}

@ExperimentalCoroutinesApi
@Singleton
@Provides
@TestDispatcher
fun provideTestDispatcher(): CoroutineDispatcher {
Copy link
Member

Choose a reason for hiding this comment

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

Please don’t do this. You should include the test dispatcher module, instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return TestCoroutineDispatcher()
}

// TODO(#59): Either isolate these to their own shared test module, or use the real logging
// module in tests to avoid needing to specify these settings for tests.
@EnableConsoleLog
Expand Down