diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd75a5b91..dc41a998ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,16 @@ -## 1.17.0-SNAPSHOT (YYYY-MM-DD) +## 2.0.0-SNAPSHOT (YYYY-MM-DD) [!NOTE] This release will bump the Realm file format from version 23 to 24. Opening a file with an older format will automatically upgrade it from file format v10. If you want to upgrade from an earlier file format version you will have to use Realm Kotlin v1.13.1 or earlier. Downgrading to a previous file format is not possible. ### Breaking changes -* None. +* Removed property `RealmLog.level`. Log levels can be set with `RealmLog.setLevel`. (Issue [#1691](https://github.com/realm/realm-kotlin/issues/1691) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1038)) +* Removed `LogConfiguration`. Log levels and custom loggers can be set with `RealmLog`. (Issue [#1691](https://github.com/realm/realm-kotlin/issues/1691) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1038)) ### Enhancements * Support for RealmLists and RealmDictionaries in `RealmAny`. (Issue [#1434](https://github.com/realm/realm-kotlin/issues/1434)) * Optimized `RealmList.indexOf()` and `RealmList.contains()` using Core implementation of operations instead of iterating elements and comparing them in Kotlin. (Issue [#1625](https://github.com/realm/realm-kotlin/pull/1666) [RKOTLIN-995](https://jira.mongodb.org/browse/RKOTLIN-995)). +* Add support for filtering logs by category. (Issue [#1691](https://github.com/realm/realm-kotlin/issues/1691) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1038)) ### Fixed * None. diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 819ab881c4..4ef9f452bf 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -62,7 +62,7 @@ val HOST_OS: OperatingSystem = findHostOs() object Realm { val ciBuild = (System.getenv("CI") != null) - const val version = "1.17.0-SNAPSHOT" + const val version = "2.0.0-SNAPSHOT" const val group = "io.realm.kotlin" const val projectUrl = "https://realm.io" const val pluginPortalId = "io.realm.kotlin" diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt index a17b3e5698..c816735cad 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt @@ -43,7 +43,7 @@ interface SyncSessionTransferCompletionCallback { interface LogCallback { // Passes core log levels as shorts to avoid unnecessary jumping between the SDK and JNI - fun log(logLevel: Short, category: String?, message: String?) + fun log(logLevel: Short, categoryValue: String, message: String?) } interface SyncBeforeClientResetHandler { diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 1602477b97..0af5ca820b 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -617,6 +617,12 @@ expect object RealmInterop { fun realm_set_log_level(level: CoreLogLevel) + fun realm_set_log_level_category(category: String, level: CoreLogLevel) + + fun realm_get_log_level_category(category: String): CoreLogLevel + + fun realm_get_category_names(): List + fun realm_app_config_set_metadata_mode( appConfig: RealmAppConfigurationPointer, metadataMode: MetadataMode diff --git a/packages/cinterop/src/jvm/jni/java_class_global_def.hpp b/packages/cinterop/src/jvm/jni/java_class_global_def.hpp index 3979de66a1..f3c32ab128 100644 --- a/packages/cinterop/src/jvm/jni/java_class_global_def.hpp +++ b/packages/cinterop/src/jvm/jni/java_class_global_def.hpp @@ -45,6 +45,7 @@ class JavaClassGlobalDef { JavaClassGlobalDef(JNIEnv* env) : m_java_util_hashmap(env, "java/util/HashMap", false) , m_java_lang_int(env, "java/lang/Integer", false) + , m_java_lang_string(env, "java/lang/String", false) , m_kotlin_jvm_functions_function0(env, "kotlin/jvm/functions/Function0", false) , m_kotlin_jvm_functions_function1(env, "kotlin/jvm/functions/Function1", false) , m_io_realm_kotlin_internal_interop_sync_network_transport(env, "io/realm/kotlin/internal/interop/sync/NetworkTransport", false) @@ -74,6 +75,7 @@ class JavaClassGlobalDef { jni_util::JavaClass m_java_util_hashmap; jni_util::JavaClass m_java_lang_int; + jni_util::JavaClass m_java_lang_string; jni_util::JavaClass m_kotlin_jvm_functions_function0; jni_util::JavaClass m_kotlin_jvm_functions_function1; jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_network_transport; @@ -133,6 +135,11 @@ class JavaClassGlobalDef { return env->NewObject(instance()->m_java_lang_int, init, value); } + inline static const jni_util::JavaClass& java_lang_string() + { + return instance()->m_java_lang_string; + } + inline static const jni_util::JavaClass& network_transport_class() { return instance()->m_io_realm_kotlin_internal_interop_sync_network_transport; diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 8dcecde28e..b0a25ca725 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -1365,9 +1365,21 @@ actual object RealmInterop { realmc.realm_set_log_level(level.priority) } + actual fun realm_set_log_level_category(category: String, level: CoreLogLevel) { + realmc.realm_set_log_level_category(category, level.priority) + } + + actual fun realm_get_log_level_category(category: String): CoreLogLevel = + CoreLogLevel.valueFromPriority(realmc.realm_get_log_level_category(category).toShort()) + + actual fun realm_get_category_names(): List { + val names: Array = realmc.realm_get_log_category_names() as Array + return names.asList() + } + actual fun realm_app_config_set_metadata_mode( appConfig: RealmAppConfigurationPointer, - metadataMode: MetadataMode + metadataMode: MetadataMode, ) { realmc.realm_app_config_set_metadata_mode( appConfig.cptr(), diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 805a713265..d360bf7981 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -15,6 +15,7 @@ */ // TODO https://github.com/realm/realm-kotlin/issues/889 @file:Suppress("TooGenericExceptionThrown", "TooGenericExceptionCaught") +@file:OptIn(ExperimentalForeignApi::class) package io.realm.kotlin.internal.interop @@ -54,6 +55,7 @@ import kotlinx.cinterop.CPointerVar import kotlinx.cinterop.CPointerVarOf import kotlinx.cinterop.CValue import kotlinx.cinterop.CVariable +import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.LongVar import kotlinx.cinterop.MemScope import kotlinx.cinterop.StableRef @@ -2511,7 +2513,7 @@ actual object RealmInterop { realm_wrapper.realm_set_log_callback( staticCFunction { userData, category, logLevel, message -> val userDataLogCallback = safeUserData(userData) - userDataLogCallback.log(logLevel.toShort(), category?.toKString(), message?.toKString()) + userDataLogCallback.log(logLevel.toShort(), category!!.toKString(), message?.toKString()) }, StableRef.create(callback).asCPointer(), staticCFunction { userData -> disposeUserData<() -> LogCallback>(userData) } @@ -2522,9 +2524,28 @@ actual object RealmInterop { realm_wrapper.realm_set_log_level(level.priority.toUInt()) } + actual fun realm_set_log_level_category(category: String, level: CoreLogLevel) { + realm_wrapper.realm_set_log_level_category(category, level.priority.toUInt()) + } + + actual fun realm_get_log_level_category(category: String): CoreLogLevel = + CoreLogLevel.valueFromPriority(realm_wrapper.realm_get_log_level_category(category).toShort()) + + actual fun realm_get_category_names(): List { + memScoped { + val namesCount = realm_wrapper.realm_get_category_names(0u, null) + val namesBuffer = allocArray>(namesCount.toInt()) + realm_wrapper.realm_get_category_names(namesCount, namesBuffer) + + return List(namesCount.toInt()) { + namesBuffer[it].safeKString() + } + } + } + actual fun realm_app_config_set_metadata_mode( appConfig: RealmAppConfigurationPointer, - metadataMode: MetadataMode + metadataMode: MetadataMode, ) { realm_wrapper.realm_app_config_set_metadata_mode( appConfig.cptr(), diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index df18021fc7..b3b0f6dd49 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -960,8 +960,8 @@ realm_sync_socket_t* realm_sync_websocket_new(int64_t sync_client_config_ptr, jo // *** END - WebSocket Client (Platform Networking) *** // void set_log_callback(jobject log_callback) { -auto jenv = get_env(true); -realm_set_log_callback([](void *userdata, const char* category, realm_log_level_e level, const char *message) { +auto jenv = get_env(false); +realm_set_log_callback([](void *userdata, const char *category, realm_log_level_e level, const char *message) { auto log_callback = static_cast(userdata); auto jenv = get_env(true); @@ -970,7 +970,7 @@ realm_set_log_callback([](void *userdata, const char* category, realm_log_level_ static JavaMethod log_method(jenv, JavaClassGlobalDef::log_callback(), "log", - "(SLjava/lang/String;Ljava/lang/String;)V"); + "(SLjava/lang/String;Ljava/lang/String;)V"); push_local_frame(jenv, 2); jenv->CallVoidMethod(log_callback, log_method, java_level, to_jstring(jenv, category), to_jstring(jenv, message)); @@ -1370,3 +1370,23 @@ realm_class_info_t_cleanup(realm_class_info_t * value) { delete[] value->primary_key; delete[] value->name; } + +jobjectArray realm_get_log_category_names() { + JNIEnv* env = get_env(true); + + size_t namesCount = realm_get_category_names(0, nullptr); + + const char** category_names = new const char*[namesCount]; + realm_get_category_names(namesCount, category_names); + + auto array = env->NewObjectArray(namesCount, JavaClassGlobalDef::java_lang_string(), nullptr); + + for(size_t i = 0; i < namesCount; i++) { + jstring string = env->NewStringUTF(category_names[i]); + env->SetObjectArrayElement(array, i, string); + } + + delete[] category_names; + + return array; +} diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index 2fae610c7b..c1ea28ed47 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -51,7 +51,7 @@ realm_http_transport_t* realm_network_transport_new(jobject network_transport); void -set_log_callback(jobject log_callbac); +set_log_callback(jobject log_callback); realm_scheduler_t* realm_create_scheduler(jobject dispatchScheduler); @@ -161,4 +161,6 @@ bool realm_sync_websocket_message(int64_t observer_ptr, jbyteArray data, size_t void realm_sync_websocket_closed(int64_t observer_ptr, bool was_clean, int error_code, const char* reason); +jobjectArray realm_get_log_category_names(); + #endif //TEST_REALM_API_HELPERS_H diff --git a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/LogCatLogger.kt b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/LogCatLogger.kt index 022e152e38..dc6129d2ab 100644 --- a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/LogCatLogger.kt +++ b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/LogCatLogger.kt @@ -16,11 +16,14 @@ package io.realm.kotlin.internal.platform import android.util.Log +import io.realm.kotlin.internal.messageWithCategory +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import java.io.PrintWriter import java.io.StringWriter import java.util.Locale +import kotlin.math.min /** * Create a logger that outputs to Android LogCat. @@ -29,13 +32,22 @@ import java.util.Locale * for message creation and formatting */ internal class LogCatLogger( - override val tag: String = "REALM", - override val level: LogLevel + private val tag: String, ) : RealmLogger { - override fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { + override fun log( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) { val priority: Int = level.priority - val logMessage: String = prepareLogMessage(throwable, message, *args) + val logMessage: String = prepareLogMessage( + throwable = throwable, + message = messageWithCategory(category, message), + args = *args + ) // Short circuit if message can fit into a single line in LogCat if (logMessage.length < MAX_LOG_LENGTH) { @@ -50,7 +62,7 @@ internal class LogCatLogger( var newline = logMessage.indexOf('\n', i) newline = if (newline != -1) newline else length do { - val end = Math.min(newline, i + MAX_LOG_LENGTH) + val end = min(newline, i + MAX_LOG_LENGTH) val part = logMessage.substring(i, end) printMessage(priority, part) i = end diff --git a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt index 166b961a13..45291eeabd 100644 --- a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt +++ b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt @@ -5,7 +5,6 @@ import io.realm.kotlin.internal.RealmInitializer import io.realm.kotlin.internal.RealmInstantImpl import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions -import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant import java.io.FileNotFoundException @@ -37,8 +36,8 @@ public actual fun assetFileAsStream(assetFilename: String): InputStream = try { } // Returns the default logger for the platform -public actual fun createDefaultSystemLogger(tag: String, logLevel: LogLevel): RealmLogger = - LogCatLogger(tag, logLevel) +public actual fun createDefaultSystemLogger(tag: String): RealmLogger = + LogCatLogger(tag) public actual fun currentTime(): RealmInstant { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt index daf8e6a42f..cd261b23bf 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Configuration.kt @@ -21,9 +21,6 @@ import io.realm.kotlin.internal.MISSING_PLUGIN_MESSAGE import io.realm.kotlin.internal.REALM_FILE_EXTENSION import io.realm.kotlin.internal.platform.PATH_SEPARATOR import io.realm.kotlin.internal.realmObjectCompanionOrNull -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher import kotlin.reflect.KClass @@ -74,23 +71,6 @@ public fun interface InitialDataCallback { public fun MutableRealm.write() } -/** - * Configuration for log events created by a Realm instance. - */ -@Deprecated("Use io.realm.kotlin.log.RealmLog instead.") -public data class LogConfiguration( - /** - * The [LogLevel] for which all log events of equal or higher priority will be reported. - */ - public val level: LogLevel, - - /** - * Any loggers to install. They will receive all log events with a priority equal to or higher - * than the value defined in [LogConfiguration.level]. - */ - public val loggers: List -) - /** * Configuration for pre-bundled asset files used as initial state of the realm file. */ @@ -126,12 +106,6 @@ public interface Configuration { */ public val schema: Set> - /** - * The log configuration used for the realm instance. - */ - @Deprecated("Use io.realm.kotlin.log.RealmLog instead.") - public val log: LogConfiguration - /** * Maximum number of active versions. * @@ -226,9 +200,6 @@ public interface Configuration { // 'name' must be nullable as it is optional when getting SyncClient's default path! protected abstract var name: String? - protected var logLevel: LogLevel = RealmLog.level - protected var appConfigLoggers: List = listOf() - protected var realmConfigLoggers: List = listOf() protected var maxNumberOfActiveVersions: Long = Long.MAX_VALUE protected var notificationDispatcher: CoroutineDispatcher? = null protected var writeDispatcher: CoroutineDispatcher? = null @@ -282,23 +253,6 @@ public interface Configuration { this.maxNumberOfActiveVersions = maxVersions } as S - /** - * Configure how Realm will report log events. - * - * @param level all events at this level or higher will be reported. - * @param customLoggers any custom loggers to send log events to. A default system logger is - * installed by default that will redirect to the common logging framework on the platform, - * i.e. LogCat on Android and NSLog on iOS. - */ - @Deprecated("Use io.realm.kotlin.log.RealmLog instead.") - public open fun log( - level: LogLevel = LogLevel.WARN, - customLoggers: List = emptyList() - ): S = apply { - this.logLevel = level - this.realmConfigLoggers = customLoggers - } as S - /** * Dispatcher on which Realm notifications are run. It is possible to listen for changes to * Realm objects from any thread, but the underlying logic will run on this dispatcher diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt index 4b46f62364..7ef592841c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/RealmConfiguration.kt @@ -20,8 +20,6 @@ import io.realm.kotlin.internal.ContextLogger import io.realm.kotlin.internal.RealmConfigurationImpl import io.realm.kotlin.internal.platform.appFilesDirectory import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.migration.AutomaticSchemaMigration import io.realm.kotlin.migration.RealmMigration import io.realm.kotlin.types.TypedRealmObject @@ -150,7 +148,7 @@ public interface RealmConfiguration : Configuration { override fun build(): RealmConfiguration { verifyConfig() - val realmLogger = ContextLogger("Sdk") + val realmLogger = ContextLogger() // Sync configs might not set 'name' but local configs always do, therefore it will // never be null here @@ -167,19 +165,10 @@ public interface RealmConfiguration : Configuration { } else { CoroutineDispatcherFactory.managed("writer-$fileName") } - - // Configure logging during creation of a (Realm/Sync)Configuration to keep old behavior - // for configuring logging. This should be removed when `LogConfiguration` is removed. - RealmLog.level = logLevel - realmConfigLoggers.forEach { RealmLog.add(it) } - @Suppress("invisible_reference", "invisible_member") - val allLoggers: List = listOf(RealmLog.systemLoggerInstalled).filterNotNull() + realmConfigLoggers - return RealmConfigurationImpl( directory, fileName, schema, - LogConfiguration(logLevel, allLoggers), maxNumberOfActiveVersions, notificationDispatcherFactory, writerDispatcherFactory, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt index 95942392a4..6b2d3a234a 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.CompactOnLaunchCallback import io.realm.kotlin.InitialDataCallback import io.realm.kotlin.InitialRealmFileConfiguration -import io.realm.kotlin.LogConfiguration import io.realm.kotlin.dynamic.DynamicMutableRealm import io.realm.kotlin.dynamic.DynamicMutableRealmObject import io.realm.kotlin.dynamic.DynamicRealm @@ -53,7 +52,6 @@ public open class ConfigurationImpl( directory: String, name: String, schema: Set>, - logConfig: LogConfiguration, maxNumberOfActiveVersions: Long, notificationDispatcher: CoroutineDispatcherFactory, writeDispatcher: CoroutineDispatcherFactory, @@ -67,7 +65,7 @@ public open class ConfigurationImpl( override val isFlexibleSyncConfiguration: Boolean, inMemory: Boolean, initialRealmFileConfiguration: InitialRealmFileConfiguration?, - logger: ContextLogger + override val logger: ContextLogger ) : InternalConfiguration { final override val path: String @@ -76,16 +74,12 @@ public open class ConfigurationImpl( final override val schema: Set> - final override val log: LogConfiguration - final override val maxNumberOfActiveVersions: Long final override val schemaVersion: Long final override val schemaMode: SchemaMode - override val logger: ContextLogger = logger - override val encryptionKey: ByteArray? get(): ByteArray? = userEncryptionKey @@ -138,7 +132,6 @@ public open class ConfigurationImpl( this.name = name this.schema = schema this.mapOfKClassWithCompanion = schema.associateWith { realmObjectCompanionOrThrow(it) } - this.log = logConfig this.maxNumberOfActiveVersions = maxNumberOfActiveVersions this.notificationDispatcherFactory = notificationDispatcher this.writeDispatcherFactory = writeDispatcher diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ContextLogger.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ContextLogger.kt index 854d6321bf..4b4231a8fa 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ContextLogger.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ContextLogger.kt @@ -2,6 +2,7 @@ package io.realm.kotlin.internal import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog +import io.realm.kotlin.log.SdkLogCategory /** * Internal logger class used to inject context aware information into log message @@ -80,11 +81,29 @@ public class ContextLogger(public val context: String? = null) { doLog(LogLevel.WTF, null, { contextPrefix + message }, *args) } + private inline fun checkPriority( + level: LogLevel, + block: () -> Unit, + ) { + if (level.priority >= RealmLog.sdkLogLevel.priority) { + block() + } + } + private inline fun doLog(level: LogLevel, throwable: Throwable?) { - RealmLog.doLog(level, throwable, null) + checkPriority(level) { + RealmLog.doLog(SdkLogCategory, level, throwable, null) + } } - private inline fun doLog(level: LogLevel, throwable: Throwable?, message: () -> String?, vararg args: Any?) { - RealmLog.doLog(level, throwable, message, *args) + private inline fun doLog( + level: LogLevel, + throwable: Throwable?, + message: () -> String?, + vararg args: Any?, + ) { + checkPriority(level) { + RealmLog.doLog(SdkLogCategory, level, throwable, message(), *args) + } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/LogUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/LogUtils.kt new file mode 100644 index 0000000000..6255540ce4 --- /dev/null +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/LogUtils.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2024 Realm Inc. + * + * 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 io.realm.kotlin.internal + +import io.realm.kotlin.internal.interop.CoreLogLevel +import io.realm.kotlin.log.LogCategory +import io.realm.kotlin.log.LogCategoryImpl +import io.realm.kotlin.log.LogLevel + +internal fun LogLevel.toCoreLogLevel(): CoreLogLevel { + return when (this) { + LogLevel.ALL -> CoreLogLevel.RLM_LOG_LEVEL_ALL + LogLevel.TRACE -> CoreLogLevel.RLM_LOG_LEVEL_TRACE + LogLevel.DEBUG -> CoreLogLevel.RLM_LOG_LEVEL_DEBUG + LogLevel.INFO -> CoreLogLevel.RLM_LOG_LEVEL_INFO + LogLevel.WARN -> CoreLogLevel.RLM_LOG_LEVEL_WARNING + LogLevel.ERROR -> CoreLogLevel.RLM_LOG_LEVEL_ERROR + LogLevel.WTF -> CoreLogLevel.RLM_LOG_LEVEL_FATAL + LogLevel.NONE -> CoreLogLevel.RLM_LOG_LEVEL_OFF + } +} + +internal fun CoreLogLevel.fromCoreLogLevel(): LogLevel { + return when (this) { + CoreLogLevel.RLM_LOG_LEVEL_ALL -> LogLevel.ALL + CoreLogLevel.RLM_LOG_LEVEL_TRACE -> LogLevel.TRACE + CoreLogLevel.RLM_LOG_LEVEL_DEBUG, + CoreLogLevel.RLM_LOG_LEVEL_DETAIL -> LogLevel.DEBUG + CoreLogLevel.RLM_LOG_LEVEL_INFO -> LogLevel.INFO + CoreLogLevel.RLM_LOG_LEVEL_WARNING -> LogLevel.WARN + CoreLogLevel.RLM_LOG_LEVEL_ERROR -> LogLevel.ERROR + CoreLogLevel.RLM_LOG_LEVEL_FATAL -> LogLevel.WTF + CoreLogLevel.RLM_LOG_LEVEL_OFF -> LogLevel.NONE + else -> throw IllegalArgumentException("Invalid core log level: $this") + } +} + +internal fun newCategory( + name: String, + parent: LogCategory? = null, +): LogCategory = LogCategoryImpl(name, parent).also { category -> + categoriesByPath["$category"] = category +} + +// at package level as a workaround to ensure compatibility with darwin and jvm +// safe to use because it is loaded during class loading. +internal val categoriesByPath: MutableMap = mutableMapOf() + +internal fun messageWithCategory( + category: LogCategory, + message: String?, +): String? = if (message.isNullOrBlank()) { null } else { + "[${category.name}] $message" +} diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt index 972f8c5386..4ca59cb4e7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.internal import io.realm.kotlin.CompactOnLaunchCallback import io.realm.kotlin.InitialDataCallback import io.realm.kotlin.InitialRealmFileConfiguration -import io.realm.kotlin.LogConfiguration import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.internal.interop.SchemaMode import io.realm.kotlin.internal.util.CoroutineDispatcherFactory @@ -34,7 +33,6 @@ internal class RealmConfigurationImpl( directory: String, name: String, schema: Set>, - logConfig: LogConfiguration, maxNumberOfActiveVersions: Long, notificationDispatcherFactory: CoroutineDispatcherFactory, writeDispatcherFactory: CoroutineDispatcherFactory, @@ -52,7 +50,6 @@ internal class RealmConfigurationImpl( directory, name, schema, - logConfig, maxNumberOfActiveVersions, notificationDispatcherFactory, writeDispatcherFactory, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 81ec75a5f1..79322194e7 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -2,7 +2,6 @@ package io.realm.kotlin.internal.platform import io.realm.kotlin.internal.interop.SyncConnectionParams -import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant import kotlin.jvm.JvmName @@ -123,7 +122,7 @@ public expect fun prepareRealmFilePath(directoryPath: String, filename: String): /** * Returns the default logger for the platform. */ -public expect fun createDefaultSystemLogger(tag: String, logLevel: LogLevel = LogLevel.NONE): RealmLogger +public expect fun createDefaultSystemLogger(tag: String): RealmLogger /** * Return the current thread id. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogCategory.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogCategory.kt new file mode 100644 index 0000000000..4760ee9b12 --- /dev/null +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogCategory.kt @@ -0,0 +1,301 @@ +/* + * Copyright 2021 Realm Inc. + * + * 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 io.realm.kotlin.log + +import io.realm.kotlin.internal.categoriesByPath +import io.realm.kotlin.internal.newCategory + +/** + * Defines a log category for the Realm logger. + */ +public sealed interface LogCategory { + public val parent: LogCategory? + public val name: String + + public operator fun contains(element: LogCategory): Boolean + override fun toString(): String + + public companion object { + /** + * Top level log category for Realm, updating this category level would set all other categories too. + * + * Category hierarchy: + * ``` + * Realm + * ├─► Storage + * │ ├─► Transaction + * │ ├─► Query + * │ ├─► Object + * │ └─► Notification + * ├─► Sync + * │ ├─► Client + * │ │ ├─► Session + * │ │ ├─► Changeset + * │ │ ├─► Network + * │ │ └─► Reset + * │ └─► Server + * ├─► App + * └─► Sdk + * ``` + */ + public val Realm: RealmLogCategory = RealmLogCategory + + internal fun fromCoreValue(categoryPath: String): LogCategory = + categoriesByPath[categoryPath]!! + } +} + +internal class LogCategoryImpl( + override val name: String, + override val parent: LogCategory? = null, +) : LogCategory { + override fun contains(element: LogCategory): Boolean = "$element".contains("$this") + + override fun toString(): String = if (parent != null) "$parent.$name" else name +} + +/** + * Top level log category for Realm, updating this category level would set all other categories too. + * + * Category hierarchy: + * ``` + * Realm + * ├─► Storage + * │ ├─► Transaction + * │ ├─► Query + * │ ├─► Object + * │ └─► Notification + * ├─► Sync + * │ ├─► Client + * │ │ ├─► Session + * │ │ ├─► Changeset + * │ │ ├─► Network + * │ │ └─► Reset + * │ └─► Server + * ├─► App + * └─► Sdk + * ``` + */ +public data object RealmLogCategory : LogCategory by newCategory("Realm") { + /** + * Log category for all storage related logs. + * + * Category hierarchy: + * ``` + * Storage + * ├─► Transaction + * ├─► Query + * ├─► Object + * └─► Notification + * ``` + */ + public val Storage: StorageLogCategory = StorageLogCategory + + /** + * Log category for all sync related logs. + * + * Category hierarchy: + * ``` + * Sync + * ├─► Client + * │ ├─► Session + * │ ├─► Changeset + * │ ├─► Network + * │ └─► Reset + * └─► Server + * ``` + */ + public val Sync: SyncLogCategory = SyncLogCategory + + /** + * Log category for all app related logs. + */ + public val App: LogCategory = AppLogCategory + + /** + * Log category for all sdk related logs. + */ + public val Sdk: LogCategory = SdkLogCategory +} + +/** + * Log category for all storage related logs. + * + * Category hierarchy: + * ``` + * Storage + * ├─► Transaction + * ├─► Query + * ├─► Object + * └─► Notification + * ``` + */ +public data object StorageLogCategory : LogCategory by newCategory("Storage", RealmLogCategory) { + + /** + * Log category for all transaction related logs. + */ + public val Transaction: LogCategory = TransactionLogCategory + + /** + * Log category for all query related logs. + */ + public val Query: LogCategory = QueryLogCategory + + /** + * Log category for all object related logs. + */ + public val Object: LogCategory = ObjectLogCategory + + /** + * Log category for all notification related logs. + */ + public val Notification: LogCategory = NotificationLogCategory +} + +/** + * Log category for all transaction related logs. + */ +public data object TransactionLogCategory : + LogCategory by newCategory("Transaction", StorageLogCategory) + +/** + * Log category for all query related logs. + */ +public data object QueryLogCategory : LogCategory by newCategory("Query", StorageLogCategory) + +/** + * Log category for all object related logs. + */ +public data object ObjectLogCategory : LogCategory by newCategory("Object", StorageLogCategory) + +/** + * Log category for all notification related logs. + */ +public data object NotificationLogCategory : + LogCategory by newCategory("Notification", StorageLogCategory) + +/** + * Category hierarchy: + * ``` + * Sync + * ├─► Client + * │ ├─► Session + * │ ├─► Changeset + * │ ├─► Network + * │ └─► Reset + * └─► Server + * ``` + */ +public data object SyncLogCategory : + LogCategory by newCategory("Sync", RealmLogCategory) { + /** + * Log category for all sync client related logs. + * + * Category hierarchy: + * ``` + * Client + * ├─► Session + * ├─► Changeset + * ├─► Network + * └─► Reset + * ``` + */ + public val Client: ClientLogCategory = ClientLogCategory + + /** + * Log category for all sync server related logs. + */ + public val Server: LogCategory = ServerLogCategory +} + +/** + * + * Log category for all sync client related logs. + * + * Category hierarchy: + * ``` + * Client + * ├─► Session + * ├─► Changeset + * ├─► Network + * └─► Reset + * ``` + */ +public data object ClientLogCategory : + LogCategory by newCategory("Client", SyncLogCategory) { + + /** + * Log category for all sync session related logs. + */ + public val Session: LogCategory = SessionLogCategory + + /** + * Log category for all sync changesets related logs. + */ + public val Changeset: LogCategory = ChangesetLogCategory + + /** + * Log category for all sync network logs. + */ + public val Network: LogCategory = NetworkLogCategory + + /** + * Log category for all sync reset related logs. + */ + public val Reset: LogCategory = ResetLogCategory +} + +/** + * Log category for all sync session related logs. + */ +public data object SessionLogCategory : + LogCategory by newCategory("Session", ClientLogCategory) + +/** + * Log category for all sync changesets related logs. + */ +public data object ChangesetLogCategory : + LogCategory by newCategory("Changeset", ClientLogCategory) + +/** + * Log category for all sync network logs. + */ +public data object NetworkLogCategory : + LogCategory by newCategory("Network", ClientLogCategory) + +/** + * Log category for all sync reset related logs. + */ +public data object ResetLogCategory : + LogCategory by newCategory("Reset", ClientLogCategory) + +/** + * Log category for all sync server related logs. + */ +public data object ServerLogCategory : + LogCategory by newCategory("Server", SyncLogCategory) + +/** + * Log category for all app related logs. + */ +public data object AppLogCategory : LogCategory by newCategory("App", RealmLogCategory) + +/** + * Log category for all sdk related logs. + */ +public data object SdkLogCategory : LogCategory by newCategory("SDK", RealmLogCategory) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogLevel.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogLevel.kt index 1b6c2f804e..563c57fdef 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogLevel.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/LogLevel.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2021 Realm Inc. + * + * 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 io.realm.kotlin.log import io.realm.kotlin.log.LogLevel.TRACE @@ -7,7 +23,7 @@ import io.realm.kotlin.log.LogLevel.WTF * Enum describing the log levels available to the Realm logger. * * Each log entry is assigned a priority between [TRACE] and [WTF]. If the log level is equal or - * higher than the priority defined in [RealmLog.level] the event will be logged. + * higher than the priority defined in [RealmLog.getLevel] the event will be logged. */ @Suppress("MagicNumber") public enum class LogLevel(public val priority: Int) { diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLog.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLog.kt index 246e89e30f..5b066e57b0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLog.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLog.kt @@ -1,11 +1,28 @@ +/* + * Copyright 2023 Realm Inc. + * + * 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 io.realm.kotlin.log import io.realm.kotlin.Realm +import io.realm.kotlin.internal.fromCoreLogLevel import io.realm.kotlin.internal.interop.CoreLogLevel import io.realm.kotlin.internal.interop.LogCallback import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.SynchronizableObject import io.realm.kotlin.internal.platform.createDefaultSystemLogger +import io.realm.kotlin.internal.toCoreLogLevel import io.realm.kotlin.log.RealmLog.add import io.realm.kotlin.log.RealmLog.addDefaultSystemLogger @@ -23,20 +40,11 @@ import io.realm.kotlin.log.RealmLog.addDefaultSystemLogger */ public object RealmLog { - /** - * The current [LogLevel]. Changing this will affect all registered loggers. - */ - public var level: LogLevel = LogLevel.WARN - set(value) { - RealmInterop.realm_set_log_level(value.toCoreLogLevel()) - field = value - } - // Lock preventing multiple threads modifying the list of loggers. private val loggersMutex = SynchronizableObject() // Reference to the currently installed system logger (if any) // `internal` until we can remove the old log API - internal var systemLoggerInstalled: RealmLogger? = null + private var systemLoggerInstalled: RealmLogger? = null // Kotlin Multiplatform is currently lacking primitives like CopyOnWriteArrayList. We could // use `io.realm.kotlin.internal.interop.SynchronizableObject`, but it would require locking // when reporting a log statement which feel a bit heavy, so instead we have added locks around @@ -44,19 +52,50 @@ public object RealmLog { // copy this reference before using it. private var loggers: MutableList = mutableListOf() + // Log level that would be set by default + private val defaultLogLevel = LogLevel.WARN + + // Cached value of the SDK log level + internal var sdkLogLevel = defaultLogLevel + + /** + * Sets the log level of a log category. By setting the log level of a category all its subcategories + * would also be updated to match its level. + * + * @param level target log level. + * @param category target log category, [LogCategory.Realm] by default. + */ + public fun setLevel(level: LogLevel, category: LogCategory = LogCategory.Realm) { + RealmInterop.realm_set_log_level_category(category.toString(), level.toCoreLogLevel()) + sdkLogLevel = getLevel(SdkLogCategory) + } + + /** + * Gets the current log level of a log category. + * + * @param category target log category. + * @return current [category] log level. + */ + public fun getLevel(category: LogCategory = LogCategory.Realm): LogLevel { + return RealmInterop.realm_get_log_level_category(category.toString()).fromCoreLogLevel() + } + init { addDefaultSystemLogger() + setLevel(level = defaultLogLevel) // Set the log level to the SDKs (might be different from cores default INFO) RealmInterop.realm_set_log_callback( object : LogCallback { - override fun log(logLevel: Short, category: String?, message: String?) { + override fun log(logLevel: Short, categoryValue: String, message: String?) { // Create concatenated up front, since Core should already filter messages // not within the log range. - val level: LogLevel = fromCoreLogLevel(CoreLogLevel.valueFromPriority(logLevel)) - // TODO Add category - Will come with https://github.com/realm/realm-kotlin/pull/1692 + val category: LogCategory = LogCategory.fromCoreValue(categoryValue) + val level: LogLevel = CoreLogLevel.valueFromPriority(logLevel).fromCoreLogLevel() + doLog( + category, level, null, - if (message.isNullOrBlank()) { null } else { "[Core] $message" } + message, ) } } @@ -132,224 +171,23 @@ public object RealmLog { } } - /** - * Logs a [LogLevel.TRACE] event. - * - * @param throwable optional exception to log. - */ - internal fun trace(throwable: Throwable?) { - doLog(LogLevel.TRACE, throwable, null) - } - - /** - * Logs a [LogLevel.TRACE] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun trace(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.TRACE, throwable, message, *args) - } - - /** - * Logs a [LogLevel.TRACE] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun trace(message: String, vararg args: Any?) { - doLog(LogLevel.TRACE, null, message, *args) - } - - /** - * Logs a [LogLevel.DEBUG] event. - * - * @param throwable optional exception to log. - */ - internal fun debug(throwable: Throwable?) { - doLog(LogLevel.DEBUG, throwable, null) - } - - /** - * Logs a [LogLevel.DEBUG] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun debug(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.DEBUG, throwable, message, *args) - } - - /** - * Logs a [LogLevel.DEBUG] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun debug(message: String, vararg args: Any?) { - doLog(LogLevel.DEBUG, null, message, *args) - } - - /** - * Logs a [LogLevel.INFO] event. - * - * @param throwable optional exception to log. - */ - internal fun info(throwable: Throwable?) { - doLog(LogLevel.INFO, throwable, null) - } - - /** - * Logs a [LogLevel.INFO] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun info(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.INFO, throwable, message, *args) - } - - /** - * Logs a [LogLevel.INFO] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun info(message: String, vararg args: Any?) { - doLog(LogLevel.INFO, null, message, *args) - } - - /** - * Logs a [LogLevel.WARN] event. - * - * @param throwable optional exception to log. - */ - internal fun warn(throwable: Throwable?) { - doLog(LogLevel.WARN, throwable, null) - } - - /** - * Logs a [LogLevel.WARN] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun warn(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.WARN, throwable, message, *args) - } - - /** - * Logs a [LogLevel.INFO] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun warn(message: String, vararg args: Any?) { - doLog(LogLevel.WARN, null, message, *args) - } - - /** - * Logs a [LogLevel.ERROR] event. - * - * @param throwable optional exception to log. - */ - internal fun error(throwable: Throwable?) { - doLog(LogLevel.ERROR, throwable, null) - } - - /** - * Logs a [LogLevel.ERROR] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun error(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.ERROR, throwable, message, *args) - } - - /** - * Logs a [LogLevel.ERROR] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun error(message: String, vararg args: Any?) { - doLog(LogLevel.ERROR, null, message, *args) - } - - /** - * Logs a [LogLevel.WTF] event. - * - * @param throwable optional exception to log. - */ - internal fun wtf(throwable: Throwable?) { - doLog(LogLevel.WTF, throwable, null) - } - - /** - * Logs a [LogLevel.WTF] event. - * - * @param throwable optional exception to log. - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun wtf(throwable: Throwable?, message: String, vararg args: Any?) { - doLog(LogLevel.WTF, throwable, message, *args) - } - - /** - * Logs a [LogLevel.WTF] event. - * - * @param message optional message. - * @param args optional args used to format the message using a subset of `String.format` - * options. Only `%s`, `%d` and `%f` are supported. - */ - internal fun wtf(message: String, vararg args: Any?) { - doLog(LogLevel.WTF, null, message, *args) - } - /** * Log a message. */ - internal fun doLog(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { - if (level.priority >= this.level.priority) { - // Copy the reference to loggers so they are stable while iterating them. - val loggers = this.loggers - loggers.forEach { - it.log(level, throwable, message, *args) - } - } - } - - /*** - * Internal method used to optimize logging from internal components. See - * [io.realm.kotlin.internal.ContextLogger] for more details. - */ - internal inline fun doLog(level: LogLevel, throwable: Throwable?, message: () -> String?, vararg args: Any?) { - if (level.priority >= this.level.priority) { - // Copy the reference to loggers so they are stable while iterating them. - val loggers = this.loggers - val msg = message() - loggers.forEach { - it.log(level, throwable, msg, *args) - } - } + internal inline fun doLog( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) = loggers.forEach { + it.log( + category = category, + level = level, + throwable = throwable, + message = message, + args = *args + ) } /** @@ -358,34 +196,6 @@ public object RealmLog { internal fun reset() { removeAll() addDefaultSystemLogger() - level = LogLevel.WARN - } - - private fun LogLevel.toCoreLogLevel(): CoreLogLevel { - return when (this) { - LogLevel.ALL -> CoreLogLevel.RLM_LOG_LEVEL_ALL - LogLevel.TRACE -> CoreLogLevel.RLM_LOG_LEVEL_TRACE - LogLevel.DEBUG -> CoreLogLevel.RLM_LOG_LEVEL_DEBUG - LogLevel.INFO -> CoreLogLevel.RLM_LOG_LEVEL_INFO - LogLevel.WARN -> CoreLogLevel.RLM_LOG_LEVEL_WARNING - LogLevel.ERROR -> CoreLogLevel.RLM_LOG_LEVEL_ERROR - LogLevel.WTF -> CoreLogLevel.RLM_LOG_LEVEL_FATAL - LogLevel.NONE -> CoreLogLevel.RLM_LOG_LEVEL_OFF - } - } - - private fun fromCoreLogLevel(level: CoreLogLevel): LogLevel { - return when (level) { - CoreLogLevel.RLM_LOG_LEVEL_ALL, - CoreLogLevel.RLM_LOG_LEVEL_TRACE -> LogLevel.TRACE - CoreLogLevel.RLM_LOG_LEVEL_DEBUG, - CoreLogLevel.RLM_LOG_LEVEL_DETAIL -> LogLevel.DEBUG - CoreLogLevel.RLM_LOG_LEVEL_INFO -> LogLevel.INFO - CoreLogLevel.RLM_LOG_LEVEL_WARNING -> LogLevel.WARN - CoreLogLevel.RLM_LOG_LEVEL_ERROR -> LogLevel.ERROR - CoreLogLevel.RLM_LOG_LEVEL_FATAL -> LogLevel.WTF - CoreLogLevel.RLM_LOG_LEVEL_OFF -> LogLevel.NONE - else -> throw IllegalArgumentException("Invalid core log level: $level") - } + setLevel(LogLevel.WARN) } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLogger.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLogger.kt index 379a906045..6546fbd9e0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLogger.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/log/RealmLogger.kt @@ -16,34 +16,20 @@ package io.realm.kotlin.log -import io.realm.kotlin.Configuration - /** * Interface describing a logger implementation. * - * @see Configuration.SharedBuilder.log + * @see RealmLog */ public interface RealmLogger { - - /** - * The [LogLevel] used in this logger. - */ - public val level: LogLevel - - /** - * Tag that can be used to describe the output. - */ - public val tag: String - - /** - * Log an event. - */ - public fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) - /** * Log an event. */ - public fun log(level: LogLevel, message: String) { - log(level, null, message) - } + public fun log( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) } diff --git a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/StdOutLogger.kt b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/StdOutLogger.kt index f0855ac686..a4179f4345 100644 --- a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/StdOutLogger.kt +++ b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/StdOutLogger.kt @@ -15,6 +15,8 @@ */ package io.realm.kotlin.internal.platform +import io.realm.kotlin.internal.messageWithCategory +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import java.io.PrintWriter @@ -22,18 +24,26 @@ import java.io.StringWriter import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter -import java.util.* /** * Logger implementation outputting to stdout. */ internal class StdOutLogger( - override val tag: String = "REALM", - override val level: LogLevel + private val tag: String, ) : RealmLogger { - override fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { - val logMessage: String = prepareLogMessage(throwable, message, *args) + override fun log( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) { + val logMessage: String = prepareLogMessage( + throwable = throwable, + message = messageWithCategory(category, message), + args = *args + ) val timestamp: String = getTimestamp() println("$timestamp ${level.name}: [$tag] $logMessage") } diff --git a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt index cedaba812b..b1eedc3a6f 100644 --- a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt +++ b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt @@ -4,7 +4,6 @@ import io.realm.kotlin.Realm import io.realm.kotlin.internal.RealmInstantImpl import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions -import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant import java.io.InputStream @@ -28,8 +27,8 @@ public actual fun assetFileAsStream(assetFilename: String): InputStream { return resource.openStream() } -public actual fun createDefaultSystemLogger(tag: String, logLevel: LogLevel): RealmLogger = - StdOutLogger(tag, logLevel) +public actual fun createDefaultSystemLogger(tag: String): RealmLogger = + StdOutLogger(tag) /** * Since internalNow() should only logically return a value after the Unix epoch, it is safe to create a RealmInstant diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/NSLogLogger.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/NSLogLogger.kt index 3cc3d6cdf5..4b01fed2ca 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/NSLogLogger.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/NSLogLogger.kt @@ -15,6 +15,8 @@ */ package io.realm.kotlin.internal.platform +import io.realm.kotlin.internal.messageWithCategory +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import platform.Foundation.NSLog @@ -27,12 +29,22 @@ import platform.Foundation.stringWithFormat * Inspiration from: https://github.com/touchlab/Kermit/blob/master/kermit/src/darwinMain/kotlin/co/touchlab/kermit/NSLogLogger.kt */ internal class NSLogLogger( - override val tag: String = "REALM", - override val level: LogLevel + private val tag: String, ) : RealmLogger { - override fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { - val logMessage: String = prepareLogMessage(throwable, message, *args) + override fun log( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) { + val logMessage: String = prepareLogMessage( + throwable = throwable, + message = messageWithCategory(category, message), + args = *args, + ) + NSLog("%s: [%s] %s", level.name, tag, logMessage) } diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 7fbc206003..87ad3abf97 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -3,7 +3,6 @@ package io.realm.kotlin.internal.platform import io.realm.kotlin.internal.RealmInstantImpl import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions -import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant import kotlinx.cinterop.BooleanVar @@ -43,8 +42,8 @@ public actual val RUNTIME_VERSION: String = "" @Suppress("MayBeConst") // Cannot make expect/actual const public actual val PATH_SEPARATOR: String = "/" -public actual fun createDefaultSystemLogger(tag: String, logLevel: LogLevel): RealmLogger = - NSLogLogger(tag, logLevel) +public actual fun createDefaultSystemLogger(tag: String): RealmLogger = + NSLogLogger(tag) public actual fun threadId(): ULong { memScoped { diff --git a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt index 813576267b..2217071573 100644 --- a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt +++ b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt @@ -28,8 +28,8 @@ import android.net.NetworkInfo import android.net.NetworkRequest import android.os.Build import androidx.startup.Initializer +import io.realm.kotlin.internal.ContextLogger import io.realm.kotlin.internal.RealmInitializer -import io.realm.kotlin.log.RealmLog /** * An **initializer** for Sync specific functionality that does not fit into the `RealmInitializer` @@ -38,6 +38,8 @@ import io.realm.kotlin.log.RealmLog internal class RealmSyncInitializer : Initializer { companion object { + val logger: ContextLogger = ContextLogger("RealmSyncInitializer") + @Suppress("DEPRECATION") // Should only be called below API 21 fun isConnected(cm: ConnectivityManager?): Boolean { return cm?.let { @@ -85,7 +87,7 @@ internal class RealmSyncInitializer : Initializer { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M /* 23 */) { request.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) } - RealmLog.info("Register ConnectivityManager network callbacks") + logger.info("Register ConnectivityManager network callbacks") connectivityManager?.registerNetworkCallback( request.build(), object : NetworkCallback() { @@ -99,7 +101,7 @@ internal class RealmSyncInitializer : Initializer { } ) } else { - RealmLog.info("Register BroadcastReceiver connectivity callbacks") + logger.info("Register BroadcastReceiver connectivity callbacks") @Suppress("DEPRECATION") context.registerReceiver( object : BroadcastReceiver() { @@ -112,10 +114,10 @@ internal class RealmSyncInitializer : Initializer { ) } } catch (ex: Exception) { - RealmLog.warn("Something went wrong trying to register a network state listener: $ex") + logger.warn("Something went wrong trying to register a network state listener: $ex") } } else { - RealmLog.warn( + logger.warn( "It was not possible to register a network state listener. " + "ACCESS_NETWORK_STATE was not granted." ) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt index d5e35e37b1..41dbad1494 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.mongodb import io.ktor.client.plugins.logging.Logger -import io.realm.kotlin.LogConfiguration import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.ContextLogger @@ -31,9 +30,6 @@ import io.realm.kotlin.internal.platform.prepareRealmDirectoryPath import io.realm.kotlin.internal.util.CoroutineDispatcherFactory import io.realm.kotlin.internal.util.DispatcherHolder import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.mongodb.ext.customData import io.realm.kotlin.mongodb.ext.profile import io.realm.kotlin.mongodb.internal.AppConfigurationImpl @@ -158,9 +154,7 @@ public interface AppConfiguration { private var baseUrl: String = DEFAULT_BASE_URL private var dispatcher: CoroutineDispatcher? = null private var encryptionKey: ByteArray? = null - private var logLevel: LogLevel? = null private var syncRootDirectory: String = appFilesDirectory() - private var userLoggers: List = listOf() private var networkTransport: NetworkTransport? = null private var websocketTransport: WebSocketTransport? = null private var appName: String? = null @@ -209,25 +203,6 @@ public interface AppConfiguration { this.dispatcher = dispatcher } - /** - * Configures how Realm will report log events for this App. - * - * @param level all events at this level or higher will be reported. - * @param customLoggers any custom loggers to send log events to. A default system logger is - * installed by default that will redirect to the common logging framework on the platform, i.e. - * LogCat on Android and NSLog on iOS. - * @return the Builder instance used. - */ - @Deprecated("Use io.realm.kotlin.log.RealmLog instead.") - public fun log( - level: LogLevel = LogLevel.WARN, - customLoggers: List = emptyList(), - ): Builder = - apply { - this.logLevel = level - this.userLoggers = customLoggers - } - /** * Configures the root folder that marks the location of a `mongodb-realm` folder. This * folder contains all files and realms used when synchronizing data between the device and @@ -414,18 +389,6 @@ public interface AppConfiguration { // to this method. @OptIn(ExperimentalKBsonSerializerApi::class) public fun build(bundleId: String): AppConfiguration { - // Configure logging during creation of AppConfiguration to keep old behavior for - // configuring logging. This should be removed when `LogConfiguration` is removed. - val allLoggers = mutableListOf() - allLoggers.addAll(userLoggers) - - val logConfig = this.logLevel?.let { - RealmLog.level = it - LogConfiguration(it, allLoggers) - } - - userLoggers.forEach { RealmLog.add(it) } - val appNetworkDispatcherFactory = if (dispatcher != null) { CoroutineDispatcherFactory.unmanaged(dispatcher!!) } else { @@ -435,7 +398,7 @@ public interface AppConfiguration { CoroutineDispatcherFactory.managed("app-dispatcher-$appId") } - val appLogger = ContextLogger("Sdk") + val appLogger = ContextLogger() val networkTransport: (dispatcher: DispatcherHolder) -> NetworkTransport = { dispatcherHolder -> val logger: Logger = object : Logger { @@ -474,7 +437,6 @@ public interface AppConfiguration { networkTransportFactory = networkTransport, websocketTransport = websocketTransport, syncRootDirectory = syncRootDirectory, - logger = logConfig, appName = appName, appVersion = appVersion, bundleId = bundleId, @@ -484,6 +446,7 @@ public interface AppConfiguration { authorizationHeaderName = authorizationHeaderName, enableSessionMultiplexing = enableSessionMultiplexing, syncTimeoutOptions = syncTimeoutOptions, + logger = appLogger ) } } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt index 0943b8c668..d172d6c173 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt @@ -16,7 +16,7 @@ package io.realm.kotlin.mongodb.internal -import io.realm.kotlin.LogConfiguration +import io.realm.kotlin.internal.ContextLogger import io.realm.kotlin.internal.SDK_VERSION import io.realm.kotlin.internal.interop.RealmAppConfigurationPointer import io.realm.kotlin.internal.interop.RealmInterop @@ -51,7 +51,6 @@ public class AppConfigurationImpl @OptIn(ExperimentalKBsonSerializerApi::class) private val websocketTransport: WebSocketTransport?, override val metadataMode: MetadataMode, override val syncRootDirectory: String, - public val logger: LogConfiguration?, override val appName: String?, override val appVersion: String?, internal val bundleId: String, @@ -61,6 +60,7 @@ public class AppConfigurationImpl @OptIn(ExperimentalKBsonSerializerApi::class) override val authorizationHeaderName: String, override val enableSessionMultiplexing: Boolean, override val syncTimeoutOptions: SyncTimeoutOptions, + public val logger: ContextLogger, ) : AppConfiguration { /** diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt index 870e08de95..2ea4681ff6 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt @@ -25,7 +25,6 @@ import io.realm.kotlin.internal.toDuration import io.realm.kotlin.internal.util.DispatcherHolder import io.realm.kotlin.internal.util.Validation import io.realm.kotlin.internal.util.use -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.AuthenticationChange @@ -93,15 +92,15 @@ public class AppImpl( // Due to the way network interfaces are re-enabled on Android, we might see multiple // "isOnline" messages in short order. So in order to prevent resetting the network // too often we throttle messages, so a reconnect can only happen ever 5 seconds. - RealmLog.debug("Network state change detected. ConnectionAvailable = $connectionAvailable") + configuration.logger.debug("Network state change detected. ConnectionAvailable = $connectionAvailable") val now: Duration = RealmInstant.now().toDuration() if (connectionAvailable && (lastOnlineStateReported == null || now.minus(lastOnlineStateReported!!) > reconnectThreshold) ) { - RealmLog.info("Trigger network reconnect.") + configuration.logger.info("Trigger network reconnect.") try { sync.reconnect() } catch (ex: Exception) { - RealmLog.error(ex.toString()) + configuration.logger.error(ex.toString()) } lastOnlineStateReported = now } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt index 1a06d2a32f..ddcf819081 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt @@ -36,7 +36,6 @@ import io.realm.kotlin.internal.interop.SyncErrorCallback import io.realm.kotlin.internal.interop.sync.SyncError import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException import io.realm.kotlin.mongodb.subscriptions @@ -223,7 +222,7 @@ internal class SyncConfigurationImpl( } } catch (ex: Exception) { @Suppress("invisible_member", "invisible_reference") - RealmLog.error("Error thrown and ignored in `onManualResetFallback`: $ex") + configuration.logger.error("Error thrown and ignored in `onManualResetFallback`: $ex") } } else { userErrorHandler.onError(session, syncError) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt index 6224e89df0..943a6d610c 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.mongodb.sync import io.realm.kotlin.Configuration -import io.realm.kotlin.LogConfiguration import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm import io.realm.kotlin.TypedRealm @@ -27,9 +26,6 @@ import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.SchemaMode import io.realm.kotlin.internal.platform.PATH_SEPARATOR import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.Credentials @@ -373,11 +369,6 @@ public interface SyncConfiguration : Configuration { if (!user.loggedIn) { throw IllegalArgumentException("A valid, logged in user is required.") } - // Prime builder with log configuration from AppConfiguration - (user as UserImpl).app.configuration.logger?.let { appLog -> - this.logLevel = appLog.level - this.appConfigLoggers = appLog.loggers - } } /** @@ -401,13 +392,6 @@ public interface SyncConfiguration : Configuration { this.syncClientResetStrategy = resetStrategy } - override fun log(level: LogLevel, customLoggers: List): Builder = - apply { - // Will clear any primed configuration - this.logLevel = level - this.realmConfigLoggers = customLoggers - } - /** * Sets the filename of the realm file. * @@ -493,7 +477,7 @@ public interface SyncConfiguration : Configuration { @Suppress("LongMethod") override fun build(): SyncConfiguration { - val realmLogger = ContextLogger("Sdk") + val realmLogger = ContextLogger() // Set default error handler after setting config logging logic if (this.errorHandler == null) { @@ -539,18 +523,10 @@ public interface SyncConfiguration : Configuration { val fileName = fullPathToFile.substringAfterLast(PATH_SEPARATOR) val directory = fullPathToFile.removeSuffix("$PATH_SEPARATOR$fileName") - // Configure logging during creation of a (Realm/Sync)Configuration to keep old behavior - // for configuring logging. This should be removed when `LogConfiguration` is removed. - RealmLog.level = logLevel - realmConfigLoggers.forEach { RealmLog.add(it) } - @Suppress("invisible_reference", "invisible_member") - val allLoggers: List = listOf(RealmLog.systemLoggerInstalled).filterNotNull() + appConfigLoggers + realmConfigLoggers - val baseConfiguration = ConfigurationImpl( directory, fileName, schema, - LogConfiguration(logLevel, allLoggers), maxNumberOfActiveVersions, if (notificationDispatcher != null) { CoroutineDispatcherFactory.unmanaged(notificationDispatcher!!) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/TestLogger.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/TestLogger.kt index e87ee84a0d..eb5a716729 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/TestLogger.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/TestLogger.kt @@ -1,5 +1,6 @@ package io.realm.kotlin.test.util +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger @@ -7,14 +8,14 @@ import io.realm.kotlin.log.RealmLogger * Logger implementation that track latest log event, so we are able to inspect it. */ class TestLogger : RealmLogger { - override val tag: String = "IGNORE" - override val level: LogLevel = LogLevel.NONE var logLevel: LogLevel = LogLevel.NONE var throwable: Throwable? = null var message: String? = null var args: Array = arrayOf() + private lateinit var category: LogCategory - override fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { + override fun log(category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { + this.category = category this.logLevel = level this.throwable = throwable this.message = message diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmConfigurationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmConfigurationTests.kt index b84b0226d6..e45b2c5c67 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmConfigurationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmConfigurationTests.kt @@ -32,7 +32,6 @@ import io.realm.kotlin.migration.AutomaticSchemaMigration import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.platform.platformFileSystem -import io.realm.kotlin.test.util.TestLogger import io.realm.kotlin.test.util.use import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.newSingleThreadContext @@ -242,38 +241,6 @@ class RealmConfigurationTests { Realm.open(config).use { } } - @Test - fun defaultLogLevel() { - val config: RealmConfiguration = RealmConfiguration.Builder(schema = setOf(Sample::class)) - .build() - assertEquals(LogLevel.WARN, config.log.level) - } - - @Test - fun logLevel() { - val config: RealmConfiguration = RealmConfiguration.Builder(schema = setOf(Sample::class)) - .log(LogLevel.NONE) - .build() - assertEquals(LogLevel.NONE, config.log.level) - } - - @Test - fun defaultCustomLoggers() { - val config: RealmConfiguration = - RealmConfiguration.Builder(schema = setOf(Sample::class)).build() - assertEquals(1, config.log.loggers.size) - } - - @Test - fun customLoggers() { - val logger = TestLogger() - val config: RealmConfiguration = RealmConfiguration.Builder(schema = setOf(Sample::class)) - .log(customLoggers = listOf(logger)) - .build() - assertEquals(2, config.log.loggers.size) - assertEquals(logger, config.log.loggers.last()) - } - @Test fun defaultMaxNumberOfActiveVersions() { val config = RealmConfiguration.create(schema = setOf(Sample::class)) @@ -483,12 +450,14 @@ class RealmConfigurationTests { @Test fun logLevelDoesNotGetOverwrittenByConfig() { val expectedLogLevel = LogLevel.ALL - RealmLog.level = expectedLogLevel + RealmLog.setLevel(expectedLogLevel) + + assertEquals(expectedLogLevel, RealmLog.getLevel()) RealmConfiguration.Builder(setOf(Sample::class)) .build() - assertEquals(expectedLogLevel, RealmLog.level) + assertEquals(expectedLogLevel, RealmLog.getLevel()) RealmLog.reset() } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmLogTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmLogTests.kt index 7a2448f82f..e8a321b316 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmLogTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmLogTests.kt @@ -16,6 +16,10 @@ @file:Suppress("invisible_reference", "invisible_member") package io.realm.kotlin.test.common +import io.realm.kotlin.internal.ContextLogger +import io.realm.kotlin.internal.categoriesByPath +import io.realm.kotlin.internal.interop.RealmInterop +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog import io.realm.kotlin.log.RealmLogger @@ -25,6 +29,7 @@ import kotlinx.atomicfu.atomic import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull @@ -41,33 +46,35 @@ import kotlin.test.fail class RealmLogTests { private lateinit var existingLogLevel: LogLevel - private lateinit var log: RealmLog + private val log: ContextLogger = ContextLogger() @BeforeTest fun setUp() { - existingLogLevel = RealmLog.level - RealmLog.level = LogLevel.ALL - log = RealmLog + existingLogLevel = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.ALL) } @AfterTest fun tearDown() { - RealmLog.level = existingLogLevel - RealmLog.removeAll() - RealmLog.addDefaultSystemLogger() + RealmLog.reset() + } + + @Test + fun defaultLevel() { + assertEquals(LogLevel.WARN, existingLogLevel) } @Test fun ignoreEventsLowerThanLogLevel() { val customLogger = TestLogger() RealmLog.apply { - level = LogLevel.WARN + setLevel(LogLevel.WARN) add(customLogger) - warn("Testing 1") + log.warn("Testing 1") assertEquals("Testing 1", customLogger.message) - this.error("Testing 2") + log.error("Testing 2") assertEquals("Testing 2", customLogger.message) - info("Testing 3") // This should be swallowed + log.info("Testing 3") // This should be swallowed assertEquals("Testing 2", customLogger.message) } } @@ -75,13 +82,13 @@ class RealmLogTests { @Test fun customLogger() { val customLogger = TestLogger() - RealmLog.level = LogLevel.ALL + RealmLog.setLevel(LogLevel.ALL) RealmLog.add(customLogger) var message = "Testing" // Simple message - RealmLog.warn(message) + log.warn(message) assertEquals(LogLevel.WARN, customLogger.logLevel) assertNull(customLogger.throwable) assertEquals(message, customLogger.message) @@ -91,7 +98,7 @@ class RealmLogTests { val throwable = RuntimeException("BOOM") message = "Message: %s" val args: Array = arrayOf("foo") - RealmLog.error(throwable, message, *args) + log.error(throwable, message, *args) assertEquals(LogLevel.ERROR, customLogger.logLevel) assertEquals(throwable, customLogger.throwable) assertEquals(message, customLogger.message) @@ -101,7 +108,7 @@ class RealmLogTests { @Test fun smallLogEntry() { val message = "Testing the RealmLog implementation" - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(message) @@ -120,7 +127,7 @@ class RealmLogTests { fun smallLogEntryWithArgs() { val message = "Testing the RealmLog implementation: (%s, %d, %f)" val args: Array = arrayOf("foo", Long.MAX_VALUE, Float.MAX_VALUE) - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(message, *args) @@ -138,7 +145,7 @@ class RealmLogTests { @Test fun longLogEntry() { val message = Utils.createRandomString(8000) - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(message) @@ -157,7 +164,7 @@ class RealmLogTests { fun longLogEntryWithArgs() { val message = "${Utils.createRandomString(8000)}: (%s, %d, %f)" val args: Array = arrayOf("foo", Long.MAX_VALUE, Float.MAX_VALUE) - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(message, *args) @@ -175,7 +182,7 @@ class RealmLogTests { @Test fun logException() { val error = IllegalArgumentException("BOOM") - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(error) @@ -195,7 +202,7 @@ class RealmLogTests { val error = IllegalArgumentException("BOOM") val message = "Details: (%s, %d, %f)" val args: Array = arrayOf("foo", Long.MAX_VALUE, Float.MAX_VALUE) - LogLevel.values().forEach { + LogLevel.entries.forEach { when (it) { LogLevel.ALL -> { /* Ignore */ } LogLevel.TRACE -> log.trace(error, message, *args) @@ -214,20 +221,20 @@ class RealmLogTests { fun addLogger() { val called = atomic(false) val customLogger = object : RealmLogger { - override val level: LogLevel = LogLevel.ALL - override val tag: String = "CUSTOM" override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any? ) { + assertEquals(LogCategory.Realm.Sdk, category) assertEquals("Hello", message) called.value = true } } RealmLog.add(customLogger) - RealmLog.trace("Hello") + log.trace("Hello") assertTrue(called.value) } @@ -235,21 +242,21 @@ class RealmLogTests { fun addLogger_twice() { val called = atomic(0) val customLogger = object : RealmLogger { - override val level: LogLevel = LogLevel.ALL - override val tag: String = "CUSTOM" override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any? ) { + assertEquals(LogCategory.Realm.Sdk, category) assertEquals("Hello", message) called.incrementAndGet() } } RealmLog.add(customLogger) RealmLog.add(customLogger) - RealmLog.trace("Hello") + log.trace("Hello") assertEquals(2, called.value) } @@ -257,14 +264,14 @@ class RealmLogTests { fun removeLogger_success() { val called = atomic(0) val customLogger = object : RealmLogger { - override val level: LogLevel = LogLevel.ALL - override val tag: String = "CUSTOM" override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any? ) { + assertEquals(LogCategory.Realm.Sdk, category) assertEquals("Hello", message) called.incrementAndGet() } @@ -278,9 +285,8 @@ class RealmLogTests { @Test fun removeLogger_falseForNonExistingLogger() { val customLogger = object : RealmLogger { - override val level: LogLevel = LogLevel.ALL - override val tag: String = "CUSTOM" override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, @@ -295,9 +301,8 @@ class RealmLogTests { @Test fun removeAll_success() { val customLogger = object : RealmLogger { - override val level: LogLevel = LogLevel.ALL - override val tag: String = "CUSTOM" override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, @@ -308,7 +313,7 @@ class RealmLogTests { } RealmLog.add(customLogger) assertTrue(RealmLog.removeAll()) - RealmLog.trace("Hello") // Should not hit `fail()` + log.trace("Hello") // Should not hit `fail()` } @Test @@ -327,4 +332,70 @@ class RealmLogTests { fun addDefaultSystemLogger_failure() { assertFalse(RealmLog.addDefaultSystemLogger()) } + + @Test + fun setCategoryLevels() { + categoriesByPath + .values + .forEach { logCategory -> + val previousLevel: LogLevel = RealmLog.getLevel(logCategory) + RealmLog.setLevel(LogLevel.TRACE, logCategory) + assertEquals(LogLevel.TRACE, RealmLog.getLevel(logCategory)) + + // Restore the level to whatever it was set before + RealmLog.setLevel(previousLevel, logCategory) + } + } + + @Test + fun categoryContains() { + assertFalse(LogCategory.Realm in LogCategory.Realm.Storage) + + assertTrue(LogCategory.Realm.Storage in LogCategory.Realm) + assertTrue(LogCategory.Realm.Storage.Transaction in LogCategory.Realm) + assertTrue(LogCategory.Realm.Storage.Transaction in LogCategory.Realm.Storage) + } + + /** + * Core defines the different categories in runtime, forcing the SDK to define the categories again. + * This test validates that we have defined the same categories as in Core. + */ + @Test + fun categoriesWatchdog() { + val coreLogCategoryNames = RealmInterop.realm_get_category_names() + + val logCategoriesPaths = categoriesByPath + .keys + + logCategoriesPaths.forEach { path -> + assertContains(coreLogCategoryNames, path) + } + + coreLogCategoryNames.forEach { path -> + assertContains(logCategoriesPaths, path) + } + } + + @Test + fun filterSdkLogs() { + val called = atomic(false) + val customLogger = object : RealmLogger { + override fun log( + category: LogCategory, + level: LogLevel, + throwable: Throwable?, + message: String?, + vararg args: Any?, + ) { + called.value = true + } + } + RealmLog.add(customLogger) + + ContextLogger().run { + RealmLog.setLevel(LogLevel.NONE, LogCategory.Realm.Sdk) + warn("should be filtered") + assertFalse(called.value, "Unexpected message logged") + } + } } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt index 1dc47b670d..a33fa06d16 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt @@ -55,7 +55,7 @@ class VersionTrackingTests { @BeforeTest fun setup() { - initialLogLevel = RealmLog.level + initialLogLevel = RealmLog.getLevel() tmpDir = PlatformUtils.createTempDir() configuration = RealmConfiguration.Builder( schema = setOf( @@ -65,7 +65,7 @@ class VersionTrackingTests { Sample::class, SampleWithPrimaryKey::class ) + embeddedSchema - ).directory(tmpDir).log(LogLevel.DEBUG).build() + ).directory(tmpDir).build() realm = Realm.open(configuration) } @@ -75,7 +75,7 @@ class VersionTrackingTests { realm.close() } PlatformUtils.deleteTempDir(tmpDir) - RealmLog.level = initialLogLevel + RealmLog.setLevel(initialLogLevel) } @Test diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt index fd8e95a59f..4af35452e4 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt @@ -26,13 +26,11 @@ import io.realm.kotlin.internal.interop.SynchronizableObject import io.realm.kotlin.internal.interop.sync.NetworkTransport import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.platform.singleThreadDispatcher -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User +import io.realm.kotlin.mongodb.internal.AppConfigurationImpl import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.util.AppAdmin @@ -98,12 +96,10 @@ open class TestApp private constructor( testId: String?, appName: String = TEST_APP_PARTITION, dispatcher: CoroutineDispatcher = singleThreadDispatcher("$testId-dispatcher"), - logLevel: LogLevel? = LogLevel.WARN, builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it.syncRootDirectory(PlatformUtils.createTempDir("$appName-$testId")) }, debug: Boolean = false, - customLogger: RealmLogger? = null, networkTransport: NetworkTransport? = null, ejson: EJson = EJson, initialSetup: suspend AppServicesClient.(app: BaasApp, service: Service) -> Unit = { app: BaasApp, service: Service -> @@ -114,8 +110,6 @@ open class TestApp private constructor( build( debug = debug, appName = appName, - logLevel = logLevel, - customLogger = customLogger, dispatcher = dispatcher, builder = builder, networkTransport = networkTransport, @@ -173,7 +167,7 @@ open class TestApp private constructor( // Some tests might render the server inaccessible, preventing us from // deleting users. Assume those tests know what they are doing and // ignore errors here. - RealmLog.warn("Server side users could not be deleted: $ex") + (configuration as AppConfigurationImpl).logger.warn("Server side users could not be deleted: $ex") } } @@ -203,8 +197,6 @@ open class TestApp private constructor( fun build( debug: Boolean, appName: String, - logLevel: LogLevel?, - customLogger: RealmLogger?, dispatcher: CoroutineDispatcher, builder: (AppConfiguration.Builder) -> AppConfiguration.Builder, networkTransport: NetworkTransport?, @@ -229,13 +221,6 @@ open class TestApp private constructor( .networkTransport(networkTransport) .ejson(ejson) .apply { - if (logLevel != null) { - log( - logLevel, - if (customLogger == null) emptyList() - else listOf(customLogger) - ) - } if (SyncServerConfig.usePlatformNetworking) { usePlatformNetworking() } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt index da9e02c6e7..61a9894bf2 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +@file:Suppress("invisible_reference", "invisible_member") package io.realm.kotlin.test.mongodb.util import io.ktor.client.HttpClient @@ -24,8 +24,8 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.serialization.kotlinx.json.json -import io.realm.kotlin.internal.platform.createDefaultSystemLogger -import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.internal.ContextLogger +import io.realm.kotlin.mongodb.internal.LogObfuscatorImpl import io.realm.kotlin.mongodb.internal.createPlatformClient import kotlinx.serialization.json.Json import kotlin.time.Duration.Companion.seconds @@ -51,15 +51,12 @@ fun defaultClient(name: String, debug: Boolean, block: HttpClientConfig<*>.() -> ) } - // TODO figure out logging and obfuscating sensitive info - // https://github.com/realm/realm-kotlin/issues/410 if (debug) { install(Logging) { logger = object : Logger { - // TODO Hook up with AppConfiguration/RealmConfiguration logger - private val logger = createDefaultSystemLogger(name) + private val logger = ContextLogger(name) override fun log(message: String) { - logger.log(LogLevel.DEBUG, throwable = null, message = message) + logger.debug(LogObfuscatorImpl.obfuscate(message)) } } level = io.ktor.client.plugins.logging.LogLevel.ALL diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt index 3b97a3b0fe..2239de354a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.internal.platform.appFilesDirectory import io.realm.kotlin.internal.platform.isWindows import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog import io.realm.kotlin.log.RealmLogger @@ -397,15 +398,13 @@ class AppConfigurationTests { } private suspend fun doCustomHeaderTest(app: App) { - val originalLevel = RealmLog.level - RealmLog.level = LogLevel.ALL + val originalLevel = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.ALL) val channel = TestChannel() val logger = object : RealmLogger { - override val level: LogLevel = LogLevel.DEBUG - override val tag: String = "LOGGER" - override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, @@ -435,22 +434,10 @@ class AppConfigurationTests { } finally { // Restore log status RealmLog.remove(logger) - RealmLog.level = originalLevel + RealmLog.setLevel(originalLevel) } } - @Test - fun logLevelDoesNotGetOverwrittenByConfig() { - val expectedLogLevel = LogLevel.ALL - RealmLog.level = expectedLogLevel - - AppConfiguration.create("") - - assertEquals(expectedLogLevel, RealmLog.level) - - RealmLog.reset() - } - @Test fun injectedBundleId() { val app = App.create(APP_ID) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt index 576ab58307..de9d26aae5 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt @@ -523,7 +523,7 @@ class AppTests { ).use { testApp -> assertEquals(SyncServerConfig.url, testApp.baseUrl) - RealmLog.level = LogLevel.ALL + RealmLog.setLevel(LogLevel.ALL) runBlocking { testApp.updateBaseUrl(null) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt index b075ce09a7..f72eba9784 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("invisible_reference", "invisible_member") package io.realm.kotlin.test.mongodb.common import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.GoogleAuthType @@ -52,11 +55,8 @@ class HttpLogObfuscatorTests { private class ObfuscatorLoggerInspector( private val channel: Channel ) : RealmLogger { - - override val level: LogLevel = LogLevel.DEBUG - override val tag: String = "ObfuscatorLoggerInspector" - override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, @@ -114,15 +114,15 @@ class HttpLogObfuscatorTests { @BeforeTest fun setUp() { + RealmLog.setLevel(LogLevel.DEBUG) channel = Channel(1) + RealmLog.add(ObfuscatorLoggerInspector(channel)) } private fun initApp(): TestApp { return TestApp( this::class.simpleName, appName = syncServerAppName("obfsctr"), - logLevel = LogLevel.DEBUG, - customLogger = ObfuscatorLoggerInspector(channel), initialSetup = { app, service -> initializeDefault(app, service) app.addFunction(TestAppInitializer.FIRST_ARG_FUNCTION) @@ -135,6 +135,7 @@ class HttpLogObfuscatorTests { @AfterTest fun tearDown() { channel.close() + RealmLog.reset() if (this::app.isInitialized) { app.close() @@ -143,13 +144,12 @@ class HttpLogObfuscatorTests { @Test fun nullObfuscator() = runBlocking { - val logger = CustomLogCollector("NULL-OBFUSCATOR", LogLevel.DEBUG) + val logger = CustomLogCollector() + RealmLog.add(logger) app = TestApp( "nullObfuscator", appName = syncServerAppName("null-obf"), - logLevel = LogLevel.DEBUG, builder = { it.httpLogObfuscator(null) }, - customLogger = logger, initialSetup = { app, service -> initializeDefault(app, service) app.addFunction(TestAppInitializer.FIRST_ARG_FUNCTION) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt index fcfa31f9f1..ede31b9391 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt @@ -26,6 +26,7 @@ import io.realm.kotlin.ext.query import io.realm.kotlin.internal.interop.ErrorCode import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog import io.realm.kotlin.log.RealmLogger @@ -113,11 +114,11 @@ class SyncClientResetIntegrationTests { builder: SyncConfiguration.Builder ) -> Unit ) { + RealmLog.setLevel(LogLevel.INFO) + RealmLog.add(ClientResetLoggerInspector(logChannel)) val app = TestApp( this::class.simpleName, appName = appName, - logLevel = LogLevel.INFO, - customLogger = ClientResetLoggerInspector(logChannel), initialSetup = { app, service -> addEmailProvider(app) when (syncMode) { @@ -306,12 +307,8 @@ class SyncClientResetIntegrationTests { val channel: Channel ) : RealmLogger { - override val level: LogLevel - get() = LogLevel.WARN - override val tag: String - get() = "SyncClientResetIntegrationTests" - override fun log( + category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, @@ -335,20 +332,16 @@ class SyncClientResetIntegrationTests { } } - private lateinit var initialLogLevel: LogLevel private lateinit var partitionValue: String @BeforeTest fun setup() { - initialLogLevel = RealmLog.level partitionValue = TestHelper.randomPartitionValue() } @AfterTest fun tearDown() { - RealmLog.removeAll() - RealmLog.addDefaultSystemLogger() - RealmLog.level = initialLogLevel + RealmLog.reset() } // --------------------------------------------------------------------------------------- diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt index a51453089c..d3867e1d11 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt @@ -29,11 +29,9 @@ import io.realm.kotlin.entities.sync.flx.FlexChildObject import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject import io.realm.kotlin.entities.sync.flx.FlexParentObject import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.createDefaultSystemLogger import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.internal.platform.singleThreadDispatcher -import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.User @@ -99,22 +97,6 @@ class SyncConfigTests { RealmLog.reset() } - @Test - fun logConfiguration() { - val user = createTestUser() - val logger = createDefaultSystemLogger("TEST", LogLevel.DEBUG) - val customLoggers = listOf(logger) - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).also { builder -> - builder.log(LogLevel.DEBUG, customLoggers) - }.build() - assertEquals(LogLevel.DEBUG, config.log.level) - assertEquals(logger, config.log.loggers[1]) // Additional logger placed after default logger - } - @Test fun errorHandler() { val errorHandler = object : SyncSession.ErrorHandler { @@ -1227,30 +1209,6 @@ class SyncConfigTests { assertTrue(config.syncClientResetStrategy is RecoverOrDiscardUnsyncedChangesStrategy) } - @Test - fun logLevelDoesNotGetOverwrittenByConfig() { - app.asTestApp.close() - // Prevent AppConfiguration to set a log level - app = TestApp("logLevelDoesNotGetOverwrittenByConfig", logLevel = null) - - val expectedLogLevel = LogLevel.ALL - - RealmLog.level = expectedLogLevel - - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - SyncConfiguration.Builder( - schema = FLEXIBLE_SYNC_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - - assertEquals(expectedLogLevel, RealmLog.level) - } - private fun createTestUser(): User = runBlocking { val (email, password) = randomEmail() to "password1234" app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 4f7884dcc8..51f3c65f3a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("invisible_reference", "invisible_member") package io.realm.kotlin.test.mongodb.common -import io.realm.kotlin.LogConfiguration import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.VersionId @@ -31,7 +31,7 @@ import io.realm.kotlin.ext.query import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException @@ -54,7 +54,6 @@ import io.realm.kotlin.schema.RealmSchema import io.realm.kotlin.schema.ValuePropertyType import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.CustomLogCollector import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn @@ -139,6 +138,8 @@ class SyncedRealmTests { if (this::app.isInitialized) { app.asTestApp.close() } + + RealmLog.reset() } @Test @@ -1069,7 +1070,6 @@ class SyncedRealmTests { fun writeCopyTo_flexibleSyncToFlexibleSync() = runBlocking { TestApp( "writeCopyTo_flexibleSyncToFlexibleSync", - logLevel = io.realm.kotlin.log.LogLevel.ALL, appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) @@ -1238,44 +1238,6 @@ class SyncedRealmTests { } } - @Test - fun customLoggersReceiveSyncLogs() = runBlocking { - val customLogger = CustomLogCollector("CUSTOM", LogLevel.ALL) - val section = Random.nextInt() - TestApp( - "customLoggersReceiveSyncLogs", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - it.log(level = LogLevel.ALL, listOf(customLogger)) - it.appName("MyCustomApp") - it.appVersion("1.0.0") - } - ).use { flexApp -> - val (email, password) = randomEmail() to "password1234" - val user = flexApp.createUserAndLogIn(email, password) - val syncConfig = createFlexibleSyncConfig( - user = user, - name = "flex.realm", - initialSubscriptions = { realm: Realm -> - realm.query("section = $0", section).subscribe() - } - ) - Realm.open(syncConfig).use { flexSyncRealm: Realm -> - flexSyncRealm.writeBlocking { - copyToRealm( - FlexParentObject().apply { - name = "local object" - } - ) - } - flexSyncRealm.syncSession.uploadAllLocalChangesOrFail() - } - assertTrue(customLogger.logs.isNotEmpty()) - assertTrue(customLogger.logs.any { it.contains("Connection[1]: Negotiated protocol version:") }, "Missing Connection[1]") - } - } - // This test verifies that the user facing Realm instance is actually advanced on an on-needed // basis even though there is no actual listener or explicit await download/upload calls. @Test @@ -1402,7 +1364,6 @@ class SyncedRealmTests { fun createInitialRealmFx() = runBlocking { TestApp( "createInitialRealmFx", - logLevel = LogLevel.ALL, appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) @@ -1865,7 +1826,6 @@ class SyncedRealmTests { partitionValue: String, name: String = DEFAULT_NAME, encryptionKey: ByteArray? = null, - log: LogConfiguration? = null, errorHandler: ErrorHandler? = null, schema: Set> = PARTITION_BASED_SCHEMA, block: SyncConfiguration.Builder.() -> Unit = {} @@ -1876,7 +1836,6 @@ class SyncedRealmTests { ).name(name).also { builder -> if (encryptionKey != null) builder.encryptionKey(encryptionKey) if (errorHandler != null) builder.errorHandler(errorHandler) - if (log != null) builder.log(log.level, log.loggers) block(builder) }.build() @@ -1885,7 +1844,6 @@ class SyncedRealmTests { user: User, name: String = DEFAULT_NAME, encryptionKey: ByteArray? = null, - log: LogConfiguration? = null, errorHandler: ErrorHandler? = null, schema: Set> = FLEXIBLE_SYNC_SCHEMA, initialSubscriptions: InitialSubscriptionsCallback? = null, @@ -1896,7 +1854,6 @@ class SyncedRealmTests { ).name(name).also { builder -> if (encryptionKey != null) builder.encryptionKey(encryptionKey) if (errorHandler != null) builder.errorHandler(errorHandler) - if (log != null) builder.log(log.level, log.loggers) if (initialSubscriptions != null) builder.initialSubscriptions(false, initialSubscriptions) block(builder) }.build() diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt index e5d75483ee..0e14201e26 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt @@ -16,6 +16,7 @@ package io.realm.kotlin.test.mongodb.common.utils +import io.realm.kotlin.log.LogCategory import io.realm.kotlin.log.LogLevel import io.realm.kotlin.log.RealmLogger import kotlinx.coroutines.runBlocking @@ -25,10 +26,7 @@ import kotlinx.coroutines.sync.withLock /** * Logged collecting all logs it has seen. */ -class CustomLogCollector( - override val tag: String, - override val level: LogLevel -) : RealmLogger { +class CustomLogCollector : RealmLogger { private val mutex = Mutex() private val _logs = mutableListOf() @@ -42,7 +40,7 @@ class CustomLogCollector( } } - override fun log(level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { + override fun log(category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { val logMessage: String = message!! runBlocking { mutex.withLock {