Skip to content

Commit 9f900a0

Browse files
authored
feat(core): Add APIs to lazily evaluate log statements (#2811)
1 parent bded3bc commit 9f900a0

File tree

7 files changed

+229
-11
lines changed

7 files changed

+229
-11
lines changed

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,19 @@ import io.mockk.every
3737
import io.mockk.mockk
3838
import io.mockk.slot
3939
import kotlin.test.assertEquals
40-
import kotlinx.coroutines.ExperimentalCoroutinesApi
4140
import kotlinx.coroutines.test.runTest
4241
import org.junit.Before
4342
import org.junit.Test
4443
import org.junit.runner.RunWith
4544
import org.robolectric.RobolectricTestRunner
4645

4746
@RunWith(RobolectricTestRunner::class)
48-
@OptIn(ExperimentalCoroutinesApi::class)
4947
class SetupTOTPCognitoActionsTest {
5048

5149
private val configuration = mockk<AuthConfiguration>()
5250
private val cognitoAuthService = mockk<AWSCognitoAuthService>()
5351
private val credentialStoreClient = mockk<StoreClientBehavior>()
54-
private val logger = mockk<Logger>()
52+
private val logger = mockk<Logger>(relaxed = true)
5553
private val cognitoIdentityProviderClientMock = mockk<CognitoIdentityProviderClient>()
5654
private val dispatcher = mockk<EventDispatcher>()
5755

@@ -61,7 +59,6 @@ class SetupTOTPCognitoActionsTest {
6159

6260
@Before
6361
fun setup() {
64-
every { logger.verbose(any()) }.answers {}
6562
every { dispatcher.send(capture(capturedEvent)) }.answers { }
6663
every { cognitoAuthService.cognitoIdentityProviderClient }.answers { cognitoIdentityProviderClientMock }
6764
authEnvironment = AuthEnvironment(

aws-datastore/src/main/java/com/amplifyframework/datastore/storage/sqlite/SQLiteModelFieldTypeConverter.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public Object convertValueFromSource(
206206
// Skip if there is no equivalent column for field in object
207207
final SQLiteColumn column = columns.get(field.getName());
208208
if (column == null) {
209-
LOGGER.verbose(String.format("Column with name %s does not exist", field.getName()));
209+
LOGGER.verbose(() -> "Column with name " + field.getName() + " does not exist");
210210
return null;
211211
}
212212

@@ -235,10 +235,8 @@ public Object convertValueFromSource(
235235
}
236236

237237
final String valueAsString = cursor.getString(columnIndex);
238-
LOGGER.verbose(String.format(
239-
"Attempt to convert value \"%s\" from field %s of type %s in model %s",
240-
valueAsString, field.getName(), field.getTargetType(), parentSchema.getName()
241-
));
238+
LOGGER.verbose(() -> "Attempt to convert value \"" + valueAsString + "\" from field " + field.getName()
239+
+ " of type " + field.getTargetType() + " in model " + parentSchema.getName());
242240

243241
switch (javaFieldType) {
244242
case STRING:

core/api/core.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,14 +3008,21 @@ public final class com/amplifyframework/logging/LogLevel : java/lang/Enum {
30083008

30093009
public abstract interface class com/amplifyframework/logging/Logger {
30103010
public abstract fun debug (Ljava/lang/String;)V
3011+
public fun debug (Ljava/util/function/Supplier;)V
30113012
public abstract fun error (Ljava/lang/String;)V
30123013
public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V
3014+
public fun error (Ljava/lang/Throwable;Ljava/util/function/Supplier;)V
3015+
public fun error (Ljava/util/function/Supplier;)V
30133016
public abstract fun getNamespace ()Ljava/lang/String;
30143017
public abstract fun getThresholdLevel ()Lcom/amplifyframework/logging/LogLevel;
30153018
public abstract fun info (Ljava/lang/String;)V
3019+
public fun info (Ljava/util/function/Supplier;)V
30163020
public abstract fun verbose (Ljava/lang/String;)V
3021+
public fun verbose (Ljava/util/function/Supplier;)V
30173022
public abstract fun warn (Ljava/lang/String;)V
30183023
public abstract fun warn (Ljava/lang/String;Ljava/lang/Throwable;)V
3024+
public fun warn (Ljava/lang/Throwable;Ljava/util/function/Supplier;)V
3025+
public fun warn (Ljava/util/function/Supplier;)V
30193026
}
30203027

30213028
public final class com/amplifyframework/logging/LoggingCategory : com/amplifyframework/core/category/Category, com/amplifyframework/logging/LoggingCategoryBehavior {

core/src/main/java/com/amplifyframework/logging/Logger.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import androidx.annotation.NonNull;
1919
import androidx.annotation.Nullable;
2020

21+
import java.util.function.Supplier;
22+
2123
/**
2224
* A component which can emit logs.
2325
*/
@@ -44,41 +46,120 @@ public interface Logger {
4446
*/
4547
void error(@Nullable String message);
4648

49+
/**
50+
* Logs a message at the {@link LogLevel#ERROR} level. The supplier is only invoked if the log level threshold
51+
* is at ERROR or below.
52+
* @param messageSupplier A function that returns an error message
53+
*/
54+
default void error(@NonNull Supplier<String> messageSupplier) {
55+
if (!getThresholdLevel().above(LogLevel.ERROR)) {
56+
error(messageSupplier.get());
57+
}
58+
}
59+
4760
/**
4861
* Logs a message and thrown error at {@link LogLevel#ERROR} level.
4962
* @param message An error message
5063
* @param error A thrown error
5164
*/
5265
void error(@Nullable String message, @Nullable Throwable error);
5366

67+
/**
68+
* Logs a message and thrown error at {@link LogLevel#ERROR} level. The supplier is only invoked if the log level
69+
* threshold is at ERROR or below.
70+
* @param error A thrown error
71+
* @param messageSupplier A function that returns an error message
72+
*/
73+
default void error(@Nullable Throwable error, @NonNull Supplier<String> messageSupplier) {
74+
if (!getThresholdLevel().above(LogLevel.ERROR)) {
75+
error(messageSupplier.get(), error);
76+
}
77+
}
78+
5479
/**
5580
* Log a message at the {@link LogLevel#WARN} level.
5681
* @param message A warning message
5782
*/
5883
void warn(@Nullable String message);
5984

85+
/**
86+
* Log a message at the {@link LogLevel#WARN} level. The supplier is only invoked if the log level threshold
87+
* is at WARN or below.
88+
* @param messageSupplier A function that returns a warning message
89+
*/
90+
default void warn(@NonNull Supplier<String> messageSupplier) {
91+
if (!getThresholdLevel().above(LogLevel.WARN)) {
92+
warn(messageSupplier.get());
93+
}
94+
}
95+
6096
/**
6197
* Log a message and a throwable issue at the {@link LogLevel#WARN} level.
6298
* @param message A warning message
6399
* @param issue An issue that caused this warning
64100
*/
65101
void warn(@Nullable String message, @Nullable Throwable issue);
66102

103+
/**
104+
* Log a message and a throwable issue at the {@link LogLevel#WARN} level. The supplier is only invoked if the
105+
* log level threshold is at WARN or below.
106+
* @param issue An issue that caused this warning
107+
* @param messageSupplier A function that returns a warning message
108+
*/
109+
default void warn(@Nullable Throwable issue, @NonNull Supplier<String> messageSupplier) {
110+
if (!getThresholdLevel().above(LogLevel.WARN)) {
111+
warn(messageSupplier.get(), issue);
112+
}
113+
}
114+
67115
/**
68116
* Logs a message at {@link LogLevel#INFO} level.
69117
* @param message An informational message
70118
*/
71119
void info(@Nullable String message);
72120

121+
/**
122+
* Logs a message at {@link LogLevel#INFO} level. The supplier is only invoked if the log level threshold
123+
* is at INFO or below.
124+
* @param messageSupplier A function that returns an info message
125+
*/
126+
default void info(@NonNull Supplier<String> messageSupplier) {
127+
if (!getThresholdLevel().above(LogLevel.INFO)) {
128+
info(messageSupplier.get());
129+
}
130+
}
131+
73132
/**
74133
* Logs a message at the {@link LogLevel#DEBUG} level.
75134
* @param message A debugging message.
76135
*/
77136
void debug(@Nullable String message);
78137

138+
/**
139+
* Logs a message at the {@link LogLevel#DEBUG} level. The supplier is only invoked if the log level threshold
140+
* is at DEBUG or below.
141+
* @param messageSupplier A function that returns a debugging message
142+
*/
143+
default void debug(@NonNull Supplier<String> messageSupplier) {
144+
if (!getThresholdLevel().above(LogLevel.DEBUG)) {
145+
debug(messageSupplier.get());
146+
}
147+
}
148+
79149
/**
80150
* Logs a message at the {@link LogLevel#VERBOSE} level.
81151
* @param message A verbose message
82152
*/
83153
void verbose(@Nullable String message);
154+
155+
/**
156+
* Logs a message at the {@link LogLevel#VERBOSE} level. The supplier is only invoked if the log level threshold
157+
* is at VERBOSE.
158+
* @param messageSupplier A function that returns a verbose message
159+
*/
160+
default void verbose(@NonNull Supplier<String> messageSupplier) {
161+
if (!getThresholdLevel().above(LogLevel.VERBOSE)) {
162+
verbose(messageSupplier.get());
163+
}
164+
}
84165
}

core/src/test/java/com/amplifyframework/logging/AndroidLoggingPluginTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void allContentLoggedAtThresholdVerbose() {
9999

100100
logger.verbose("This logs");
101101
logger.debug("This too");
102-
logger.info(null);
102+
logger.info((String) null);
103103
logger.warn("Getting serious...");
104104
logger.error("It. Got. Serious.");
105105

@@ -125,7 +125,7 @@ public void noContentLoggedAtThresholdNone() {
125125
Logger logger = plugin.logger("logging-test");
126126

127127
logger.error("An error happened!");
128-
logger.info(null);
128+
logger.info((String) null);
129129
logger.warn("Uh oh, not great...");
130130

131131
assertTrue(systemLog.getLines().isEmpty());

core/src/test/java/com/amplifyframework/logging/FakeLogger.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ static FakeLogger instance(@NonNull String namespace, @NonNull LogLevel threshol
5050
return new FakeLogger(namespace, threshold);
5151
}
5252

53+
static FakeLogger instance(@NonNull LogLevel threshold) {
54+
return instance("", threshold);
55+
}
56+
5357
@NonNull
5458
@Override
5559
public LogLevel getThresholdLevel() {
@@ -127,6 +131,13 @@ static Log instance(@NonNull LogLevel level, @Nullable String message, @Nullable
127131
return new Log(level, message, throwable);
128132
}
129133

134+
void assertEquals(
135+
@Nullable LogLevel actualLevel,
136+
@Nullable String actualMessage
137+
) {
138+
assertEquals(actualLevel, actualMessage, null);
139+
}
140+
130141
void assertEquals(
131142
@Nullable LogLevel actualLevel,
132143
@Nullable String actualMessage,
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.logging
17+
18+
import io.kotest.matchers.collections.shouldBeEmpty
19+
import io.mockk.mockk
20+
import org.junit.Test
21+
22+
class LoggerTest {
23+
@Test
24+
fun `verbose log emitted`() {
25+
val logger = FakeLogger.instance(LogLevel.VERBOSE)
26+
logger.verbose { "test" }
27+
logger.logs.first().assertEquals(LogLevel.VERBOSE, "test")
28+
}
29+
30+
@Test
31+
fun `verbose log not emitted`() {
32+
val logger = FakeLogger.instance(LogLevel.INFO)
33+
logger.verbose { "test" }
34+
logger.logs.shouldBeEmpty()
35+
}
36+
37+
@Test
38+
fun `debug log emitted`() {
39+
val logger = FakeLogger.instance(LogLevel.DEBUG)
40+
logger.debug { "test" }
41+
logger.logs.first().assertEquals(LogLevel.DEBUG, "test")
42+
}
43+
44+
@Test
45+
fun `debug log not emitted`() {
46+
val logger = FakeLogger.instance(LogLevel.INFO)
47+
logger.debug { "test" }
48+
logger.logs.shouldBeEmpty()
49+
}
50+
51+
@Test
52+
fun `info log emitted`() {
53+
val logger = FakeLogger.instance(LogLevel.INFO)
54+
logger.info { "test" }
55+
logger.logs.first().assertEquals(LogLevel.INFO, "test")
56+
}
57+
58+
@Test
59+
fun `info log not emitted`() {
60+
val logger = FakeLogger.instance(LogLevel.WARN)
61+
logger.info { "test" }
62+
logger.logs.shouldBeEmpty()
63+
}
64+
65+
@Test
66+
fun `warn log emitted`() {
67+
val logger = FakeLogger.instance(LogLevel.WARN)
68+
logger.warn { "test" }
69+
logger.logs.first().assertEquals(LogLevel.WARN, "test")
70+
}
71+
72+
@Test
73+
fun `warn log not emitted`() {
74+
val logger = FakeLogger.instance(LogLevel.ERROR)
75+
logger.warn { "test" }
76+
logger.logs.shouldBeEmpty()
77+
}
78+
79+
@Test
80+
fun `warn log emitted with throwable`() {
81+
val throwable = mockk<Throwable>()
82+
val logger = FakeLogger.instance(LogLevel.WARN)
83+
logger.warn(throwable) { "test" }
84+
logger.logs.first().assertEquals(LogLevel.WARN, "test", throwable)
85+
}
86+
87+
@Test
88+
fun `warn log not emitted with throwable`() {
89+
val throwable = mockk<Throwable>()
90+
val logger = FakeLogger.instance(LogLevel.ERROR)
91+
logger.warn(throwable) { "test" }
92+
logger.logs.shouldBeEmpty()
93+
}
94+
95+
@Test
96+
fun `error log emitted`() {
97+
val logger = FakeLogger.instance(LogLevel.ERROR)
98+
logger.error { "test" }
99+
logger.logs.first().assertEquals(LogLevel.ERROR, "test")
100+
}
101+
102+
@Test
103+
fun `error log not emitted`() {
104+
val logger = FakeLogger.instance(LogLevel.NONE)
105+
logger.error { "test" }
106+
logger.logs.shouldBeEmpty()
107+
}
108+
109+
@Test
110+
fun `error log emitted with throwable`() {
111+
val throwable = mockk<Throwable>()
112+
val logger = FakeLogger.instance(LogLevel.ERROR)
113+
logger.error(throwable) { "test" }
114+
logger.logs.first().assertEquals(LogLevel.ERROR, "test", throwable)
115+
}
116+
117+
@Test
118+
fun `error log not emitted with throwable`() {
119+
val throwable = mockk<Throwable>()
120+
val logger = FakeLogger.instance(LogLevel.NONE)
121+
logger.error(throwable) { "test" }
122+
logger.logs.shouldBeEmpty()
123+
}
124+
}

0 commit comments

Comments
 (0)