Skip to content

Commit

Permalink
feat(core): Add APIs to lazily evaluate log statements (#2811)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattcreaser authored May 15, 2024
1 parent bded3bc commit 9f900a0
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,19 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
class SetupTOTPCognitoActionsTest {

private val configuration = mockk<AuthConfiguration>()
private val cognitoAuthService = mockk<AWSCognitoAuthService>()
private val credentialStoreClient = mockk<StoreClientBehavior>()
private val logger = mockk<Logger>()
private val logger = mockk<Logger>(relaxed = true)
private val cognitoIdentityProviderClientMock = mockk<CognitoIdentityProviderClient>()
private val dispatcher = mockk<EventDispatcher>()

Expand All @@ -61,7 +59,6 @@ class SetupTOTPCognitoActionsTest {

@Before
fun setup() {
every { logger.verbose(any()) }.answers {}
every { dispatcher.send(capture(capturedEvent)) }.answers { }
every { cognitoAuthService.cognitoIdentityProviderClient }.answers { cognitoIdentityProviderClientMock }
authEnvironment = AuthEnvironment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public Object convertValueFromSource(
// Skip if there is no equivalent column for field in object
final SQLiteColumn column = columns.get(field.getName());
if (column == null) {
LOGGER.verbose(String.format("Column with name %s does not exist", field.getName()));
LOGGER.verbose(() -> "Column with name " + field.getName() + " does not exist");
return null;
}

Expand Down Expand Up @@ -235,10 +235,8 @@ public Object convertValueFromSource(
}

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

switch (javaFieldType) {
case STRING:
Expand Down
7 changes: 7 additions & 0 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -3008,14 +3008,21 @@ public final class com/amplifyframework/logging/LogLevel : java/lang/Enum {

public abstract interface class com/amplifyframework/logging/Logger {
public abstract fun debug (Ljava/lang/String;)V
public fun debug (Ljava/util/function/Supplier;)V
public abstract fun error (Ljava/lang/String;)V
public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V
public fun error (Ljava/lang/Throwable;Ljava/util/function/Supplier;)V
public fun error (Ljava/util/function/Supplier;)V
public abstract fun getNamespace ()Ljava/lang/String;
public abstract fun getThresholdLevel ()Lcom/amplifyframework/logging/LogLevel;
public abstract fun info (Ljava/lang/String;)V
public fun info (Ljava/util/function/Supplier;)V
public abstract fun verbose (Ljava/lang/String;)V
public fun verbose (Ljava/util/function/Supplier;)V
public abstract fun warn (Ljava/lang/String;)V
public abstract fun warn (Ljava/lang/String;Ljava/lang/Throwable;)V
public fun warn (Ljava/lang/Throwable;Ljava/util/function/Supplier;)V
public fun warn (Ljava/util/function/Supplier;)V
}

public final class com/amplifyframework/logging/LoggingCategory : com/amplifyframework/core/category/Category, com/amplifyframework/logging/LoggingCategoryBehavior {
Expand Down
81 changes: 81 additions & 0 deletions core/src/main/java/com/amplifyframework/logging/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.function.Supplier;

/**
* A component which can emit logs.
*/
Expand All @@ -44,41 +46,120 @@ public interface Logger {
*/
void error(@Nullable String message);

/**
* Logs a message at the {@link LogLevel#ERROR} level. The supplier is only invoked if the log level threshold
* is at ERROR or below.
* @param messageSupplier A function that returns an error message
*/
default void error(@NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.ERROR)) {
error(messageSupplier.get());
}
}

/**
* Logs a message and thrown error at {@link LogLevel#ERROR} level.
* @param message An error message
* @param error A thrown error
*/
void error(@Nullable String message, @Nullable Throwable error);

/**
* Logs a message and thrown error at {@link LogLevel#ERROR} level. The supplier is only invoked if the log level
* threshold is at ERROR or below.
* @param error A thrown error
* @param messageSupplier A function that returns an error message
*/
default void error(@Nullable Throwable error, @NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.ERROR)) {
error(messageSupplier.get(), error);
}
}

/**
* Log a message at the {@link LogLevel#WARN} level.
* @param message A warning message
*/
void warn(@Nullable String message);

/**
* Log a message at the {@link LogLevel#WARN} level. The supplier is only invoked if the log level threshold
* is at WARN or below.
* @param messageSupplier A function that returns a warning message
*/
default void warn(@NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.WARN)) {
warn(messageSupplier.get());
}
}

/**
* Log a message and a throwable issue at the {@link LogLevel#WARN} level.
* @param message A warning message
* @param issue An issue that caused this warning
*/
void warn(@Nullable String message, @Nullable Throwable issue);

/**
* Log a message and a throwable issue at the {@link LogLevel#WARN} level. The supplier is only invoked if the
* log level threshold is at WARN or below.
* @param issue An issue that caused this warning
* @param messageSupplier A function that returns a warning message
*/
default void warn(@Nullable Throwable issue, @NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.WARN)) {
warn(messageSupplier.get(), issue);
}
}

/**
* Logs a message at {@link LogLevel#INFO} level.
* @param message An informational message
*/
void info(@Nullable String message);

/**
* Logs a message at {@link LogLevel#INFO} level. The supplier is only invoked if the log level threshold
* is at INFO or below.
* @param messageSupplier A function that returns an info message
*/
default void info(@NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.INFO)) {
info(messageSupplier.get());
}
}

/**
* Logs a message at the {@link LogLevel#DEBUG} level.
* @param message A debugging message.
*/
void debug(@Nullable String message);

/**
* Logs a message at the {@link LogLevel#DEBUG} level. The supplier is only invoked if the log level threshold
* is at DEBUG or below.
* @param messageSupplier A function that returns a debugging message
*/
default void debug(@NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.DEBUG)) {
debug(messageSupplier.get());
}
}

/**
* Logs a message at the {@link LogLevel#VERBOSE} level.
* @param message A verbose message
*/
void verbose(@Nullable String message);

/**
* Logs a message at the {@link LogLevel#VERBOSE} level. The supplier is only invoked if the log level threshold
* is at VERBOSE.
* @param messageSupplier A function that returns a verbose message
*/
default void verbose(@NonNull Supplier<String> messageSupplier) {
if (!getThresholdLevel().above(LogLevel.VERBOSE)) {
verbose(messageSupplier.get());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void allContentLoggedAtThresholdVerbose() {

logger.verbose("This logs");
logger.debug("This too");
logger.info(null);
logger.info((String) null);
logger.warn("Getting serious...");
logger.error("It. Got. Serious.");

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

logger.error("An error happened!");
logger.info(null);
logger.info((String) null);
logger.warn("Uh oh, not great...");

assertTrue(systemLog.getLines().isEmpty());
Expand Down
11 changes: 11 additions & 0 deletions core/src/test/java/com/amplifyframework/logging/FakeLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ static FakeLogger instance(@NonNull String namespace, @NonNull LogLevel threshol
return new FakeLogger(namespace, threshold);
}

static FakeLogger instance(@NonNull LogLevel threshold) {
return instance("", threshold);
}

@NonNull
@Override
public LogLevel getThresholdLevel() {
Expand Down Expand Up @@ -127,6 +131,13 @@ static Log instance(@NonNull LogLevel level, @Nullable String message, @Nullable
return new Log(level, message, throwable);
}

void assertEquals(
@Nullable LogLevel actualLevel,
@Nullable String actualMessage
) {
assertEquals(actualLevel, actualMessage, null);
}

void assertEquals(
@Nullable LogLevel actualLevel,
@Nullable String actualMessage,
Expand Down
124 changes: 124 additions & 0 deletions core/src/test/java/com/amplifyframework/logging/LoggerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.amplifyframework.logging

import io.kotest.matchers.collections.shouldBeEmpty
import io.mockk.mockk
import org.junit.Test

class LoggerTest {
@Test
fun `verbose log emitted`() {
val logger = FakeLogger.instance(LogLevel.VERBOSE)
logger.verbose { "test" }
logger.logs.first().assertEquals(LogLevel.VERBOSE, "test")
}

@Test
fun `verbose log not emitted`() {
val logger = FakeLogger.instance(LogLevel.INFO)
logger.verbose { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `debug log emitted`() {
val logger = FakeLogger.instance(LogLevel.DEBUG)
logger.debug { "test" }
logger.logs.first().assertEquals(LogLevel.DEBUG, "test")
}

@Test
fun `debug log not emitted`() {
val logger = FakeLogger.instance(LogLevel.INFO)
logger.debug { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `info log emitted`() {
val logger = FakeLogger.instance(LogLevel.INFO)
logger.info { "test" }
logger.logs.first().assertEquals(LogLevel.INFO, "test")
}

@Test
fun `info log not emitted`() {
val logger = FakeLogger.instance(LogLevel.WARN)
logger.info { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `warn log emitted`() {
val logger = FakeLogger.instance(LogLevel.WARN)
logger.warn { "test" }
logger.logs.first().assertEquals(LogLevel.WARN, "test")
}

@Test
fun `warn log not emitted`() {
val logger = FakeLogger.instance(LogLevel.ERROR)
logger.warn { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `warn log emitted with throwable`() {
val throwable = mockk<Throwable>()
val logger = FakeLogger.instance(LogLevel.WARN)
logger.warn(throwable) { "test" }
logger.logs.first().assertEquals(LogLevel.WARN, "test", throwable)
}

@Test
fun `warn log not emitted with throwable`() {
val throwable = mockk<Throwable>()
val logger = FakeLogger.instance(LogLevel.ERROR)
logger.warn(throwable) { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `error log emitted`() {
val logger = FakeLogger.instance(LogLevel.ERROR)
logger.error { "test" }
logger.logs.first().assertEquals(LogLevel.ERROR, "test")
}

@Test
fun `error log not emitted`() {
val logger = FakeLogger.instance(LogLevel.NONE)
logger.error { "test" }
logger.logs.shouldBeEmpty()
}

@Test
fun `error log emitted with throwable`() {
val throwable = mockk<Throwable>()
val logger = FakeLogger.instance(LogLevel.ERROR)
logger.error(throwable) { "test" }
logger.logs.first().assertEquals(LogLevel.ERROR, "test", throwable)
}

@Test
fun `error log not emitted with throwable`() {
val throwable = mockk<Throwable>()
val logger = FakeLogger.instance(LogLevel.NONE)
logger.error(throwable) { "test" }
logger.logs.shouldBeEmpty()
}
}

0 comments on commit 9f900a0

Please sign in to comment.