Skip to content

Commit

Permalink
Java: Add Logger (valkey-io#1422)
Browse files Browse the repository at this point in the history
* Implement Logger for Java client

Signed-off-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
3 people authored and cyip10 committed Jul 16, 2024
1 parent 66d6b9a commit 754a971
Show file tree
Hide file tree
Showing 12 changed files with 477 additions and 29 deletions.
219 changes: 219 additions & 0 deletions java/client/src/main/java/glide/api/logging/Logger.java
Original file line number Diff line number Diff line change
@@ -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 -
*
* <ol>
* <li>By calling <code>Logger.init</code>, which configures the logger only if it wasn't
* previously configured.
* <li>By calling <code>Logger.setLoggerConfig</code>, which replaces the existing configuration,
* and means that new logs will not be saved with the logs that were sent before the call.
* </ol>
*
* If <code>setLoggerConfig</code> 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 <code>fileName</code> argument, will write the
* logs to files postfixed with <code>fileName</code>. If <code>fileName</code> isn't provided,
* the logs will be written to the console.
*
* @param level Set the logger level to one of <code>
* [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
* </code>. 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 <code>fileName</code>
* argument, will write the logs to files postfixed with <code>fileName</code>.
*
* @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 <code>[DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
* </code>. 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 <code>Supplier</code> 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 <code>Supplier</code> of the message to log.
*/
public static void log(
@NonNull Level level,
@NonNull String logIdentifier,
@NonNull Supplier<String> messageSupplier) {
if (loggerLevel == null) {
initLogger(Level.DEFAULT, null);
}

if (level == Level.DISABLED) {
return;
}

if (!(level.getLevel() <= loggerLevel.getLevel())) {
return;
}
logInternal(level.getLevel(), logIdentifier, messageSupplier.get());
}

/**
* Logs the provided message if the provided log level is lower than the logger level.
*
* @param level The log level of the provided message.
* @param logIdentifier The log identifier should give the log a context.
* @param message The message to log.
*/
public static void log(
@NonNull Level level, @NonNull String logIdentifier, @NonNull String message) {
if (loggerLevel == null) {
initLogger(Level.DEFAULT, null);
}

if (level == Level.DISABLED) {
return;
}

if (!(level.getLevel() <= loggerLevel.getLevel())) {
return;
}
logInternal(level.getLevel(), logIdentifier, message);
}

/**
* Creates a new logger instance and configure it with the provided log level and file name.
*
* @param level Set the logger level to one of <code>
* [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
* </code>. 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 <code>
* [DISABLED, DEFAULT, ERROR, WARN, INFO, DEBUG, TRACE]
* </code>. 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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")
Expand All @@ -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]));
Expand All @@ -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));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions java/client/src/main/java/glide/ffi/resolvers/LoggerResolver.java
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions java/client/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading

0 comments on commit 754a971

Please sign in to comment.