Commit 2c423b5e authored by MozLando's avatar MozLando
Browse files

Merge #7328



7328: Closes #7325: Don't submit viaduct rust errors to crash reporter  r=rocketsroger a=grigoryk

Also, expands test coverage for this component.
Co-authored-by: default avatarGrisha Kruglov <gkruglov@mozilla.com>
parents bc6c6a74 b3580550
......@@ -27,6 +27,23 @@ android {
}
}
configurations {
// There's an interaction between Gradle's resolution of dependencies with different types
// (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in
// JUnit test runner. The runtime classpath in the built-in JUnit test runner gets the
// dependency from the `implementation`, which is type @aar, and therefore the JNA dependency
// doesn't provide the JNI dispatch libraries in the correct Java resource directories. I think
// what's happening is that @aar type in `implementation` resolves to the @jar type in
// `testImplementation`, and that it wins the dependency resolution battle.
//
// A workaround is to add a new configuration which depends on the @jar type and to reference
// the underlying JAR file directly in `testImplementation`. This JAR file doesn't resolve to
// the @aar type in `implementation`. This works when invoked via `gradle`, but also sets the
// correct runtime classpath when invoked with Android Studio's built-in JUnit test runner.
// Success!
jnaForTest
}
dependencies {
implementation Dependencies.mozilla_rustlog
......@@ -36,8 +53,14 @@ dependencies {
api project(':support-base')
testImplementation Dependencies.mozilla_rustlog
testImplementation project(':support-test')
jnaForTest Dependencies.thirdparty_jna
testImplementation files(configurations.jnaForTest.copyRecursive().files)
testImplementation Dependencies.mozilla_full_megazord_forUnitTests
testImplementation Dependencies.testing_junit
testImplementation Dependencies.androidx_test_junit
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
}
......
......@@ -4,7 +4,9 @@
package mozilla.components.support.rustlog
import androidx.annotation.VisibleForTesting
import mozilla.appservices.rustlog.LogLevelFilter
import mozilla.appservices.rustlog.OnLog
import mozilla.appservices.rustlog.RustLogAdapter
import mozilla.components.support.base.crash.CrashReporting
import mozilla.components.support.base.log.Log
......@@ -36,23 +38,7 @@ object RustLog {
* @param crashReporter [CrashReporting] instance used for reporting 'error' log messages.
*/
fun enable(crashReporter: CrashReporting? = null) {
RustLogAdapter.enable { level, tagStr, msgStr ->
val priority = levelToPriority(level)
crashReporter?.let {
if (priority == Log.Priority.ERROR) {
it.submitCaughtException(RustErrorException(tagStr, msgStr))
}
}
Log.log(priority, tagStr, null, msgStr)
// Return true to keep open. Eventually we could intercept calls
// to disable that happen as the direct result of the above call
// (e.g. on this thread, before this function returns) and return
// false if any happen, but for now this is fine. (Exceptions thrown
// by a log sink will also close us)
true
}
RustLogAdapter.enable(CrashReporterOnLog(crashReporter))
}
/**
......@@ -81,23 +67,63 @@ object RustLog {
* debug logs to contain PII.
*/
fun setMaxLevel(level: Log.Priority, includePII: Boolean = false) {
val levelFilter = when (level) {
Log.Priority.DEBUG -> {
if (includePII) {
LogLevelFilter.TRACE
} else {
LogLevelFilter.DEBUG
}
RustLogAdapter.setMaxLevel(level.asLevelFilter(includePII))
}
}
@VisibleForTesting
internal class CrashReporterOnLog(private val crashReporter: CrashReporting? = null) : OnLog {
override fun invoke(level: Int, tag: String?, msg: String): Boolean {
val priority = levelToPriority(level)
crashReporter?.let { maybeSendLogToCrashReporter(it, priority, tag, msg) }
Log.log(priority, tag, null, msg)
// Return true to keep open. Eventually we could intercept calls
// to disable that happen as the direct result of the above call
// (e.g. on this thread, before this function returns) and return
// false if any happen, but for now this is fine. (Exceptions thrown
// by a log sink will also close us)
return true
}
private fun maybeSendLogToCrashReporter(
crashReporter: CrashReporting,
priority: Log.Priority,
tag: String?,
msg: String
) {
val ignoredTags = listOf(
// Majority of these are likely to be various network issues.
"viaduct::backend::ffi"
)
if (priority != Log.Priority.ERROR) {
return
}
if (ignoredTags.contains(tag)) {
return
}
crashReporter.submitCaughtException(RustErrorException(tag, msg))
}
}
@VisibleForTesting
internal fun Log.Priority.asLevelFilter(includePII: Boolean): LogLevelFilter {
return when (this) {
Log.Priority.DEBUG -> {
if (includePII) {
LogLevelFilter.TRACE
} else {
LogLevelFilter.DEBUG
}
Log.Priority.INFO -> LogLevelFilter.INFO
Log.Priority.WARN -> LogLevelFilter.WARN
Log.Priority.ERROR -> LogLevelFilter.ERROR
}
RustLogAdapter.setMaxLevel(levelFilter)
Log.Priority.INFO -> LogLevelFilter.INFO
Log.Priority.WARN -> LogLevelFilter.WARN
Log.Priority.ERROR -> LogLevelFilter.ERROR
}
}
internal fun levelToPriority(level: Int): Log.Priority {
private fun levelToPriority(level: Int): Log.Priority {
return when {
level <= android.util.Log.DEBUG -> Log.Priority.DEBUG
level == android.util.Log.INFO -> Log.Priority.INFO
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.support.rustlog
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Job
import mozilla.appservices.rustlog.LogAdapterCannotEnable
import mozilla.appservices.rustlog.LogLevelFilter
import mozilla.components.support.base.crash.Breadcrumb
import mozilla.components.support.base.crash.CrashReporting
import mozilla.components.support.base.log.Log
import org.junit.Test
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyZeroInteractions
@RunWith(AndroidJUnit4::class)
class RustLogTest {
@Test
fun `basic RustLog interactions do not blow up`() {
RustLog.enable()
try {
RustLog.enable()
fail("can't enable RustLog more than once")
} catch (e: LogAdapterCannotEnable) {}
try {
RustLog.enable(mock())
fail("can't enable RustLog more than once")
} catch (e: LogAdapterCannotEnable) {}
RustLog.setMaxLevel(Log.Priority.DEBUG, false)
RustLog.setMaxLevel(Log.Priority.DEBUG, true)
RustLog.setMaxLevel(Log.Priority.INFO, false)
RustLog.setMaxLevel(Log.Priority.INFO, true)
RustLog.setMaxLevel(Log.Priority.WARN, false)
RustLog.setMaxLevel(Log.Priority.WARN, true)
RustLog.setMaxLevel(Log.Priority.ERROR, false)
RustLog.setMaxLevel(Log.Priority.ERROR, true)
RustLog.disable()
RustLog.enable(mock())
}
@Test
fun `maybeSendLogToCrashReporter log processing ignores low priority stuff`() {
val crashReporter = mock<CrashReporting>()
val onLog = CrashReporterOnLog(crashReporter)
onLog(android.util.Log.VERBOSE, "sync15:multiple", "Stuff broke")
verifyZeroInteractions(crashReporter)
onLog(android.util.Log.DEBUG, "sync15:multiple", "Stuff broke")
verifyZeroInteractions(crashReporter)
onLog(android.util.Log.INFO, "sync15:multiple", "Stuff broke")
verifyZeroInteractions(crashReporter)
onLog(android.util.Log.WARN, "sync15:multiple", "Stuff broke")
verifyZeroInteractions(crashReporter)
onLog(android.util.Log.WARN, null, "Stuff broke")
verifyZeroInteractions(crashReporter)
}
@Test
fun `maybeSendLogToCrashReporter log processing reports error level stuff`() {
val crashReporter = TestCrashReporter()
val onLog = CrashReporterOnLog(crashReporter)
onLog(android.util.Log.ERROR, "sync15:multiple", "Stuff broke")
crashReporter.assertLastException(1, "sync15:multiple - Stuff broke")
onLog(android.util.Log.ERROR, null, "Something maybe broke")
crashReporter.assertLastException(2, "null - Something maybe broke")
}
@Test
fun `maybeSendLogToCrashReporter log processing ignores certain tags`() {
val expectedIgnoredTags = listOf("viaduct::backend::ffi")
val crashReporter = TestCrashReporter()
val onLog = CrashReporterOnLog(crashReporter)
expectedIgnoredTags.forEach { tag ->
onLog(android.util.Log.ERROR, tag, "Stuff broke")
assertTrue(crashReporter.exceptions.isEmpty())
}
// null tags are fine
onLog(android.util.Log.ERROR, null, "Stuff broke")
crashReporter.assertLastException(1, "null - Stuff broke")
// subsequent non-null and non-ignored are fine
onLog(android.util.Log.ERROR, "sync15:places", "DB stuff broke")
crashReporter.assertLastException(2, "sync15:places - DB stuff broke")
// ignored are still ignored
expectedIgnoredTags.forEach { tag ->
onLog(android.util.Log.ERROR, tag, "Stuff broke")
assertEquals(2, crashReporter.exceptions.size)
}
}
@Test
fun `log priority to level filter`() {
assertEquals(LogLevelFilter.DEBUG, Log.Priority.DEBUG.asLevelFilter(false))
assertEquals(LogLevelFilter.TRACE, Log.Priority.DEBUG.asLevelFilter(true))
assertEquals(LogLevelFilter.INFO, Log.Priority.INFO.asLevelFilter(false))
assertEquals(LogLevelFilter.INFO, Log.Priority.INFO.asLevelFilter(true))
assertEquals(LogLevelFilter.WARN, Log.Priority.WARN.asLevelFilter(false))
assertEquals(LogLevelFilter.WARN, Log.Priority.WARN.asLevelFilter(true))
assertEquals(LogLevelFilter.ERROR, Log.Priority.ERROR.asLevelFilter(false))
assertEquals(LogLevelFilter.ERROR, Log.Priority.ERROR.asLevelFilter(true))
}
private class TestCrashReporter : CrashReporting {
val exceptions: MutableList<Throwable> = mutableListOf()
override fun submitCaughtException(throwable: Throwable): Job {
exceptions.add(throwable)
return mock()
}
override fun recordCrashBreadcrumb(breadcrumb: Breadcrumb) {
fail()
}
fun assertLastException(expectedCount: Int, msg: String) {
assertEquals(expectedCount, exceptions.size)
assertTrue(exceptions.last() is RustErrorException)
assertEquals(msg, exceptions.last().message)
}
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment