Skip to content

Commit 2a0d17a

Browse files
authored
Merge 7631f77 into 2b072d9
2 parents 2b072d9 + 7631f77 commit 2a0d17a

File tree

7 files changed

+131
-15
lines changed

7 files changed

+131
-15
lines changed

firebase-dataconnect/CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
2-
2+
* [changed] `FirebaseDataConnect.logLevel` type changed from `LogLevel` to
3+
`MutableStateFlow<LogLevel>`. This enables apps to "collect" the flow to,
4+
for example, update a UI component when the log level changes.
5+
([#6586](https://github.com/firebase/firebase-android-sdk/pull/6586))
36

47
# 16.0.0-beta03
58
* [changed] Requires Data Connect emulator version 1.6.1 or later for code generation.

firebase-dataconnect/api.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ package com.google.firebase.dataconnect {
112112
public final class FirebaseDataConnectKt {
113113
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());
114114
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());
115-
method @NonNull public static com.google.firebase.dataconnect.LogLevel getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion);
116-
method public static void setLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion, @NonNull com.google.firebase.dataconnect.LogLevel);
115+
method @NonNull public static kotlinx.coroutines.flow.MutableStateFlow<com.google.firebase.dataconnect.LogLevel> getLogLevel(@NonNull com.google.firebase.dataconnect.FirebaseDataConnect.Companion);
117116
}
118117

119118
@kotlinx.serialization.Serializable(with=LocalDateSerializer::class) public final class LocalDate {

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/FirebaseDataConnect.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.google.firebase.app
2323
import com.google.firebase.dataconnect.core.FirebaseDataConnectFactory
2424
import com.google.firebase.dataconnect.core.LoggerGlobals
2525
import kotlinx.coroutines.CoroutineScope
26+
import kotlinx.coroutines.flow.MutableStateFlow
2627
import kotlinx.serialization.DeserializationStrategy
2728
import kotlinx.serialization.SerializationStrategy
2829
import kotlinx.serialization.modules.SerializersModule
@@ -381,8 +382,14 @@ public fun FirebaseDataConnect.Companion.getInstance(
381382
/**
382383
* The log level used by all [FirebaseDataConnect] instances.
383384
*
385+
* As a [MutableStateFlow], the log level can be changed by assigning [MutableStateFlow.value].
386+
* Also, the flow can be "collected" as a means of observing the log level, which may be useful in
387+
* the case that a user interface shows a UI element, such as a checkbox, to represent whether debug
388+
* logging is enabled.
389+
*
384390
* The default log level is [LogLevel.WARN]. Setting this to [LogLevel.DEBUG] will enable debug
385391
* logging, which is especially useful when reporting issues to Google or investigating problems
386392
* yourself. Setting it to [LogLevel.NONE] will disable all logging.
387393
*/
388-
public var FirebaseDataConnect.Companion.logLevel: LogLevel by LoggerGlobals::logLevel
394+
public val FirebaseDataConnect.Companion.logLevel: MutableStateFlow<LogLevel>
395+
get() = LoggerGlobals.logLevel

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/LogLevel.kt

+22-1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,26 @@ public enum class LogLevel {
3030
WARN,
3131

3232
/** Do not log anything. */
33-
NONE,
33+
NONE;
34+
35+
internal companion object {
36+
37+
/**
38+
* Returns one of the two given log levels, the one that is "noisier" (i.e. that logs more).
39+
*
40+
* It can be useful to figure out which of two log levels are noisier on log level change, to
41+
* emit a message about the log level change at the noisiest level.
42+
*/
43+
fun noisiestOf(logLevel1: LogLevel, logLevel2: LogLevel): LogLevel =
44+
when (logLevel1) {
45+
DEBUG -> DEBUG
46+
NONE -> logLevel2
47+
WARN ->
48+
when (logLevel2) {
49+
DEBUG -> DEBUG
50+
WARN -> WARN
51+
NONE -> WARN
52+
}
53+
}
54+
}
3455
}

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/Logger.kt

+42-6
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ import android.util.Log
2020
import com.google.firebase.dataconnect.BuildConfig
2121
import com.google.firebase.dataconnect.LogLevel
2222
import com.google.firebase.dataconnect.core.LoggerGlobals.LOG_TAG
23+
import com.google.firebase.dataconnect.core.LoggerGlobals.Logger
2324
import com.google.firebase.util.nextAlphanumericString
2425
import kotlin.random.Random
26+
import kotlinx.coroutines.CoroutineScope
27+
import kotlinx.coroutines.DelicateCoroutinesApi
28+
import kotlinx.coroutines.GlobalScope
29+
import kotlinx.coroutines.flow.Flow
30+
import kotlinx.coroutines.flow.MutableStateFlow
31+
import kotlinx.coroutines.flow.getAndUpdate
32+
import kotlinx.coroutines.flow.launchIn
33+
import kotlinx.coroutines.flow.onEach
2534

2635
internal interface Logger {
2736
val name: String
@@ -58,31 +67,58 @@ private class LoggerImpl(override val name: String) : Logger {
5867
internal object LoggerGlobals {
5968
const val LOG_TAG = "FirebaseDataConnect"
6069

61-
@Volatile var logLevel: LogLevel = LogLevel.WARN
70+
val logLevel =
71+
MutableStateFlow(LogLevel.WARN).also { logLevelFlow ->
72+
val logger = Logger("LogLevelChange")
73+
@OptIn(DelicateCoroutinesApi::class)
74+
logger.logChanges(logLevelFlow.value, logLevelFlow, GlobalScope)
75+
}
6276

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

6781
fun Logger.debug(message: String) {
68-
if (logLevel <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message)
82+
if (logLevel.value <= LogLevel.DEBUG) log(null, LogLevel.DEBUG, message)
6983
}
7084

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

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

7993
fun Logger.warn(message: String) {
8094
warn(null, message)
8195
}
8296

8397
fun Logger.warn(exception: Throwable?, message: String) {
84-
if (logLevel <= LogLevel.WARN) log(exception, LogLevel.WARN, message)
98+
if (logLevel.value <= LogLevel.WARN) log(exception, LogLevel.WARN, message)
8599
}
86100

87101
fun Logger(name: String): Logger = LoggerImpl(name)
102+
103+
// Log a message each time the log level changes. This is intended to provide context when debug
104+
// logging is enabled and no logs are produced, to at least confirm that debug logging has been
105+
// enabled. Also, it will leave a "mark" in the logs when debug logging is _disabled_ to explain
106+
// why the debug logs stop.
107+
private fun Logger.logChanges(
108+
initialLogLevel: LogLevel,
109+
flow: Flow<LogLevel>,
110+
coroutineScope: CoroutineScope
111+
) {
112+
val state = MutableStateFlow(initialLogLevel)
113+
log(null, initialLogLevel, "Log level set to $initialLogLevel")
114+
flow
115+
.onEach { newLogLevel: LogLevel ->
116+
val oldLogLevel = state.getAndUpdate { newLogLevel }
117+
if (newLogLevel != oldLogLevel) {
118+
val emitLogLevel = LogLevel.noisiestOf(newLogLevel, oldLogLevel)
119+
log(null, emitLogLevel, "Log level changed to $newLogLevel (was $oldLogLevel)")
120+
}
121+
}
122+
.launchIn(coroutineScope)
123+
}
88124
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.dataconnect
18+
19+
import io.kotest.assertions.assertSoftly
20+
import io.kotest.assertions.withClue
21+
import io.kotest.matchers.shouldBe
22+
import kotlinx.coroutines.test.runTest
23+
import org.junit.Test
24+
25+
class LoggerUnitTest {
26+
27+
@Test
28+
fun `noisiestOf()`() = runTest {
29+
assertSoftly {
30+
verifyNoisiestOf(LogLevel.NONE, LogLevel.NONE, LogLevel.NONE)
31+
verifyNoisiestOf(LogLevel.NONE, LogLevel.WARN, LogLevel.WARN)
32+
verifyNoisiestOf(LogLevel.NONE, LogLevel.DEBUG, LogLevel.DEBUG)
33+
verifyNoisiestOf(LogLevel.WARN, LogLevel.NONE, LogLevel.WARN)
34+
verifyNoisiestOf(LogLevel.WARN, LogLevel.WARN, LogLevel.WARN)
35+
verifyNoisiestOf(LogLevel.WARN, LogLevel.DEBUG, LogLevel.DEBUG)
36+
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.NONE, LogLevel.DEBUG)
37+
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.WARN, LogLevel.DEBUG)
38+
verifyNoisiestOf(LogLevel.DEBUG, LogLevel.DEBUG, LogLevel.DEBUG)
39+
}
40+
}
41+
42+
private companion object {
43+
44+
fun verifyNoisiestOf(logLevel1: LogLevel, logLevel2: LogLevel, expected: LogLevel) {
45+
withClue("noisiestOf($logLevel1, $logLevel2)") {
46+
LogLevel.noisiestOf(logLevel1, logLevel2) shouldBe expected
47+
}
48+
}
49+
}
50+
}

firebase-dataconnect/testutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectLogLevelRule.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ import org.junit.rules.ExternalResource
2424
* A JUnit test rule that sets the Firebase Data Connect log level to the desired level, then
2525
* restores it upon completion of the test.
2626
*/
27-
class DataConnectLogLevelRule(val logLevelDuringTest: LogLevel? = LogLevel.DEBUG) :
27+
class DataConnectLogLevelRule(val logLevelDuringTest: LogLevel = LogLevel.DEBUG) :
2828
ExternalResource() {
2929

3030
private lateinit var logLevelBefore: LogLevel
3131

3232
override fun before() {
33-
logLevelBefore = FirebaseDataConnect.logLevel
34-
logLevelDuringTest?.also { FirebaseDataConnect.logLevel = it }
33+
logLevelBefore = FirebaseDataConnect.logLevel.value
34+
logLevelDuringTest.also { FirebaseDataConnect.logLevel.value = it }
3535
}
3636

3737
override fun after() {
38-
FirebaseDataConnect.logLevel = logLevelBefore
38+
FirebaseDataConnect.logLevel.value = logLevelBefore
3939
}
4040
}

0 commit comments

Comments
 (0)