Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Add APIs to lazily evaluate log statements #2811

Merged
merged 7 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(() -> String.format("Column with name %s does not exist", field.getName()));
return null;
}

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

final String valueAsString = cursor.getString(columnIndex);
LOGGER.verbose(String.format(
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()
));
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()
}
}
Loading