Remove the rc_log crate.
This commit is contained in:
Родитель
47ef7c8c98
Коммит
a5e715b286
|
@ -49,13 +49,6 @@ projects:
|
|||
- name: remotesettings
|
||||
type: aar
|
||||
description: A Remote Settings client intended for the application layer.
|
||||
rustlog:
|
||||
path: components/rc_log/android
|
||||
artifactId: rustlog
|
||||
publications:
|
||||
- name: rustlog
|
||||
type: aar
|
||||
description: Android hook into the log crate.
|
||||
rust-log-forwarder:
|
||||
path: components/support/rust-log-forwarder/android
|
||||
artifactId: rust-log-forwarder
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## 🦊 What's Changed 🦊
|
||||
|
||||
- The long-deprecated `rc_log` crate has been removed.
|
||||
|
||||
### Android
|
||||
- Upgraded NDK from r25c to r26c. ([#6134](https://github.com/mozilla/application-services/pull/6134))
|
||||
|
||||
|
|
|
@ -2352,7 +2352,6 @@ dependencies = [
|
|||
"nimbus-sdk",
|
||||
"places",
|
||||
"push",
|
||||
"rc_log_ffi",
|
||||
"remote_settings",
|
||||
"rust-log-forwarder",
|
||||
"suggest",
|
||||
|
@ -2367,7 +2366,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"error-support",
|
||||
"nimbus-sdk",
|
||||
"rc_log_ffi",
|
||||
"remote_settings",
|
||||
"rust-log-forwarder",
|
||||
"viaduct",
|
||||
|
@ -2387,7 +2385,6 @@ dependencies = [
|
|||
"nimbus-sdk",
|
||||
"places",
|
||||
"push",
|
||||
"rc_log_ffi",
|
||||
"remote_settings",
|
||||
"rust-log-forwarder",
|
||||
"suggest",
|
||||
|
@ -3484,16 +3481,6 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rc_log_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"ffi-support",
|
||||
"lazy_static",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
|
|
|
@ -12,7 +12,6 @@ members = [
|
|||
"components/places",
|
||||
"components/push",
|
||||
"components/remote_settings",
|
||||
"components/rc_log",
|
||||
"components/suggest",
|
||||
"components/support/error",
|
||||
"components/support/error/tests",
|
||||
|
@ -91,7 +90,6 @@ default-members = [
|
|||
"components/places",
|
||||
"components/push",
|
||||
"components/remote_settings",
|
||||
"components/rc_log",
|
||||
"components/support/error",
|
||||
"components/support/error/macros",
|
||||
"components/support/error/tests",
|
||||
|
|
11
README.md
11
README.md
|
@ -57,14 +57,11 @@ The application-services library primary consumers are Fenix (Firefox on Android
|
|||
|
||||
# Rust Components
|
||||
|
||||
[./components/](components) contains the source for each component, and its
|
||||
FFI bindings.
|
||||
|
||||
> Please note that we are in the process of moving away from hand-written ffi code and instead favouring the use of the [uniffi](https://github.com/mozilla/uniffi-rs/) library.
|
||||
[./components/](components) contains the source for each component. Note that most components have their FFI generated
|
||||
by the [uniffi](https://github.com/mozilla/uniffi-rs/) library.
|
||||
* See [./components/places/](components/places) for an example, where you can
|
||||
find:
|
||||
* The shared [rust code](components/places/src).
|
||||
* The mapping into a [C FFI](components/places/ffi).
|
||||
* The [Kotlin bindings](components/places/android) for use by Android
|
||||
applications.
|
||||
* The [Swift bindings](components/places/ios) for use by iOS applications.
|
||||
|
@ -85,8 +82,6 @@ The application-services library primary consumers are Fenix (Firefox on Android
|
|||
browsing history
|
||||
* [push](components/push) - for applications to receive real-time updates via
|
||||
WebPush
|
||||
* [rc_log](components/rc_log) - for connecting component log output to the
|
||||
application's log stream
|
||||
* [support](components/support) - low-level utility libraries
|
||||
* [support/rc_crypto](components/rc_crypto) - handles cryptographic needs backed by Mozilla's
|
||||
[NSS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS) library
|
||||
|
@ -100,3 +95,5 @@ The application-services library primary consumers are Fenix (Firefox on Android
|
|||
* [viaduct](components/viaduct) - an HTTP request library
|
||||
* [webext-storage](components/webext-storage) - powers an implementation of the
|
||||
chrome.storage.sync WebExtension API
|
||||
|
||||
Note the above list is partial; see the actual list under the `components` directory.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "rc_log_ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"]
|
||||
license = "MPL-2.0"
|
||||
exclude = ["/android", "/ios"]
|
||||
|
||||
[lib]
|
||||
name = "rc_log_ffi"
|
||||
crate-type = ["lib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# Required for gradle in robolectric
|
||||
force_android = []
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
ffi-support = "0.4"
|
||||
lazy_static = "1.4"
|
||||
cfg-if = "0.1"
|
|
@ -1,10 +0,0 @@
|
|||
apply from: "$rootDir/build-scripts/component-common.gradle"
|
||||
apply from: "$rootDir/build-scripts/protobuf-common.gradle"
|
||||
apply from: "$rootDir/publish.gradle"
|
||||
|
||||
android {
|
||||
namespace 'org.mozilla.appservices.rustlog'
|
||||
}
|
||||
|
||||
ext.dependsOnTheMegazord()
|
||||
ext.configurePublish()
|
|
@ -1,21 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -1 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
|
|
@ -1,49 +0,0 @@
|
|||
/* 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.appservices.rustlog
|
||||
|
||||
import com.sun.jna.Callback
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.PointerType
|
||||
import mozilla.appservices.support.native.loadIndirect
|
||||
import org.mozilla.appservices.rustlog.BuildConfig
|
||||
|
||||
@Suppress("FunctionNaming", "TooGenericExceptionThrown")
|
||||
internal interface LibRustLogAdapter : Library {
|
||||
companion object {
|
||||
// XXX this should be direct binding...
|
||||
internal var INSTANCE: LibRustLogAdapter =
|
||||
loadIndirect(componentName = "rustlog", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
fun rc_log_adapter_create(
|
||||
callback: RawLogCallback,
|
||||
outErr: RustError.ByReference,
|
||||
): RawLogAdapter?
|
||||
|
||||
fun rc_log_adapter_set_max_level(
|
||||
level: Int,
|
||||
outErr: RustError.ByReference,
|
||||
)
|
||||
|
||||
fun rc_log_adapter_destroy(
|
||||
adapter: RawLogAdapter,
|
||||
)
|
||||
|
||||
fun rc_log_adapter_destroy_string(
|
||||
stringPtr: Pointer,
|
||||
)
|
||||
|
||||
fun rc_log_adapter_test__log_msg(
|
||||
string: String,
|
||||
)
|
||||
}
|
||||
|
||||
internal interface RawLogCallback : Callback {
|
||||
fun invoke(level: Int, tag: Pointer?, message: Pointer): Byte
|
||||
}
|
||||
|
||||
internal class RawLogAdapter : PointerType()
|
|
@ -1,38 +0,0 @@
|
|||
/* 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.appservices.rustlog
|
||||
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.Structure
|
||||
|
||||
/**
|
||||
* This should be considered private, but it needs to be public for JNA.
|
||||
*/
|
||||
@Structure.FieldOrder("code", "message")
|
||||
open class RustError : Structure() {
|
||||
|
||||
class ByReference : RustError(), Structure.ByReference
|
||||
|
||||
@JvmField var code: Int = 0
|
||||
|
||||
@JvmField var message: Pointer? = null
|
||||
|
||||
fun isFailure(): Boolean {
|
||||
return code != 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and consume the error message, or null if there is none.
|
||||
*/
|
||||
@Synchronized
|
||||
fun consumeErrorMessage(): String {
|
||||
val result = this.message?.getAndConsumeRustString()
|
||||
this.message = null
|
||||
if (result == null) {
|
||||
throw NullPointerException("consumeErrorMessage called with null message!")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
/* 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.appservices.rustlog
|
||||
|
||||
import com.sun.jna.CallbackThreadInitializer
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.Pointer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
typealias OnLog = (Int, String?, String) -> Boolean
|
||||
class RustLogAdapter private constructor(
|
||||
// IMPORTANT: This must not be GCed while the adapter is alive!
|
||||
@Suppress("Unused")
|
||||
private val callbackImpl: RawLogCallbackImpl,
|
||||
private val adapter: RawLogAdapter,
|
||||
) {
|
||||
companion object {
|
||||
@Volatile
|
||||
private var instance: RustLogAdapter? = null
|
||||
|
||||
/**
|
||||
* true if the log is enabled.
|
||||
*/
|
||||
val isEnabled get() = getEnabled()
|
||||
|
||||
// Used to signal from the log callback that we should disable
|
||||
// the adapter because the callback returned false. Note that
|
||||
// Rust handles this too.
|
||||
internal val disabledRemotely = AtomicBoolean(false)
|
||||
|
||||
@Synchronized
|
||||
private fun getEnabled(): Boolean {
|
||||
if (instance == null) {
|
||||
return false
|
||||
}
|
||||
if (disabledRemotely.getAndSet(false)) {
|
||||
this.disable()
|
||||
}
|
||||
return instance != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the logger and use the provided logging callback.
|
||||
*
|
||||
* @throws [LogAdapterCannotEnable] if it is already enabled.
|
||||
*/
|
||||
@Synchronized
|
||||
fun enable(onLog: OnLog) {
|
||||
if (isEnabled) {
|
||||
throw LogAdapterCannotEnable("Adapter is already enabled")
|
||||
}
|
||||
// Tell JNA to reuse the callback thread.
|
||||
val initializer = CallbackThreadInitializer(
|
||||
// Don't block JVM shutdown waiting for this thread to exit.
|
||||
true, // daemon
|
||||
// Don't detach the JVM from this thread after invoking the callback.
|
||||
false, // detach
|
||||
"RustLogThread", // name
|
||||
)
|
||||
val callbackImpl = RawLogCallbackImpl(onLog)
|
||||
Native.setCallbackThreadInitializer(callbackImpl, initializer)
|
||||
// Hopefully there is no way to half-initialize the logger such that where the callback
|
||||
// could still get called despite an error/null being returned? If there is, we need to
|
||||
// make callbackImpl isn't GCed here, or very bad things will happen. (Should the logger
|
||||
// init code abort on panic?)
|
||||
val adapter = rustCall { err ->
|
||||
LibRustLogAdapter.INSTANCE.rc_log_adapter_create(callbackImpl, err)
|
||||
}
|
||||
// For example, it would be *extremely bad* if somehow adapter were actually null here.
|
||||
instance = RustLogAdapter(callbackImpl, adapter!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to enable the logger if it can be enabled. Returns true if
|
||||
* the logger was enabled by this call.
|
||||
*/
|
||||
@Synchronized
|
||||
fun tryEnable(onLog: OnLog): Boolean {
|
||||
if (isEnabled) {
|
||||
return false
|
||||
}
|
||||
enable(onLog)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the logger, allowing the logging callback to be garbage collected.
|
||||
*/
|
||||
@Synchronized
|
||||
fun disable() {
|
||||
val state = instance ?: return
|
||||
LibRustLogAdapter.INSTANCE.rc_log_adapter_destroy(state.adapter)
|
||||
// XXX Letting that callback get GCed still makes me extremely uneasy...
|
||||
// Maybe we should just null out the callback provided by the user so that
|
||||
// it can be GCed (while letting the RawLogCallbackImpl which actually is
|
||||
// called by Rust live on).
|
||||
instance = null
|
||||
disabledRemotely.set(false)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun setMaxLevel(level: LogLevelFilter) {
|
||||
if (isEnabled) {
|
||||
rustCall { e ->
|
||||
LibRustLogAdapter.INSTANCE.rc_log_adapter_set_max_level(
|
||||
level.value,
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <U> rustCall(callback: (RustError.ByReference) -> U): U {
|
||||
val e = RustError.ByReference()
|
||||
val ret: U = callback(e)
|
||||
if (e.isFailure()) {
|
||||
val msg = e.consumeErrorMessage()
|
||||
throw LogAdapterUnexpectedError(msg)
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All errors emitted by the LogAdapter will subclass this.
|
||||
*/
|
||||
sealed class LogAdapterError(msg: String) : Exception(msg)
|
||||
|
||||
/**
|
||||
* Error indicating that the log adapter cannot be enabled because it is already enabled.
|
||||
*/
|
||||
class LogAdapterCannotEnable(msg: String) : LogAdapterError("Log adapter may not be enabled: $msg")
|
||||
|
||||
/**
|
||||
* Thrown for unexpected log adapter errors (generally rust panics).
|
||||
*/
|
||||
class LogAdapterUnexpectedError(msg: String) : LogAdapterError("Unexpected log adapter error: $msg")
|
||||
|
||||
// Note: keep values in sync with level_filter_from_i32 in rust.
|
||||
/** Level filters, for use with setMaxLevel. */
|
||||
enum class LogLevelFilter(internal val value: Int) {
|
||||
/** Disable all logging */
|
||||
OFF(0),
|
||||
|
||||
/** Only allow ERROR logs. */
|
||||
ERROR(1),
|
||||
|
||||
/** Allow WARN and ERROR logs. */
|
||||
WARN(2),
|
||||
|
||||
/** Allow WARN, ERROR, and INFO logs. The default. */
|
||||
INFO(3),
|
||||
|
||||
/** Allow WARN, ERROR, INFO, and DEBUG logs. */
|
||||
DEBUG(4),
|
||||
|
||||
/** Allow all logs, including those that may contain PII. */
|
||||
TRACE(5),
|
||||
}
|
||||
|
||||
internal class RawLogCallbackImpl(private val onLog: OnLog) : RawLogCallback {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun invoke(level: Int, tag: Pointer?, message: Pointer): Byte {
|
||||
// We can't safely throw here!
|
||||
val result = try {
|
||||
val tagStr = tag?.getString(0, "utf8")
|
||||
val msgStr = message.getString(0, "utf8")
|
||||
onLog(level, tagStr, msgStr)
|
||||
} catch (e: Throwable) {
|
||||
try {
|
||||
println("Exception when logging: $e")
|
||||
} catch (e: Throwable) {
|
||||
// :(
|
||||
}
|
||||
false
|
||||
}
|
||||
return if (result) {
|
||||
1
|
||||
} else {
|
||||
RustLogAdapter.disabledRemotely.set(true)
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Pointer.getAndConsumeRustString(): String {
|
||||
try {
|
||||
return this.getString(0, "utf8")
|
||||
} finally {
|
||||
LibRustLogAdapter.INSTANCE.rc_log_adapter_destroy_string(this)
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package mozilla.appservices.rustlog
|
||||
|
||||
import mozilla.appservices.Megazord
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.lang.RuntimeException
|
||||
import java.util.WeakHashMap
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class LogTest {
|
||||
|
||||
fun writeTestLog(m: String) {
|
||||
LibRustLogAdapter.INSTANCE.rc_log_adapter_test__log_msg(m)
|
||||
Thread.sleep(100) // Wait for it to arrive...
|
||||
}
|
||||
|
||||
// This should be split up now that we can re-enable after disabling
|
||||
// (note that it will still need to run sequentially!)
|
||||
@Test
|
||||
fun testLogging() {
|
||||
Megazord.init()
|
||||
val logs: MutableList<String> = mutableListOf()
|
||||
val threadIds = mutableSetOf<Long>()
|
||||
val threads = WeakHashMap<Thread, Long>()
|
||||
fun handler(level: Int, tag: String?, msg: String) {
|
||||
val threadId = Thread.currentThread().id
|
||||
threads.set(Thread.currentThread(), threadId)
|
||||
threadIds.add(threadId)
|
||||
val info = "Rust log from $threadId | Level: $level | tag: $tag| message: $msg"
|
||||
println(info)
|
||||
logs += info
|
||||
}
|
||||
|
||||
assert(!RustLogAdapter.isEnabled)
|
||||
|
||||
RustLogAdapter.enable { level, tagStr, msgStr ->
|
||||
handler(level, tagStr, msgStr)
|
||||
true
|
||||
}
|
||||
|
||||
// We log an informational message after initializing (but it's processed asynchronously).
|
||||
Thread.sleep(100)
|
||||
assertEquals(logs.size, 1)
|
||||
writeTestLog("Test1")
|
||||
assertEquals(logs.size, 2)
|
||||
assert(RustLogAdapter.isEnabled)
|
||||
|
||||
// Check that trying to enable again throws
|
||||
@Suppress("EmptyCatchBlock")
|
||||
try {
|
||||
RustLogAdapter.enable { _, _, _ -> true }
|
||||
} catch (e: LogAdapterCannotEnable) {
|
||||
}
|
||||
|
||||
var wasCalled = false
|
||||
|
||||
val didEnable = RustLogAdapter.tryEnable { _, _, _ ->
|
||||
wasCalled = true
|
||||
true
|
||||
}
|
||||
|
||||
assert(!didEnable)
|
||||
writeTestLog("Test2")
|
||||
|
||||
assertEquals(logs.size, 3)
|
||||
assert(!wasCalled)
|
||||
|
||||
repeat(15) {
|
||||
Thread.sleep(10)
|
||||
@Suppress("ExplicitGarbageCollectionCall")
|
||||
System.gc()
|
||||
}
|
||||
// Make sure GC can't collect our background thread (we're still using it)
|
||||
assertEquals(threads.size, 1)
|
||||
|
||||
// Adjust the max level so that the test log (which is logged at info level)
|
||||
// will not be present.
|
||||
RustLogAdapter.setMaxLevel(LogLevelFilter.WARN)
|
||||
|
||||
writeTestLog("Test3")
|
||||
|
||||
assertEquals(logs.size, 3)
|
||||
|
||||
// Make sure we can re-enable it
|
||||
RustLogAdapter.setMaxLevel(LogLevelFilter.INFO)
|
||||
writeTestLog("Test4")
|
||||
|
||||
assertEquals(logs.size, 4)
|
||||
// All the previous calls should have been run on the same background thread
|
||||
assertEquals(threadIds.size, 1)
|
||||
|
||||
RustLogAdapter.disable()
|
||||
assert(!RustLogAdapter.isEnabled)
|
||||
|
||||
// Shouldn't do anything, we disabled the log.
|
||||
writeTestLog("Test5")
|
||||
|
||||
assertEquals(logs.size, 4)
|
||||
assert(!wasCalled)
|
||||
|
||||
val didEnable2 = RustLogAdapter.tryEnable { level, tagStr, msgStr ->
|
||||
handler(level, tagStr, msgStr)
|
||||
wasCalled = true
|
||||
true
|
||||
}
|
||||
Thread.sleep(100)
|
||||
assert(didEnable2)
|
||||
assertEquals(logs.size, 5)
|
||||
|
||||
writeTestLog("Test6")
|
||||
assert(wasCalled)
|
||||
assertEquals(logs.size, 6)
|
||||
|
||||
// We called `enable` again, so we expect to have used another thread
|
||||
|
||||
// TODO: changing to indirect binding has chnged how JNA allocates threads
|
||||
// for our callbacks, and has this next line fail. We should change it back
|
||||
// once things are back to normal. Ditto for commented out lines below labeled
|
||||
// assertEquals(threadIds.size, 2) // INDIRECT
|
||||
|
||||
RustLogAdapter.disable()
|
||||
|
||||
// Check behavior of 'disable by returning false'
|
||||
RustLogAdapter.enable { level, tagStr, msgStr ->
|
||||
handler(level, tagStr, msgStr)
|
||||
// Stop after we log twice
|
||||
logs.size < 8
|
||||
}
|
||||
Thread.sleep(100)
|
||||
// Initial log emitted when we set the adapter.
|
||||
assertEquals(logs.size, 7)
|
||||
writeTestLog("Test7")
|
||||
assertEquals(logs.size, 8)
|
||||
assert(!RustLogAdapter.isEnabled)
|
||||
|
||||
// new log callback, new thread.
|
||||
// assertEquals(threadIds.size, 3) // INDIRECT
|
||||
|
||||
// Check behavior of 'disable by throw'
|
||||
RustLogAdapter.enable { level, tagStr, msgStr ->
|
||||
handler(level, tagStr, msgStr)
|
||||
if (logs.size >= 10) {
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
throw RuntimeException("Throw an exception to stop logging")
|
||||
}
|
||||
true
|
||||
}
|
||||
Thread.sleep(100)
|
||||
// Initial log emitted when we set the adapter.
|
||||
assertEquals(logs.size, 9)
|
||||
|
||||
writeTestLog("Test8")
|
||||
assertEquals(logs.size, 10)
|
||||
assert(!RustLogAdapter.isEnabled)
|
||||
|
||||
// new log callback, new thread.
|
||||
// assertEquals(threadIds.size, 4) // INDIRECT
|
||||
|
||||
// Clean up
|
||||
RustLogAdapter.disable()
|
||||
|
||||
// Make sure the GC can now collect the background threads.
|
||||
repeat(15) {
|
||||
Thread.sleep(10)
|
||||
@Suppress("ExplicitGarbageCollectionCall")
|
||||
System.gc()
|
||||
}
|
||||
// assertEquals(threads.size, 0) // INDIRECT
|
||||
assertEquals(threads.size, 1)
|
||||
}
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
import Foundation
|
||||
#if canImport(MozillaRustComponents)
|
||||
import MozillaRustComponents
|
||||
#endif
|
||||
|
||||
/// The level of a log message
|
||||
public enum LogLevel: Int32 {
|
||||
/// The log message in question is verbose information which
|
||||
/// may contain user PII.
|
||||
case trace = 2
|
||||
/// The log message in question is verbose information,
|
||||
/// but should contain no PII.
|
||||
case debug
|
||||
/// The log message is informational
|
||||
case info
|
||||
/// The log message is a warning
|
||||
case warn
|
||||
/// The log message indicates an error.
|
||||
case error
|
||||
|
||||
init(safeRawValue value: Int32) {
|
||||
if let result = LogLevel(rawValue: value) {
|
||||
self = result
|
||||
return
|
||||
}
|
||||
|
||||
if value < LogLevel.trace.rawValue {
|
||||
self = .trace
|
||||
} else {
|
||||
self = .error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum representing a maximum log level. It is used with
|
||||
/// `RustLog.shared.setLevelFilter`.
|
||||
///
|
||||
/// This is roughly equivalent to LogLevel, however contains
|
||||
/// `Off`, for filtering all logging.
|
||||
public enum LogLevelFilter: Int32 {
|
||||
/// Disable all logging
|
||||
case off
|
||||
/// Only allow `error` logs.
|
||||
case error
|
||||
/// Allow `warn` and `error` logs.
|
||||
case warn
|
||||
/// Allow `warn`, `error`, and `info` logs.
|
||||
case info
|
||||
/// Allow `warn`, `error`, `info`, and `debug` logs. The default.
|
||||
case debug
|
||||
/// Allow all logs, including those that may contain PII.
|
||||
case trace
|
||||
}
|
||||
|
||||
/// The type of the log callback. You can provide a value of this type to
|
||||
/// `RustLog.shared.enable` or `RustLog.shared.tryEnable`, and it will be called for
|
||||
/// all log messages emitted by Rust code.
|
||||
///
|
||||
/// The first argument is the level of the log. The maximum value of this can
|
||||
/// be provided using the `RustLog.shared.setLevelFilter` method.
|
||||
///
|
||||
/// The second argument is the tag, which is typically a rust module path
|
||||
/// string. It might be nil in some cases that aren't documented by the
|
||||
/// underlying rust log crate.
|
||||
///
|
||||
/// The last argument is the log message. It will not be nil.
|
||||
///
|
||||
/// This callback should return `true` to indicate everything is fine, and
|
||||
/// false if we should disable the logger. You cannot call `disable()`
|
||||
/// from inside the callback (it's protected by a dispatch queue you're
|
||||
/// already running on).
|
||||
public typealias LogCallback = (_ level: LogLevel, _ tag: String?, _ message: String) -> Bool
|
||||
|
||||
/// The public interface to Rust's logger.
|
||||
///
|
||||
/// This is a singleton, and should be used via the
|
||||
/// `shared` static member.
|
||||
public class RustLog {
|
||||
fileprivate let state = RustLogState()
|
||||
fileprivate let queue = DispatchQueue(label: "com.mozilla.appservices.rust-log")
|
||||
/// The singleton instance of RustLog
|
||||
public static let shared = RustLog()
|
||||
|
||||
private init() {}
|
||||
|
||||
/// True if the logger currently has a bound callback.
|
||||
public var isEnabled: Bool {
|
||||
return queue.sync { state.isEnabled }
|
||||
}
|
||||
|
||||
/// Set the current log callback.
|
||||
///
|
||||
/// Note that by default, after enabling the level filter
|
||||
/// will be at the `debug` level. If you want to increase or decrease it,
|
||||
/// you may use `setLevelFilter`
|
||||
///
|
||||
///
|
||||
/// See alse `tryEnable`.
|
||||
///
|
||||
/// Throws:
|
||||
///
|
||||
/// - `RustLogError.alreadyEnabled`: If we're already enabled. Explicitly disable first.
|
||||
///
|
||||
/// - `RustLogError.unexpectedError`: If the rust code panics. This shouldn't happen,
|
||||
/// but if it does, we would appreciate reports from telemetry or similar
|
||||
public func enable(_ callback: @escaping LogCallback) throws {
|
||||
try queue.sync {
|
||||
try state.enable(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the level filter (the maximum log level) of the logger.
|
||||
///
|
||||
/// Throws:
|
||||
/// - `RustLogError.unexpectedError`: If the rust code panics. This shouldn't happen,
|
||||
/// but if it does, we would appreciate reports from telemetry or similar
|
||||
public func setLevelFilter(filter: LogLevelFilter) throws {
|
||||
// Note: Doesn't need to synchronize.
|
||||
try rustCall { error in
|
||||
rc_log_adapter_set_max_level(filter.rawValue, error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable the previously set logger. This also sets the level filter to `.off`.
|
||||
///
|
||||
/// Does nothing if the logger is disabled
|
||||
public func disable() {
|
||||
queue.sync {
|
||||
state.disable()
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable the logger if possible.
|
||||
///
|
||||
/// Returns false in the cases where `enable` would throw, true otherwise.
|
||||
///
|
||||
/// If it would throw due to a panic, it also writes some information about
|
||||
/// the panic to the provided callback
|
||||
public func tryEnable(_ callback: @escaping LogCallback) -> Bool {
|
||||
return queue.sync {
|
||||
state.tryEnable(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a test message at `.info` severity.
|
||||
public func logTestMessage(message: String) {
|
||||
rc_log_adapter_test__log_msg(message)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of errors reported by RustLog. These either indicate bugs
|
||||
/// in our logging code (as in `UnexpectedError`), or usage errors
|
||||
/// (as in `AlreadyEnabled`)
|
||||
public enum RustLogError: Error {
|
||||
/// This generally means a panic occurred, or something went very wrong.
|
||||
/// We would appreciate bug reports about when these appear in the wild, if they do.
|
||||
case unexpectedError(message: String)
|
||||
|
||||
/// Error indicating that the log adapter cannot be enabled
|
||||
/// because it is already enabled.
|
||||
///
|
||||
/// This is a usage error, either `disable` it first, or
|
||||
/// use `RustLog.shared.tryEnable`
|
||||
case alreadyEnabled
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func rustCall<T>(_ callback: (UnsafeMutablePointer<RcLogError>) throws -> T) throws -> T {
|
||||
var err = RcLogError(code: 0, message: nil)
|
||||
let result = try callback(&err)
|
||||
if err.code != 0 {
|
||||
let message: String
|
||||
if let messageP = err.message {
|
||||
defer { rc_log_adapter_destroy_string(messageP) }
|
||||
message = String(cString: messageP)
|
||||
} else {
|
||||
message = "Bug: No message provided with code \(err.code)!"
|
||||
}
|
||||
throw RustLogError.unexpectedError(message: message)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// This is the function actually passed to Rust.
|
||||
private func logCallbackFunc(level: Int32, optTagP: UnsafePointer<CChar>?, msgP: UnsafePointer<CChar>) {
|
||||
guard let callback = RustLog.shared.state.callback else {
|
||||
return
|
||||
}
|
||||
let msg = String(cString: msgP)
|
||||
// Probably a better way to do this...
|
||||
let tag: String?
|
||||
if let optTagP = optTagP {
|
||||
tag = String(cString: optTagP)
|
||||
} else {
|
||||
tag = nil
|
||||
}
|
||||
RustLog.shared.queue.async {
|
||||
if !callback(LogLevel(safeRawValue: level), tag, msg) {
|
||||
RustLog.shared.state.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This implements everything, but without synchronization. It needs to be
|
||||
// guarded by a queue, which is done by the RustLog class.
|
||||
private class RustLogState {
|
||||
var adapter: OpaquePointer?
|
||||
var callback: LogCallback?
|
||||
|
||||
var isEnabled: Bool { return adapter != nil }
|
||||
|
||||
func enable(_ callback: @escaping LogCallback) throws {
|
||||
if isEnabled {
|
||||
throw RustLogError.alreadyEnabled
|
||||
}
|
||||
assert(self.callback == nil)
|
||||
self.callback = callback
|
||||
adapter = try rustCall { error in
|
||||
rc_log_adapter_create(logCallbackFunc, error)
|
||||
}
|
||||
}
|
||||
|
||||
func disable() {
|
||||
guard let adapter = adapter else {
|
||||
return
|
||||
}
|
||||
self.adapter = nil
|
||||
callback = nil
|
||||
rc_log_adapter_destroy(adapter)
|
||||
}
|
||||
|
||||
func tryEnable(_ callback: @escaping LogCallback) -> Bool {
|
||||
if isEnabled {
|
||||
return false
|
||||
}
|
||||
do {
|
||||
try enable(callback)
|
||||
return true
|
||||
} catch {
|
||||
_ = callback(.error,
|
||||
"RustLog.swift",
|
||||
"RustLog.enable failed: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <Foundation/NSObjCRuntime.h>
|
||||
|
||||
typedef struct RcLogError {
|
||||
int32_t code;
|
||||
char *_Nullable message;
|
||||
} RcLogError;
|
||||
|
||||
typedef void RustLogCallback(int32_t level,
|
||||
char const *_Nullable tag,
|
||||
char const *_Nonnull msg);
|
||||
|
||||
typedef struct RustLogAdapter RustLogAdapter;
|
||||
|
||||
RustLogAdapter *_Nullable rc_log_adapter_create(RustLogCallback *_Nonnull callback,
|
||||
RcLogError *_Nonnull out_err);
|
||||
|
||||
void rc_log_adapter_set_max_level(int32_t max_level,
|
||||
RcLogError *_Nonnull out_err);
|
||||
|
||||
void rc_log_adapter_destroy(RustLogAdapter *_Nonnull to_destroy);
|
||||
|
||||
void rc_log_adapter_test__log_msg(char const *_Nonnull msg);
|
||||
|
||||
// Only use for Error strings!
|
||||
void rc_log_adapter_destroy_string(char *_Nonnull msg);
|
|
@ -1,218 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This is the android backend for rc_log. It has a decent amount of
|
||||
//! complexity, as Rust logs can be emitted by any thread, regardless of whether
|
||||
//! or not they have an associated JVM thread. JNA's Callback class helps us
|
||||
//! here, by providing a way for mapping native threads to JVM threads.
|
||||
//! Unfortunately, naive usage of this class in a multithreaded context will be
|
||||
//! very suboptimal in terms of memory and thread usage.
|
||||
//!
|
||||
//! To avoid this, we only call into the JVM from a single thread, which we
|
||||
//! launch when initializing the logger. This thread just polls a channel
|
||||
//! listening for log messages, where a log message is an enum (`LogMessage`)
|
||||
//! that either tells it to log an item, or to stop logging all together.
|
||||
//!
|
||||
//! 1. We cannot guarantee that the callback from android lives past when the
|
||||
//! android code tells us to stop logging, so in order to be memory safe, we
|
||||
//! need to stop logging immediately when this happens. We do this using an
|
||||
//! `Arc<AtomicBool>`, used to indicate that we should stop logging.
|
||||
//!
|
||||
//! 2. There's no safe way to terminate a thread in Rust (for good reason), so
|
||||
//! the background thread must close willingly. To make sure this happens
|
||||
//! promptly (e.g. to avoid a case where we're blocked until some thread
|
||||
//! somewhere else happens to log something), we need to add something onto
|
||||
//! the log channel, hence the existence of `LogMessage::Stop`.
|
||||
//!
|
||||
//! It's important to note that because of point 1, the polling thread may
|
||||
//! have to stop prior to getting `LogMessage::Stop`. We do not want to wait
|
||||
//! for it to process whatever log messages were sent prior to being told to
|
||||
//! stop.
|
||||
|
||||
use std::{
|
||||
ffi::CString,
|
||||
os::raw::c_char,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{sync_channel, SyncSender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use crate::LogLevel;
|
||||
|
||||
/// Type of the log callback provided to us by java/swift. Takes the following
|
||||
/// arguments:
|
||||
///
|
||||
/// - Log level (an i32).
|
||||
///
|
||||
/// - Tag: a (nullable) nul terminated c string. The callback must not free this
|
||||
/// string, which is only valid until the the callback returns. If you need
|
||||
/// it past that, you must copy it into an internal buffer!
|
||||
///
|
||||
/// - Message: a (non-nullable) nul terminated c string. The callback must not free this
|
||||
/// string, which is only valid until the the callback returns. If you need
|
||||
/// it past that, you must copy it into an internal buffer!
|
||||
///
|
||||
/// and returns 0 if we should close the thread, and 1 otherwise. This is done
|
||||
/// because attempting to call `disable` from within the log callback will
|
||||
/// deadlock.
|
||||
pub type LogCallback = unsafe extern "C" fn(i32, *const c_char, *const c_char) -> u8;
|
||||
|
||||
// TODO: use serde to send this to the other thread as bincode or something,
|
||||
// rather than allocating all these strings for every message.
|
||||
struct LogRecord {
|
||||
level: LogLevel,
|
||||
tag: Option<CString>,
|
||||
message: CString,
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'b log::Record<'a>> for LogRecord {
|
||||
// XXX important! Don't log in this function!
|
||||
fn from(r: &'b log::Record<'a>) -> Self {
|
||||
let thread_id = format!("{:?}", std::thread::current().id());
|
||||
let thread_id = if thread_id.starts_with("ThreadId(") && thread_id.ends_with(')') {
|
||||
format!("t{}", &thread_id[9..(thread_id.len() - 1)])
|
||||
} else {
|
||||
thread_id
|
||||
};
|
||||
let message = format!("{} {}", thread_id, r.args());
|
||||
let level = LogLevel::from_level_and_message(r.level(), &message);
|
||||
Self {
|
||||
level,
|
||||
tag: r
|
||||
.module_path()
|
||||
.and_then(|mp| CString::new(mp.to_owned()).ok()),
|
||||
message: crate::string_to_cstring_lossy(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LogMessage {
|
||||
Stop,
|
||||
Record(LogRecord),
|
||||
}
|
||||
|
||||
pub struct LogAdapterState {
|
||||
// Thread handle for the BG thread.
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
stopped: Arc<Mutex<bool>>,
|
||||
sender: SyncSender<LogMessage>,
|
||||
}
|
||||
|
||||
pub struct LogSink {
|
||||
sender: SyncSender<LogMessage>,
|
||||
// Used locally for preventing unnecessary work after the `sender`
|
||||
// is closed. Not shared. Not required for correctness.
|
||||
disabled: AtomicBool,
|
||||
}
|
||||
|
||||
impl log::Log for LogSink {
|
||||
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
|
||||
// Really this could just be Acquire but whatever
|
||||
!self.disabled.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
// Important: we check stopped before writing, which means
|
||||
// it must be set before
|
||||
if self.disabled.load(Ordering::SeqCst) {
|
||||
// Note: `enabled` is not automatically called.
|
||||
return;
|
||||
}
|
||||
// Either the queue is full, or the receiver is closed.
|
||||
// In either case, we want to stop all logging immediately.
|
||||
if self
|
||||
.sender
|
||||
.try_send(LogMessage::Record(record.into()))
|
||||
.is_err()
|
||||
{
|
||||
self.disabled.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LogAdapterState {
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
pub fn init(callback: LogCallback) -> Self {
|
||||
// This uses a mutex (instead of an atomic bool) to avoid a race condition
|
||||
// where `stopped` gets set by another thread between when we read it and
|
||||
// when we call the callback. This way, they'll block.
|
||||
let stopped = Arc::new(Mutex::new(false));
|
||||
let (message_sender, message_recv) = sync_channel(4096);
|
||||
let handle = {
|
||||
let stopped = stopped.clone();
|
||||
thread::spawn(move || {
|
||||
// We stop if we see `Err` (which means the channel got closed,
|
||||
// which probably can't happen since the sender owned by the
|
||||
// logger will never get dropped), or if we get `LogMessage::Stop`,
|
||||
// which means we should stop processing.
|
||||
while let Ok(LogMessage::Record(record)) = message_recv.recv() {
|
||||
let LogRecord {
|
||||
tag,
|
||||
level,
|
||||
message,
|
||||
} = record;
|
||||
let tag_ptr = tag
|
||||
.as_ref()
|
||||
.map(|s| s.as_ptr())
|
||||
.unwrap_or_else(std::ptr::null);
|
||||
let msg_ptr = message.as_ptr();
|
||||
|
||||
let mut stop_guard = stopped.lock().unwrap();
|
||||
if *stop_guard {
|
||||
return;
|
||||
}
|
||||
let keep_going = unsafe { callback(level as i32, tag_ptr, msg_ptr) };
|
||||
if keep_going == 0 {
|
||||
*stop_guard = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let sink = LogSink {
|
||||
sender: message_sender.clone(),
|
||||
disabled: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
crate::settable_log::set_logger(Box::new(sink));
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
log::info!("rc_log adapter initialized!");
|
||||
Self {
|
||||
handle: Some(handle),
|
||||
stopped,
|
||||
sender: message_sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LogAdapterState {
|
||||
fn drop(&mut self) {
|
||||
{
|
||||
// It would be nice to write a log that says something like
|
||||
// "if we deadlock here it's because you tried to close the
|
||||
// log adapter from within the log callback", but, well, we
|
||||
// can't exactly log anything from here (and even if we could,
|
||||
// they'd never see it if they hit that situation)
|
||||
let mut stop_guard = self.stopped.lock().unwrap();
|
||||
*stop_guard = true;
|
||||
// We can ignore a failure here because it means either
|
||||
// - The recv is dropped, in which case we don't need to send anything
|
||||
// - The recv is completely full, in which case it will see the flag we
|
||||
// wrote into `stop_guard` soon enough anyway.
|
||||
let _ = self.sender.try_send(LogMessage::Stop);
|
||||
}
|
||||
// Wait for the calling thread to stop. This should be relatively
|
||||
// quickly unless something terrible has happened.
|
||||
if let Some(h) = self.handle.take() {
|
||||
h.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ffi_support::implement_into_ffi_by_pointer!(LogAdapterState);
|
|
@ -1,90 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
use crate::LogLevel;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
/// Type of the log callback provided to us by swift. Takes the following
|
||||
/// arguments:
|
||||
///
|
||||
/// - Log level (an i32).
|
||||
///
|
||||
/// - Tag: a (nullable) nul terminated c string. The callback must not free this
|
||||
/// string, which is only valid until the the callback returns. If you need
|
||||
/// it past that, you must copy it into an internal buffer!
|
||||
///
|
||||
/// - Message: a (non-nullable) nul terminated c string. The callback must not free this
|
||||
/// string, which is only valid until the the callback returns. If you need
|
||||
/// it past that, you must copy it into an internal buffer!
|
||||
///
|
||||
/// This is equivalent to the callback java uses **except** it cannot return 1/0
|
||||
/// for disabling. Instead, the swift bindings allow calling disable from the
|
||||
/// callback (which is more difficult for the java bindings).
|
||||
pub type LogCallback = unsafe extern "C" fn(i32, *const c_char, *const c_char);
|
||||
|
||||
pub struct LogAdapterState {
|
||||
stop: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
struct Logger {
|
||||
callback: LogCallback,
|
||||
stop: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
|
||||
!self.stop.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
if self.stop.load(Ordering::SeqCst) {
|
||||
// Note: `enabled` is not automatically called.
|
||||
return;
|
||||
}
|
||||
let tag = record
|
||||
.module_path()
|
||||
.and_then(|mp| CString::new(mp.as_bytes()).ok());
|
||||
|
||||
// TODO: use SmallVec<[u8; 4096]> or something?
|
||||
let msg_string = format!("{}", record.args());
|
||||
let level = LogLevel::from_level_and_message(record.level(), &msg_string);
|
||||
let msg_cstring = crate::string_to_cstring_lossy(msg_string);
|
||||
|
||||
let tag_ptr = tag
|
||||
.as_ref()
|
||||
.map(|s| s.as_ptr())
|
||||
.unwrap_or_else(std::ptr::null);
|
||||
let msg_ptr = msg_cstring.as_ptr() as *const c_char;
|
||||
|
||||
unsafe { (self.callback)(level as i32, tag_ptr, msg_ptr) };
|
||||
}
|
||||
}
|
||||
|
||||
impl LogAdapterState {
|
||||
pub fn init(callback: LogCallback) -> Self {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let log = Logger {
|
||||
callback,
|
||||
stop: stop.clone(),
|
||||
};
|
||||
crate::settable_log::set_logger(Box::new(log));
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
log::info!("rc_log adapter initialized!");
|
||||
Self { stop }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LogAdapterState {
|
||||
fn drop(&mut self) {
|
||||
self.stop.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
ffi_support::implement_into_ffi_by_pointer!(LogAdapterState);
|
|
@ -1,170 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This crate allows users from the other side of the FFI to hook into Rust's
|
||||
//! `log` crate, which is used by us and several of our dependencies. The
|
||||
//! primary use case is providing logs to Android and iOS in a way that is more
|
||||
//! flexible than writing to liblog (which goes to logcat, which cannot be
|
||||
//! accessed by programs on the device, short of rooting it), or stdout/stderr.
|
||||
//!
|
||||
//! See the header comment in android.rs and fallback.rs for details.
|
||||
//!
|
||||
//! It's worth noting that the log crate is rather inflexable, in that
|
||||
//! it does not allow users to change loggers after the first initialization. We
|
||||
//! work around this using our `settable_log` module.
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
// We always include both modules when doing test builds, so for test builds,
|
||||
// allow dead code.
|
||||
#![cfg_attr(test, allow(dead_code))]
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
// Import this in tests (even on non-android builds / cases where the
|
||||
// force_android feature is not enabled) so we can check that it compiles
|
||||
// easily.
|
||||
#[cfg(any(test, os = "android", feature = "force_android"))]
|
||||
pub mod android;
|
||||
// Import this in tests (even if we're building for android or force_android is
|
||||
// turned on) so we can check that it compiles easily
|
||||
#[cfg(any(test, not(any(os = "android", feature = "force_android"))))]
|
||||
pub mod ios;
|
||||
|
||||
mod settable_log;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(os = "android", feature = "force_android"))] {
|
||||
use crate::android as imp;
|
||||
} else {
|
||||
use crate::ios as imp;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn string_to_cstring_lossy(s: String) -> CString {
|
||||
let mut bytes = s.into_bytes();
|
||||
for byte in bytes.iter_mut() {
|
||||
if *byte == 0 {
|
||||
*byte = b'?';
|
||||
}
|
||||
}
|
||||
CString::new(bytes).expect("Bug in string_to_cstring_lossy!")
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[repr(i32)]
|
||||
pub enum LogLevel {
|
||||
// Android logger levels
|
||||
VERBOSE = 2,
|
||||
DEBUG = 3,
|
||||
INFO = 4,
|
||||
WARN = 5,
|
||||
ERROR = 6,
|
||||
}
|
||||
|
||||
impl LogLevel {
|
||||
/// Equivalent to the `into()` conversion but avoids reporting network
|
||||
/// errors as errors, and downgrades them into warnings.
|
||||
pub(crate) fn from_level_and_message(mut level: log::Level, msg: &str) -> Self {
|
||||
if level == log::Level::Error && msg.contains("[no-sentry]") {
|
||||
level = log::Level::Warn;
|
||||
}
|
||||
level.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<log::Level> for LogLevel {
|
||||
fn from(l: log::Level) -> Self {
|
||||
match l {
|
||||
log::Level::Trace => LogLevel::VERBOSE,
|
||||
log::Level::Debug => LogLevel::DEBUG,
|
||||
log::Level::Info => LogLevel::INFO,
|
||||
log::Level::Warn => LogLevel::WARN,
|
||||
log::Level::Error => LogLevel::ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rc_log_adapter_create(
|
||||
callback: imp::LogCallback,
|
||||
out_err: &mut ffi_support::ExternError,
|
||||
) -> *mut imp::LogAdapterState {
|
||||
ffi_support::call_with_output(out_err, || imp::LogAdapterState::init(callback))
|
||||
}
|
||||
|
||||
// Note: keep in sync with LogLevelFilter in kotlin.
|
||||
fn level_filter_from_i32(level_arg: i32) -> log::LevelFilter {
|
||||
match level_arg {
|
||||
4 => log::LevelFilter::Debug,
|
||||
3 => log::LevelFilter::Info,
|
||||
2 => log::LevelFilter::Warn,
|
||||
1 => log::LevelFilter::Error,
|
||||
// We clamp out of bounds level values.
|
||||
n if n <= 0 => log::LevelFilter::Off,
|
||||
n if n >= 5 => log::LevelFilter::Trace,
|
||||
_ => unreachable!("This is actually exhaustive"),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rc_log_adapter_set_max_level(level: i32, out_err: &mut ffi_support::ExternError) {
|
||||
ffi_support::call_with_output(out_err, || log::set_max_level(level_filter_from_i32(level)))
|
||||
}
|
||||
|
||||
// Can't use define_box_destructor because this can panic. TODO: Maybe we should
|
||||
// keep this around globally (as lazy_static or something) and basically just
|
||||
// turn it on/off in create/destroy... Might be more reliable?
|
||||
/// # Safety
|
||||
/// Unsafe because it frees it's argument.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rc_log_adapter_destroy(to_destroy: *mut imp::LogAdapterState) {
|
||||
ffi_support::abort_on_panic::call_with_output(move || {
|
||||
log::set_max_level(log::LevelFilter::Off);
|
||||
drop(Box::from_raw(to_destroy));
|
||||
settable_log::unset_logger();
|
||||
})
|
||||
}
|
||||
|
||||
// Used just to allow tests to produce logs.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rc_log_adapter_test__log_msg(msg: ffi_support::FfiStr<'_>) {
|
||||
ffi_support::abort_on_panic::call_with_output(|| {
|
||||
log::info!("testing: {}", msg.as_str());
|
||||
});
|
||||
}
|
||||
|
||||
ffi_support::define_string_destructor!(rc_log_adapter_destroy_string);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_level_msg() {
|
||||
assert_eq!(
|
||||
LogLevel::ERROR,
|
||||
LogLevel::from_level_and_message(
|
||||
log::Level::Error,
|
||||
"Rusqlite Error: The database is wrong and bad.",
|
||||
),
|
||||
"Normal errors should come through as errors",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
LogLevel::WARN,
|
||||
LogLevel::from_level_and_message(
|
||||
log::Level::Error,
|
||||
"Network Error: [no-sentry] Network error: the server is furious at you.",
|
||||
),
|
||||
"[no-sentry] errors should come through as warnings",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
LogLevel::INFO,
|
||||
LogLevel::from_level_and_message(log::Level::Info, "[no-sentry] 🙀"),
|
||||
"Everything else should be unchanged, even if it has a [no-sentry] tag",
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::{Once, RwLock};
|
||||
|
||||
use log::Log;
|
||||
|
||||
struct SettableLog {
|
||||
inner: RwLock<Option<Box<dyn Log>>>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SETTABLE_LOG: SettableLog = SettableLog {
|
||||
inner: RwLock::new(None)
|
||||
};
|
||||
}
|
||||
|
||||
impl SettableLog {
|
||||
fn set(&self, logger: Box<dyn Log>) {
|
||||
let mut write_lock = self.inner.write().unwrap();
|
||||
*write_lock = Some(logger);
|
||||
}
|
||||
|
||||
fn unset(&self) {
|
||||
let mut write_lock = self.inner.write().unwrap();
|
||||
drop(write_lock.take());
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for SettableLog {
|
||||
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
|
||||
let inner = self.inner.read().unwrap();
|
||||
if let Some(log) = &*inner {
|
||||
log.enabled(metadata)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let inner = self.inner.read().unwrap();
|
||||
if let Some(log) = &*inner {
|
||||
log.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
let inner = self.inner.read().unwrap();
|
||||
if let Some(log) = &*inner {
|
||||
log.log(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_once() {
|
||||
static INITIALIZER: Once = Once::new();
|
||||
INITIALIZER.call_once(|| {
|
||||
log::set_logger(&*SETTABLE_LOG).expect(
|
||||
"Failed to initialize SettableLog, other log implementation already initialized?",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_logger(logger: Box<dyn Log>) {
|
||||
init_once();
|
||||
SETTABLE_LOG.set(logger);
|
||||
}
|
||||
|
||||
pub fn unset_logger() {
|
||||
init_once();
|
||||
SETTABLE_LOG.unset();
|
||||
}
|
|
@ -17,7 +17,6 @@ sync_manager = { path = "../../components/sync_manager/" }
|
|||
# webext-storage = { path = "../../components/webext-storage/" }
|
||||
places = { path = "../../components/places" }
|
||||
push = { path = "../../components/push" }
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
remote_settings = { path = "../../components/remote_settings" }
|
||||
rust-log-forwarder = { path = "../../components/support/rust-log-forwarder" }
|
||||
viaduct = { path = "../../components/viaduct" }
|
||||
|
|
|
@ -16,9 +16,6 @@ pub use logins;
|
|||
pub use nimbus;
|
||||
pub use places;
|
||||
pub use push;
|
||||
// TODO: Drop this dependency once android-components switches to using `rust_log_forwarder` for
|
||||
// log forwarding.
|
||||
pub use rc_log_ffi;
|
||||
pub use remote_settings;
|
||||
pub use rust_log_forwarder;
|
||||
pub use suggest;
|
||||
|
|
|
@ -9,7 +9,6 @@ license = "MPL-2.0"
|
|||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
rc_log_ffi = { path = "../../components/rc_log" }
|
||||
rust-log-forwarder = { path = "../../components/support/rust-log-forwarder" }
|
||||
viaduct = { path = "../../components/viaduct" }
|
||||
viaduct-reqwest = { path = "../../components/support/viaduct-reqwest" }
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// This is the "umbrella header" for our combined Rust code library.
|
||||
// It needs to import all of the individual headers.
|
||||
|
||||
#import "RustLogFFI.h"
|
||||
#import "RustViaductFFI.h"
|
||||
#import "autofillFFI.h"
|
||||
#import "crashtestFFI.h"
|
||||
|
|
|
@ -14,10 +14,8 @@
|
|||
1B3BC94327B1D63B00229CF6 /* PlacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC54027AE065300DAFEF2 /* PlacesTests.swift */; };
|
||||
1B3BC94527B1D6A500229CF6 /* SyncUnlockInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC5B027AE11AA00DAFEF2 /* SyncUnlockInfo.swift */; };
|
||||
1B3BC94627B1D6A500229CF6 /* ResultError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC5B127AE11AA00DAFEF2 /* ResultError.swift */; };
|
||||
1B3BC94727B1D73500229CF6 /* LogTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC53F27AE065300DAFEF2 /* LogTest.swift */; };
|
||||
1B3BC94827B1D73700229CF6 /* LoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC53B27AE065300DAFEF2 /* LoginsTests.swift */; };
|
||||
1B3BC94B27B1D79B00229CF6 /* LoginsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC58627AE09C600DAFEF2 /* LoginsStorage.swift */; };
|
||||
1B3BC94D27B1D7B700229CF6 /* RustLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC55327AE076200DAFEF2 /* RustLog.swift */; };
|
||||
1B3BC94F27B1D92800229CF6 /* NimbusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC53E27AE065300DAFEF2 /* NimbusTests.swift */; };
|
||||
1B3BC95027B1D92A00229CF6 /* NimbusFeatureVariablesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC54427AE065300DAFEF2 /* NimbusFeatureVariablesTests.swift */; };
|
||||
1B3BC95127B1D93100229CF6 /* CrashTestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBAC54327AE065300DAFEF2 /* CrashTestTests.swift */; };
|
||||
|
@ -152,14 +150,11 @@
|
|||
1BBAC53B27AE065300DAFEF2 /* LoginsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginsTests.swift; path = MozillaTestServicesTests/LoginsTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC53C27AE065300DAFEF2 /* FxAccountMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FxAccountMocks.swift; path = MozillaTestServicesTests/FxAccountMocks.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC53E27AE065300DAFEF2 /* NimbusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NimbusTests.swift; path = MozillaTestServicesTests/NimbusTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC53F27AE065300DAFEF2 /* LogTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LogTest.swift; path = MozillaTestServicesTests/LogTest.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC54027AE065300DAFEF2 /* PlacesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlacesTests.swift; path = MozillaTestServicesTests/PlacesTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC54127AE065300DAFEF2 /* FxAccountManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FxAccountManagerTests.swift; path = MozillaTestServicesTests/FxAccountManagerTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC54227AE065300DAFEF2 /* NimbusMessagingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NimbusMessagingTests.swift; path = MozillaTestServicesTests/NimbusMessagingTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC54327AE065300DAFEF2 /* CrashTestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CrashTestTests.swift; path = MozillaTestServicesTests/CrashTestTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC54427AE065300DAFEF2 /* NimbusFeatureVariablesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NimbusFeatureVariablesTests.swift; path = MozillaTestServicesTests/NimbusFeatureVariablesTests.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC55327AE076200DAFEF2 /* RustLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RustLog.swift; path = ../../../components/rc_log/ios/RustLog.swift; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC55427AE076200DAFEF2 /* RustLogFFI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RustLogFFI.h; path = ../../../components/rc_log/ios/RustLogFFI.h; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC56127AE085F00DAFEF2 /* PersistedFirefoxAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PersistedFirefoxAccount.swift; path = "../../../components/fxa-client/ios/FxAClient/PersistedFirefoxAccount.swift"; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC56227AE085F00DAFEF2 /* FxAccountDeviceConstellation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FxAccountDeviceConstellation.swift; path = "../../../components/fxa-client/ios/FxAClient/FxAccountDeviceConstellation.swift"; sourceTree = SOURCE_ROOT; };
|
||||
1BBAC56427AE085F00DAFEF2 /* KeychainWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KeychainWrapper.swift; path = "../../../components/fxa-client/ios/FxAClient/MZKeychain/KeychainWrapper.swift"; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -369,7 +364,6 @@
|
|||
1BBAC5AF27AE112D00DAFEF2 /* Sync15 */,
|
||||
1BBAC58027AE099B00DAFEF2 /* Logins */,
|
||||
1BBAC55B27AE082400DAFEF2 /* FxAClient */,
|
||||
1BBAC55227AE073A00DAFEF2 /* RustLog */,
|
||||
);
|
||||
path = MozillaTestServices;
|
||||
sourceTree = "<group>";
|
||||
|
@ -383,7 +377,6 @@
|
|||
1BBAC53C27AE065300DAFEF2 /* FxAccountMocks.swift */,
|
||||
1BBAC54227AE065300DAFEF2 /* NimbusMessagingTests.swift */,
|
||||
1BBAC53B27AE065300DAFEF2 /* LoginsTests.swift */,
|
||||
1BBAC53F27AE065300DAFEF2 /* LogTest.swift */,
|
||||
1BBAC54427AE065300DAFEF2 /* NimbusFeatureVariablesTests.swift */,
|
||||
1BBAC53E27AE065300DAFEF2 /* NimbusTests.swift */,
|
||||
39083AAA29561E2400FDD302 /* OperationTests.swift */,
|
||||
|
@ -396,16 +389,6 @@
|
|||
path = MozillaTestServicesTests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
1BBAC55227AE073A00DAFEF2 /* RustLog */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1BBAC55327AE076200DAFEF2 /* RustLog.swift */,
|
||||
1BBAC55427AE076200DAFEF2 /* RustLogFFI.h */,
|
||||
);
|
||||
name = RustLog;
|
||||
path = ../../../../components/rc_log/ios;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1BBAC55B27AE082400DAFEF2 /* FxAClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -865,7 +848,6 @@
|
|||
1BF50F1027B1E17B00A9C8A5 /* FxAccountStorage.swift in Sources */,
|
||||
39EE00FD29F6DB2A001E7758 /* ArgumentProcessor.swift in Sources */,
|
||||
1BF50F0E27B1E17B00A9C8A5 /* FxAccountLogging.swift in Sources */,
|
||||
1B3BC94D27B1D7B700229CF6 /* RustLog.swift in Sources */,
|
||||
1BF50F1627B1E18000A9C8A5 /* KeychainWrapper.swift in Sources */,
|
||||
1BF50F1227B1E17B00A9C8A5 /* FxAccountManager.swift in Sources */,
|
||||
39F5D7642956161E004E2384 /* Operation+.swift in Sources */,
|
||||
|
@ -898,7 +880,6 @@
|
|||
39EE00FF29F6DBBA001E7758 /* NimbusArgumentProcessorTests.swift in Sources */,
|
||||
1BF50F1A27B1E19800A9C8A5 /* FxAccountMocks.swift in Sources */,
|
||||
1B3BC95127B1D93100229CF6 /* CrashTestTests.swift in Sources */,
|
||||
1B3BC94727B1D73500229CF6 /* LogTest.swift in Sources */,
|
||||
F85ED649299C1F49005EEF36 /* RustSyncTelemetryPingTests.swift in Sources */,
|
||||
3937440A29E43B8800726A72 /* EventStoreTests.swift in Sources */,
|
||||
1B3BC94327B1D63B00229CF6 /* PlacesTests.swift in Sources */,
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/* 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/. */
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import MozillaTestServices
|
||||
|
||||
class LogTests: XCTestCase {
|
||||
func writeTestLog(_ message: String) {
|
||||
RustLog.shared.logTestMessage(message: message)
|
||||
// Wait for the log to come in. Cludgey but good enough.
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
// Force us to synchronize on the queue.
|
||||
_ = RustLog.shared.isEnabled
|
||||
}
|
||||
|
||||
func testLogging() {
|
||||
var logs: [(LogLevel, String?, String)] = []
|
||||
|
||||
assert(!RustLog.shared.isEnabled)
|
||||
|
||||
try! RustLog.shared.enable { level, tag, msg in
|
||||
let info = "Rust | Level: \(level) | tag: \(String(describing: tag)) | message: \(msg)"
|
||||
print(info)
|
||||
logs.append((level, tag, msg))
|
||||
return true
|
||||
}
|
||||
|
||||
// We log an informational message after initializing (but it's processed asynchronously).
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
XCTAssert(RustLog.shared.isEnabled)
|
||||
XCTAssertEqual(logs.count, 1)
|
||||
writeTestLog("Test1")
|
||||
XCTAssertEqual(logs.count, 2)
|
||||
do {
|
||||
try RustLog.shared.enable { _, _, _ in true }
|
||||
XCTFail("Enable should fail")
|
||||
} catch let error as RustLogError {
|
||||
switch error {
|
||||
case .alreadyEnabled:
|
||||
print("enable failed as it should")
|
||||
default:
|
||||
XCTFail("Wrong RustLogError: \(error)")
|
||||
}
|
||||
} catch {
|
||||
XCTFail("Wrong error: \(error)")
|
||||
}
|
||||
// tryEnable should return false
|
||||
XCTAssert(!RustLog.shared.tryEnable { _, _, _ in true })
|
||||
|
||||
// Adjust the max level so that the test log (which is logged at info level)
|
||||
// will not be present.
|
||||
try! RustLog.shared.setLevelFilter(filter: .warn)
|
||||
writeTestLog("Test2")
|
||||
// Should not increase
|
||||
XCTAssertEqual(logs.count, 2)
|
||||
try! RustLog.shared.setLevelFilter(filter: .info)
|
||||
writeTestLog("Test3")
|
||||
XCTAssertEqual(logs.count, 3)
|
||||
|
||||
RustLog.shared.disable()
|
||||
XCTAssert(!RustLog.shared.isEnabled)
|
||||
|
||||
// Shouldn't do anything, we disabled the log.
|
||||
writeTestLog("Test4")
|
||||
|
||||
XCTAssertEqual(logs.count, 3)
|
||||
var counter = 0
|
||||
let didEnable = RustLog.shared.tryEnable { level, tag, msg in
|
||||
let info = "Rust | Level: \(level) | tag: \(String(describing: tag)) | message: \(msg)"
|
||||
print(info)
|
||||
logs.append((level, tag, msg))
|
||||
counter += 1
|
||||
if counter == 3 {
|
||||
// Test disabling from inside log callback
|
||||
print("Disabling log from inside callback")
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
XCTAssert(didEnable)
|
||||
// Wait for the 'we enabled logging' log to come in asynchronously
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
XCTAssert(RustLog.shared.isEnabled)
|
||||
XCTAssertEqual(logs.count, 4)
|
||||
|
||||
writeTestLog("Test5")
|
||||
XCTAssertEqual(logs.count, 5)
|
||||
|
||||
writeTestLog("Test6")
|
||||
XCTAssertEqual(logs.count, 6)
|
||||
// Should be disabled now,
|
||||
XCTAssert(!RustLog.shared.isEnabled)
|
||||
}
|
||||
}
|
|
@ -142,7 +142,6 @@ mkdir -p "$COMMON/Headers"
|
|||
# First we move the files that are common between both
|
||||
# firefox-ios and Focus
|
||||
cp "$WORKING_DIR/$FRAMEWORK_NAME.h" "$COMMON/Headers"
|
||||
cp "$REPO_ROOT/components/rc_log/ios/RustLogFFI.h" "$COMMON/Headers"
|
||||
cp "$REPO_ROOT/components/viaduct/ios/RustViaductFFI.h" "$COMMON/Headers"
|
||||
$CARGO uniffi-bindgen generate "$REPO_ROOT/components/remote_settings/src/remote_settings.udl" -l swift -o "$COMMON/Headers"
|
||||
$CARGO uniffi-bindgen generate "$REPO_ROOT/components/nimbus/src/nimbus.udl" -l swift -o "$COMMON/Headers"
|
||||
|
|
|
@ -9,7 +9,6 @@ license = "MPL-2.0"
|
|||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
rc_log_ffi = { path = "../../../components/rc_log" }
|
||||
rust-log-forwarder = { path = "../../../components/support/rust-log-forwarder" }
|
||||
viaduct = { path = "../../../components/viaduct" }
|
||||
viaduct-reqwest = { path = "../../../components/support/viaduct-reqwest" }
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// This is the "umbrella header" for our combined Rust code library.
|
||||
// It needs to import all of the individual headers.
|
||||
|
||||
#import "RustLogFFI.h"
|
||||
#import "RustViaductFFI.h"
|
||||
#import "nimbusFFI.h"
|
||||
#import "errorFFI.h"
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
pub use error_support;
|
||||
pub use nimbus;
|
||||
pub use rc_log_ffi;
|
||||
pub use remote_settings;
|
||||
pub use rust_log_forwarder;
|
||||
pub use viaduct_reqwest;
|
||||
|
|
|
@ -14,9 +14,6 @@ pub use logins;
|
|||
pub use nimbus;
|
||||
pub use places;
|
||||
pub use push;
|
||||
// TODO: Drop this dependency once firefox-ios switches to using `rust_log_forwarder` for log
|
||||
// forwarding.
|
||||
pub use rc_log_ffi;
|
||||
pub use remote_settings;
|
||||
pub use rust_log_forwarder;
|
||||
pub use suggest;
|
||||
|
|
|
@ -44,14 +44,12 @@ SOURCE_TO_COPY = [
|
|||
"components/places/ios/Places",
|
||||
"components/sync15/ios/Sync15",
|
||||
"components/sync_manager/ios/SyncManager",
|
||||
"components/rc_log/ios/*",
|
||||
"components/viaduct/ios/*",
|
||||
]
|
||||
|
||||
# List of udl_paths to generate bindings for
|
||||
FOCUS_SOURCE_TO_COPY = [
|
||||
"components/nimbus/ios/Nimbus",
|
||||
"components/rc_log/ios/*",
|
||||
"components/viaduct/ios/*",
|
||||
]
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ existing_tasks:
|
|||
module-build-places: CjxF6i5XTUuue3N6hur76A
|
||||
module-build-push: Eb1ryYa0TQas2d6L8shrKg
|
||||
module-build-rust-log-forwarder: PavZP7KCQUa6eIiH7rcguw
|
||||
module-build-rustlog: DzsCxDB2RfmjXjSb9sXUcg
|
||||
module-build-sync15: c-mAP_Z5Tt-jep2NqUVEPQ
|
||||
module-build-syncmanager: aO_QpDq_RoOcNf56oxDtug
|
||||
module-build-tabs: d_M2TbtARdeqZdNuLDGMxA
|
||||
|
|
|
@ -26,7 +26,6 @@ existing_tasks:
|
|||
module-build-places: CjxF6i5XTUuue3N6hur76A
|
||||
module-build-push: Eb1ryYa0TQas2d6L8shrKg
|
||||
module-build-rust-log-forwarder: PavZP7KCQUa6eIiH7rcguw
|
||||
module-build-rustlog: DzsCxDB2RfmjXjSb9sXUcg
|
||||
module-build-sync15: c-mAP_Z5Tt-jep2NqUVEPQ
|
||||
module-build-syncmanager: aO_QpDq_RoOcNf56oxDtug
|
||||
module-build-tabs: d_M2TbtARdeqZdNuLDGMxA
|
||||
|
|
Загрузка…
Ссылка в новой задаче