Skip to content

Commit

Permalink
Merge branch 'main' into rl.catalog.sorted
Browse files Browse the repository at this point in the history
  • Loading branch information
dconeybe authored Dec 11, 2024
2 parents 7988e85 + 8130292 commit 5bb5807
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 15 deletions.
5 changes: 4 additions & 1 deletion firebase-dataconnect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased

* [changed] `FirebaseDataConnect.logLevel` type changed from `LogLevel` to
`MutableStateFlow<LogLevel>`. This enables apps to "collect" the flow to,
for example, update a UI component when the log level changes.
([#6586](https://github.com/firebase/firebase-android-sdk/pull/6586))

# 16.0.0-beta03
* [changed] Requires Data Connect emulator version 1.6.1 or later for code generation.
Expand Down
3 changes: 1 addition & 2 deletions firebase-dataconnect/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ package com.google.firebase.dataconnect {
public final class FirebaseDataConnectKt {
method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.FirebaseApp app, @NonNull com.google.firebase.dataconnect.ConnectorConfig config, @NonNull com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings());
method @NonNull public static com.google.firebase.dataconnect.FirebaseDataConnect getInstance(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.ConnectorConfig config, @NonNull com.google.firebase.dataconnect.DataConnectSettings settings = com.google.firebase.dataconnect.DataConnectSettings());
method @NonNull public static com.google.firebase.dataconnect.LogLevel getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion);
method public static void setLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.LogLevel);
method @NonNull public static kotlinx.coroutines.flow.MutableStateFlow<com.google.firebase.dataconnect.LogLevel> getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion);
}

@kotlinx.serialization.Serializable(with=LocalDateSerializer::class) public final class LocalDate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.firebase.app
import com.google.firebase.dataconnect.core.FirebaseDataConnectFactory
import com.google.firebase.dataconnect.core.LoggerGlobals
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.modules.SerializersModule
Expand Down Expand Up @@ -381,8 +382,14 @@ public fun FirebaseDataConnect.Companion.getInstance(
/**
* The log level used by all [FirebaseDataConnect] instances.
*
* As a [MutableStateFlow], the log level can be changed by assigning [MutableStateFlow.value].
* Also, the flow can be "collected" as a means of observing the log level, which may be useful in
* the case that a user interface shows a UI element, such as a checkbox, to represent whether debug
* logging is enabled.
*
* The default log level is [LogLevel.WARN]. Setting this to [LogLevel.DEBUG] will enable debug
* logging, which is especially useful when reporting issues to Google or investigating problems
* yourself. Setting it to [LogLevel.NONE] will disable all logging.
*/
public var FirebaseDataConnect.Companion.logLevel: LogLevel by LoggerGlobals::logLevel
public val FirebaseDataConnect.Companion.logLevel: MutableStateFlow<LogLevel>
get() = LoggerGlobals.logLevel
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,26 @@ public enum class LogLevel {
WARN,

/** Do not log anything. */
NONE,
NONE;

internal companion object {

/**
* Returns one of the two given log levels, the one that is "noisier" (i.e. that logs more).
*
* It can be useful to figure out which of two log levels are noisier on log level change, to
* emit a message about the log level change at the noisiest level.
*/
fun noisiestOf(logLevel1: LogLevel, logLevel2: LogLevel): LogLevel =
when (logLevel1) {
DEBUG -> DEBUG
NONE -> logLevel2
WARN ->
when (logLevel2) {
DEBUG -> DEBUG
WARN -> WARN
NONE -> WARN
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@ import android.util.Log
import com.google.firebase.dataconnect.BuildConfig
import com.google.firebase.dataconnect.LogLevel
import com.google.firebase.dataconnect.core.LoggerGlobals.LOG_TAG
import com.google.firebase.dataconnect.core.LoggerGlobals.Logger
import com.google.firebase.util.nextAlphanumericString
import kotlin.random.Random
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

internal interface Logger {
val name: String
Expand Down Expand Up @@ -58,31 +67,58 @@ private class LoggerImpl(override val name: String) : Logger {
internal object LoggerGlobals {
const val LOG_TAG = "FirebaseDataConnect"

@Volatile var logLevel: LogLevel = LogLevel.WARN
val logLevel =
MutableStateFlow(LogLevel.WARN).also { logLevelFlow ->
val logger = Logger("LogLevelChange")
@OptIn(DelicateCoroutinesApi::class)
logger.logChanges(logLevelFlow.value, logLevelFlow, GlobalScope)
}

inline fun Logger.debug(message: () -> Any?) {
if (logLevel <= LogLevel.DEBUG) debug("${message()}")
if (logLevel.value <= LogLevel.DEBUG) debug("${message()}")
}

fun Logger.debug(message: String) {
if (logLevel <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message)
if (logLevel.value <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message)
}

inline fun Logger.warn(message: () -> Any?) {
if (logLevel <= LogLevel.WARN) warn("${message()}")
if (logLevel.value <= LogLevel.WARN) warn("${message()}")
}

inline fun Logger.warn(exception: Throwable?, message: () -> Any?) {
if (logLevel <= LogLevel.WARN) warn(exception, "${message()}")
if (logLevel.value <= LogLevel.WARN) warn(exception, "${message()}")
}

fun Logger.warn(message: String) {
warn(null, message)
}

fun Logger.warn(exception: Throwable?, message: String) {
if (logLevel <= LogLevel.WARN) log(exception, LogLevel.WARN, message)
if (logLevel.value <= LogLevel.WARN) log(exception, LogLevel.WARN, message)
}

fun Logger(name: String): Logger = LoggerImpl(name)

// Log a message each time the log level changes. This is intended to provide context when debug
// logging is enabled and no logs are produced, to at least confirm that debug logging has been
// enabled. Also, it will leave a "mark" in the logs when debug logging is _disabled_ to explain
// why the debug logs stop.
private fun Logger.logChanges(
initialLogLevel: LogLevel,
flow: Flow<LogLevel>,
coroutineScope: CoroutineScope
) {
val state = MutableStateFlow(initialLogLevel)
log(null, initialLogLevel, "Log level set to $initialLogLevel")
flow
.onEach { newLogLevel: LogLevel ->
val oldLogLevel = state.getAndUpdate { newLogLevel }
if (newLogLevel != oldLogLevel) {
val emitLogLevel = LogLevel.noisiestOf(newLogLevel, oldLogLevel)
log(null, emitLogLevel, "Log level changed to $newLogLevel (was $oldLogLevel)")
}
}
.launchIn(coroutineScope)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 Google LLC
*
* 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 com.google.firebase.dataconnect

import io.kotest.assertions.assertSoftly
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.Test

class LoggerUnitTest {

@Test
fun `noisiestOf()`() = runTest {
assertSoftly {
verifyNoisiestOf(LogLevel.NONE, LogLevel.NONE, LogLevel.NONE)
verifyNoisiestOf(LogLevel.NONE, LogLevel.WARN, LogLevel.WARN)
verifyNoisiestOf(LogLevel.NONE, LogLevel.DEBUG, LogLevel.DEBUG)
verifyNoisiestOf(LogLevel.WARN, LogLevel.NONE, LogLevel.WARN)
verifyNoisiestOf(LogLevel.WARN, LogLevel.WARN, LogLevel.WARN)
verifyNoisiestOf(LogLevel.WARN, LogLevel.DEBUG, LogLevel.DEBUG)
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.NONE, LogLevel.DEBUG)
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.WARN, LogLevel.DEBUG)
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.DEBUG, LogLevel.DEBUG)
}
}

private companion object {

fun verifyNoisiestOf(logLevel1: LogLevel, logLevel2: LogLevel, expected: LogLevel) {
withClue("noisiestOf($logLevel1, $logLevel2)") {
LogLevel.noisiestOf(logLevel1, logLevel2) shouldBe expected
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ import org.junit.rules.ExternalResource
* A JUnit test rule that sets the Firebase Data Connect log level to the desired level, then
* restores it upon completion of the test.
*/
class DataConnectLogLevelRule(val logLevelDuringTest: LogLevel? = LogLevel.DEBUG) :
class DataConnectLogLevelRule(val logLevelDuringTest: LogLevel = LogLevel.DEBUG) :
ExternalResource() {

private lateinit var logLevelBefore: LogLevel

override fun before() {
logLevelBefore = FirebaseDataConnect.logLevel
logLevelDuringTest?.also { FirebaseDataConnect.logLevel = it }
logLevelBefore = FirebaseDataConnect.logLevel.value
logLevelDuringTest.also { FirebaseDataConnect.logLevel.value = it }
}

override fun after() {
FirebaseDataConnect.logLevel = logLevelBefore
FirebaseDataConnect.logLevel.value = logLevelBefore
}
}

0 comments on commit 5bb5807

Please sign in to comment.