diff --git a/java/client/src/main/java/glide/api/logging/Logger.java b/java/client/src/main/java/glide/api/logging/Logger.java new file mode 100644 index 0000000000..744c5c2ddc --- /dev/null +++ b/java/client/src/main/java/glide/api/logging/Logger.java @@ -0,0 +1,219 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.logging; + +import static glide.ffi.resolvers.LoggerResolver.initInternal; +import static glide.ffi.resolvers.LoggerResolver.logInternal; + +import java.util.function.Supplier; +import lombok.Getter; +import lombok.NonNull; + +/** + * A singleton class that allows logging which is consistent with logs from the internal rust core. + * The logger can be set up in 2 ways - + * + *
Logger.init
, which configures the logger only if it wasn't
+ * previously configured.
+ * Logger.setLoggerConfig
, which replaces the existing configuration,
+ * and means that new logs will not be saved with the logs that were sent before the call.
+ * setLoggerConfig
wasn't called, the first log attempt will initialize a new logger
+ * with default configuration decided by Glide core.
+ */
+public final class Logger {
+ @Getter
+ public enum Level {
+ DISABLED(-2),
+ DEFAULT(-1),
+ ERROR(0),
+ WARN(1),
+ INFO(2),
+ DEBUG(3),
+ TRACE(4);
+
+ private final int level;
+
+ Level(int level) {
+ this.level = level;
+ }
+
+ public static Level fromInt(int i) {
+ switch (i) {
+ case 0:
+ return ERROR;
+ case 1:
+ return WARN;
+ case 2:
+ return INFO;
+ case 3:
+ return DEBUG;
+ case 4:
+ return TRACE;
+ default:
+ return DEFAULT;
+ }
+ }
+ }
+
+ @Getter private static Level loggerLevel;
+
+ private static void initLogger(@NonNull Level level, String fileName) {
+ if (level == Level.DISABLED) {
+ loggerLevel = level;
+ return;
+ }
+ loggerLevel = Level.fromInt(initInternal(level.getLevel(), fileName));
+ }
+
+ /**
+ * Initialize a logger if it wasn't initialized before - this method is meant to be used when
+ * there is no intention to replace an existing logger. The logger will filter all logs with a
+ * level lower than the given level. If given a fileName
argument, will write the
+ * logs to files postfixed with fileName
. If fileName
isn't provided,
+ * the logs will be written to the console.
+ *
+ * @param level Set the logger level to one of
+ * [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
+ *
. If log level isn't provided, the logger will be configured with default
+ * configuration decided by Glide core.
+ * @param fileName If provided, the target of the logs will be the file mentioned. Otherwise, logs
+ * will be printed to the console.
+ */
+ public static void init(@NonNull Level level, String fileName) {
+ if (loggerLevel == null) {
+ initLogger(level, fileName);
+ }
+ }
+
+ /**
+ * Initialize a logger if it wasn't initialized before - this method is meant to be used when
+ * there is no intention to replace an existing logger. The logger will filter all logs with a
+ * level lower than the default level decided by Glide core. Given a fileName
+ * argument, will write the logs to files postfixed with fileName
.
+ *
+ * @param fileName The target of the logs will be the file mentioned.
+ */
+ public static void init(@NonNull String fileName) {
+ init(Level.DEFAULT, fileName);
+ }
+
+ /**
+ * Initialize a logger if it wasn't initialized before - this method is meant to be used when
+ * there is no intention to replace an existing logger. The logger will filter all logs with a
+ * level lower than the default level decided by Glide core. The logs will be written to stdout.
+ */
+ public static void init() {
+ init(Level.DEFAULT, null);
+ }
+
+ /**
+ * Initialize a logger if it wasn't initialized before - this method is meant to be used when
+ * there is no intention to replace an existing logger. The logger will filter all logs with a
+ * level lower than the given level. The logs will be written to stdout.
+ *
+ * @param level Set the logger level to one of [DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
+ *
. If log level isn't provided, the logger will be configured with default
+ * configuration decided by Glide core.
+ */
+ public static void init(@NonNull Level level) {
+ init(level, null);
+ }
+
+ /**
+ * Logs the provided message if the provided log level is lower than the logger level. This
+ * overload takes a Supplier
to lazily construct the message.
+ *
+ * @param level The log level of the provided message.
+ * @param logIdentifier The log identifier should give the log a context.
+ * @param messageSupplier The Supplier
of the message to log.
+ */
+ public static void log(
+ @NonNull Level level,
+ @NonNull String logIdentifier,
+ @NonNull Supplier
+ * [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
+ *
. If log level isn't provided, the logger will be configured with default
+ * configuration decided by Glide core.
+ * @param fileName If provided, the target of the logs will be the file mentioned. Otherwise, logs
+ * will be printed to stdout.
+ */
+ public static void setLoggerConfig(@NonNull Level level, String fileName) {
+ initLogger(level, fileName);
+ }
+
+ /**
+ * Creates a new logger instance and configure it with the provided log level. The logs will be
+ * written to stdout.
+ *
+ * @param level Set the logger level to one of
+ * [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
+ *
. If log level isn't provided, the logger will be configured with default
+ * configuration decided by Glide core.
+ */
+ public static void setLoggerConfig(@NonNull Level level) {
+ setLoggerConfig(level, null);
+ }
+
+ /**
+ * Creates a new logger instance and configure it with the provided file name and default log
+ * level. The logger will filter all logs with a level lower than the default level decided by the
+ * Glide core.
+ *
+ * @param fileName If provided, the target of the logs will be the file mentioned. Otherwise, logs
+ * will be printed to stdout.
+ */
+ public static void setLoggerConfig(String fileName) {
+ setLoggerConfig(Level.DEFAULT, fileName);
+ }
+
+ /**
+ * Creates a new logger instance. The logger will filter all logs with a level lower than the
+ * default level decided by Glide core. The logs will be written to stdout.
+ */
+ public static void setLoggerConfig() {
+ setLoggerConfig(Level.DEFAULT, null);
+ }
+}
diff --git a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java
index 03b499dd66..cfd349afdf 100644
--- a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java
+++ b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java
@@ -1,6 +1,9 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.connectors.handlers;
+import static glide.api.logging.Logger.Level.ERROR;
+
+import glide.api.logging.Logger;
import glide.api.models.exceptions.ClosingException;
import glide.api.models.exceptions.ConnectionException;
import glide.api.models.exceptions.ExecAbortException;
@@ -115,11 +118,15 @@ public void completeRequest(Response response) {
}
future.completeAsync(() -> response);
} else {
- // TODO: log an error thru logger.
// probably a response was received after shutdown or `registerRequest` call was missing
- System.err.printf(
- "Received a response for not registered callback id %d, request error = %s%n",
- callbackId, response.getRequestError());
+ Logger.log(
+ ERROR,
+ "callback dispatcher",
+ () ->
+ "Received a response for not registered callback id "
+ + callbackId
+ + ", request error = "
+ + response.getRequestError());
distributeClosingException("Client is in an erroneous state and should close");
}
}
diff --git a/java/client/src/main/java/glide/connectors/handlers/MessageHandler.java b/java/client/src/main/java/glide/connectors/handlers/MessageHandler.java
index 74de6ddeb0..da2d16502a 100644
--- a/java/client/src/main/java/glide/connectors/handlers/MessageHandler.java
+++ b/java/client/src/main/java/glide/connectors/handlers/MessageHandler.java
@@ -1,6 +1,7 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.connectors.handlers;
+import glide.api.logging.Logger;
import glide.api.models.PubSubMessage;
import glide.api.models.configuration.BaseSubscriptionConfiguration.MessageCallback;
import glide.api.models.exceptions.RedisException;
@@ -39,8 +40,10 @@ public class MessageHandler {
public void handle(Response response) {
Object data = responseResolver.apply(response);
if (!(data instanceof Map)) {
- // TODO log thru logger https://github.com/aws/glide-for-redis/pull/1422
- System.err.println("Received invalid push: empty or in incorrect format.");
+ Logger.log(
+ Logger.Level.WARN,
+ "invalid push",
+ "Received invalid push: empty or in incorrect format.");
throw new RedisException("Received invalid push: empty or in incorrect format.");
}
@SuppressWarnings("unchecked")
@@ -50,11 +53,10 @@ public void handle(Response response) {
switch (pushType) {
case Disconnection:
- // TODO log thru logger https://github.com/aws/glide-for-redis/pull/1422
- // ClientLogger.log(
- // LogLevel.WARN,
- // "disconnect notification",
- // "Transport disconnected, messages might be lost",
+ Logger.log(
+ Logger.Level.WARN,
+ "disconnect notification",
+ "Transport disconnected, messages might be lost");
break;
case PMessage:
handle(new PubSubMessage((String) values[2], (String) values[1], (String) values[0]));
@@ -70,21 +72,22 @@ public void handle(Response response) {
case PUnsubscribe:
case SUnsubscribe:
// ignore for now
- // TODO log thru logger https://github.com/aws/glide-for-redis/pull/1422
- System.out.printf(
- "Received push notification of type '%s': %s\n",
- pushType,
- Arrays.stream(values)
- .map(v -> v instanceof Number ? v.toString() : String.format("'%s'", v))
- .collect(Collectors.joining(" ")));
+ Logger.log(
+ Logger.Level.INFO,
+ "subscribe/unsubscribe notification",
+ () ->
+ String.format(
+ "Received push notification of type '%s': %s",
+ pushType,
+ Arrays.stream(values)
+ .map(v -> v instanceof Number ? v.toString() : String.format("'%s'", v))
+ .collect(Collectors.joining(" "))));
break;
default:
- // TODO log thru logger https://github.com/aws/glide-for-redis/pull/1422
- System.err.printf("Received push with unsupported type: %s.\n", pushType);
- // ClientLogger.log(
- // LogLevel.WARN,
- // "unknown notification",
- // f"Unknown notification message: '{message_kind}'",
+ Logger.log(
+ Logger.Level.WARN,
+ "unknown notification",
+ () -> String.format("Unknown notification message: '%s'", pushType));
}
}
diff --git a/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java b/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java
index ca801a6094..d78d144cea 100644
--- a/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java
+++ b/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java
@@ -1,6 +1,9 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.connectors.handlers;
+import static glide.api.logging.Logger.Level.ERROR;
+
+import glide.api.logging.Logger;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.NonNull;
@@ -29,9 +32,7 @@ public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg)
/** Handles uncaught exceptions from {@link #channelRead(ChannelHandlerContext, Object)}. */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- // TODO: log thru logger
- System.out.printf("=== exceptionCaught %s %s %n", ctx, cause);
- cause.printStackTrace();
+ Logger.log(ERROR, "read handler", () -> "=== exceptionCaught " + ctx + " " + cause);
callbackDispatcher.distributeClosingException(
"An unhandled error while reading from UDS channel: " + cause);
diff --git a/java/client/src/main/java/glide/ffi/resolvers/LoggerResolver.java b/java/client/src/main/java/glide/ffi/resolvers/LoggerResolver.java
new file mode 100644
index 0000000000..4c1e2469cc
--- /dev/null
+++ b/java/client/src/main/java/glide/ffi/resolvers/LoggerResolver.java
@@ -0,0 +1,13 @@
+/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
+package glide.ffi.resolvers;
+
+public class LoggerResolver {
+ // TODO: consider lazy loading the glide_rs library
+ static {
+ NativeUtils.loadGlideLib();
+ }
+
+ public static native int initInternal(int level, String fileName);
+
+ public static native void logInternal(int level, String logIdentifier, String message);
+}
diff --git a/java/client/src/main/java/module-info.java b/java/client/src/main/java/module-info.java
index 2dbaca04f7..99c4655082 100644
--- a/java/client/src/main/java/module-info.java
+++ b/java/client/src/main/java/module-info.java
@@ -1,6 +1,7 @@
module glide.api {
exports glide.api;
exports glide.api.commands;
+ exports glide.api.logging;
exports glide.api.models;
exports glide.api.models.commands;
exports glide.api.models.commands.bitmap;
diff --git a/java/client/src/test/java/glide/ExceptionHandlingTests.java b/java/client/src/test/java/glide/ExceptionHandlingTests.java
index f59b50af4d..d383797bd5 100644
--- a/java/client/src/test/java/glide/ExceptionHandlingTests.java
+++ b/java/client/src/test/java/glide/ExceptionHandlingTests.java
@@ -6,6 +6,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static response.ResponseOuterClass.RequestErrorType.Disconnect;
import static response.ResponseOuterClass.RequestErrorType.ExecAbort;
@@ -13,6 +14,7 @@
import static response.ResponseOuterClass.RequestErrorType.Unspecified;
import connection_request.ConnectionRequestOuterClass;
+import glide.api.logging.Logger;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.api.models.exceptions.ClosingException;
import glide.api.models.exceptions.ConnectionException;
@@ -34,19 +36,24 @@
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
import redis_request.RedisRequestOuterClass.RedisRequest;
import response.ResponseOuterClass.RequestError;
import response.ResponseOuterClass.RequestErrorType;
import response.ResponseOuterClass.Response;
public class ExceptionHandlingTests {
+ private MockedStatic