Skip to content

Commit

Permalink
- Added option to log with suppliers (#1406)
Browse files Browse the repository at this point in the history
- Added option to log with suppliers
- made tests single threaded
  • Loading branch information
Yossi Farjoun authored Aug 12, 2019
1 parent 833a0e2 commit cea307e
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 36 deletions.
142 changes: 107 additions & 35 deletions src/main/java/htsjdk/samtools/util/Log.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.function.Supplier;

/**
* <p>A <em>wafer thin</em> wrapper around System.err that uses var-args to make it
Expand All @@ -40,8 +41,10 @@
* @author Tim Fennell
*/
public final class Log {
/** Enumeration for setting log levels. */
public enum LogLevel { ERROR, WARNING, INFO, DEBUG }
/**
* Enumeration for setting log levels.
*/
public enum LogLevel {ERROR, WARNING, INFO, DEBUG}

private static LogLevel globalLogLevel = LogLevel.INFO;
private static PrintStream out = System.err;
Expand All @@ -60,6 +63,7 @@ private Log(final Class<?> clazz) {
/**
* Get a Log instance to perform logging within the Class specified. Returns an instance
* of this class which wraps an instance of the commons logging Log class.
*
* @param clazz the Class which is going to be doing the logging
* @return a Log instance with which to log
*/
Expand All @@ -70,7 +74,7 @@ public static Log getInstance(final Class<?> clazz) {
/**
* Set the log level.
*
* @param logLevel The log level enumeration
* @param logLevel The log level enumeration
*/
public static void setGlobalLogLevel(final LogLevel logLevel) {
globalLogLevel = logLevel;
Expand All @@ -88,21 +92,24 @@ public static LogLevel getGlobalLogLevel() {
/**
* Set the {@link PrintStream} for writing.
*
* @param stream {@link PrintStream} to write to.
* @param stream {@link PrintStream} to write to.
*/
public static void setGlobalPrintStream(final PrintStream stream) { out = stream; }
public static void setGlobalPrintStream(final PrintStream stream) {
out = stream;
}

/**
* Get the {@link PrintStream} for writing.
*
* @return {@link PrintStream} to write to.
* @return {@link PrintStream} to write to.
*/
public static PrintStream getGlobalPrintStream() {
return out;
}


/** Returns true if the specified log level is enabled otherwise false. */
/**
* Returns true if the specified log level is enabled otherwise false.
*/
public static final boolean isEnabled(final LogLevel level) {
return level.ordinal() <= globalLogLevel.ordinal();
}
Expand All @@ -111,9 +118,9 @@ public static final boolean isEnabled(final LogLevel level) {
* Private method that does the actual printing of messages to a PrintWriter. Outputs the log level,
* class name and parts followed by the stack trace if a throwable is provided.
*
* @param level the Log level being logged at
* @param level the Log level being logged at
* @param throwable a Throwable if one is available otherwise null
* @param parts the parts of the message to be concatenated
* @param parts the parts of the message to be concatenated
*/
private void emit(final LogLevel level, final Throwable throwable, final Object... parts) {
if (isEnabled(level)) {
Expand All @@ -128,17 +135,26 @@ private void emit(final LogLevel level, final Throwable throwable, final Object.
for (final Object part : parts) {
if (part != null && part.getClass().isArray()) {
final Class<?> component = part.getClass().getComponentType();
if (component.equals(Boolean.TYPE)) tmp.append(Arrays.toString( (boolean[]) part));
else if (component.equals(Byte.TYPE)) tmp.append(Arrays.toString( (byte[]) part));
else if (component.equals(Character.TYPE)) tmp.append(Arrays.toString( (char[]) part));
else if (component.equals(Double.TYPE)) tmp.append(Arrays.toString( (double[]) part));
else if (component.equals(Float.TYPE)) tmp.append(Arrays.toString( (float[]) part));
else if (component.equals(Integer.TYPE)) tmp.append(Arrays.toString( (int[]) part));
else if (component.equals(Long.TYPE)) tmp.append(Arrays.toString( (long[]) part));
else if (component.equals(Short.TYPE)) tmp.append(Arrays.toString( (short[]) part));
else tmp.append(Arrays.toString( (Object[]) part));
}
else {
if (component.equals(Boolean.TYPE)) {
tmp.append(Arrays.toString((boolean[]) part));
} else if (component.equals(Byte.TYPE)) {
tmp.append(Arrays.toString((byte[]) part));
} else if (component.equals(Character.TYPE)) {
tmp.append(Arrays.toString((char[]) part));
} else if (component.equals(Double.TYPE)) {
tmp.append(Arrays.toString((double[]) part));
} else if (component.equals(Float.TYPE)) {
tmp.append(Arrays.toString((float[]) part));
} else if (component.equals(Integer.TYPE)) {
tmp.append(Arrays.toString((int[]) part));
} else if (component.equals(Long.TYPE)) {
tmp.append(Arrays.toString((long[]) part));
} else if (component.equals(Short.TYPE)) {
tmp.append(Arrays.toString((short[]) part));
} else {
tmp.append(Arrays.toString((Object[]) part));
}
} else {
tmp.append(part);
}
}
Expand All @@ -149,8 +165,7 @@ private void emit(final LogLevel level, final Throwable throwable, final Object.
this.out.println(tmp.toString());
throwable.printStackTrace(this.out);
}
}
else {
} else {
this.out.println(tmp.toString());
}
}
Expand All @@ -167,39 +182,43 @@ protected String getTimestamp() {

/**
* Logs a Throwable and optional message parts at level error.
* @param throwable an instance of Throwable that should be logged with stack trace
*
* @param throwable an instance of Throwable that should be logged with stack trace
* @param messageParts zero or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void error(final Throwable throwable, final Object... messageParts) {
emit(LogLevel.ERROR, throwable, messageParts);
}

/**
* Logs a Throwable and optional message parts at level warn.
* @param throwable an instance of Throwable that should be logged with stack trace
*
* @param throwable an instance of Throwable that should be logged with stack trace
* @param messageParts zero or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void warn(final Throwable throwable, final Object... messageParts) {
emit(LogLevel.WARNING, throwable, messageParts);
}

/**
* Logs a Throwable and optional message parts at level info.
* @param throwable an instance of Throwable that should be logged with stack trace
*
* @param throwable an instance of Throwable that should be logged with stack trace
* @param messageParts zero or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void info(final Throwable throwable, final Object... messageParts) {
emit(LogLevel.INFO, throwable, messageParts);
}

/**
* Logs a Throwable and optional message parts at level debug.
* @param throwable an instance of Throwable that should be logged with stack trace
*
* @param throwable an instance of Throwable that should be logged with stack trace
* @param messageParts zero or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void debug(final Throwable throwable, final Object... messageParts) {
emit(LogLevel.DEBUG, throwable, messageParts);
Expand All @@ -209,37 +228,90 @@ public final void debug(final Throwable throwable, final Object... messageParts)

/**
* Logs one or more message parts at level error.
*
* @param messageParts one or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void error(final Object... messageParts) {
emit(LogLevel.ERROR, null, messageParts);
}

/**
* Logs one or more message parts at level warn.
*
* @param messageParts one or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void warn(final Object... messageParts) {
emit(LogLevel.WARNING, null, messageParts);
}

/**
* Logs one or more message parts at level info.
*
* @param messageParts one or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void info(final Object... messageParts) {
emit(LogLevel.INFO, null, messageParts);
}

/**
* Logs one or more message parts at level debug.
*
* @param messageParts one or more objects which should be combined, by calling toString()
* to form the log message.
* to form the log message.
*/
public final void debug(final Object... messageParts) {
emit(LogLevel.DEBUG, null, messageParts);
}

// Similar methods, but with Suppliers, follow. These enable avoiding generating the message
// if the logging level is such that the message will be dropped.

private final void getAndEmitIfEnabled(LogLevel logLevel, final Supplier<Object> messageParts) {
if (Log.isEnabled(logLevel)) {
emit(logLevel, null, messageParts.get());
}
}

/**
* Logs a message part at level error.
*
* @param messageParts a supplier of the object that will be obtained and then toString'ed
* to form the log message.
*/
public final void error(final Supplier<Object> messageParts) {
getAndEmitIfEnabled(LogLevel.ERROR, messageParts);
}

/**
* Logs a message part at level warn.
*
* @param messageParts a supplier of the object that will be obtained and then toString'ed
* to form the log message.
*/
public final void warn(final Supplier<Object> messageParts) {
getAndEmitIfEnabled(LogLevel.WARNING, messageParts);
}

/**
* Logs a message part at level info.
*
* @param messageParts a supplier of the object that will be obtained and then toString'ed
* to form the log message.
*/
public final void info(final Supplier<Object> messageParts) {
getAndEmitIfEnabled(LogLevel.INFO, messageParts);
}

/**
* Logs a message at level debug.
*
* @param messageParts a supplier of the object that will be obtained and then toString'ed
* to form the log message.
*/
public final void debug(final Supplier<Object> messageParts) {
getAndEmitIfEnabled(LogLevel.DEBUG, messageParts);
}
}
40 changes: 39 additions & 1 deletion src/test/java/htsjdk/samtools/util/LogTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import htsjdk.HtsjdkTest;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.io.File;
Expand All @@ -12,6 +11,7 @@
import java.nio.file.Files;
import java.util.List;

@Test(singleThreaded = true)
public class LogTest extends HtsjdkTest {

private final Log log = Log.getInstance(getClass());
Expand All @@ -38,4 +38,42 @@ public void testLogToFile() throws IOException {
Log.setGlobalPrintStream(originalStream);
}
}

@Test
public void testLogToFileWithSupplier() throws IOException {
final File logFile = File.createTempFile(getClass().getSimpleName(), ".tmp");
logFile.deleteOnExit();

final Log.LogLevel originalLogLevel = Log.getGlobalLogLevel();
final PrintStream originalStream = Log.getGlobalPrintStream();

try (final PrintStream stream = new PrintStream(new FileOutputStream(logFile.getPath(), true))) {
Log.setGlobalPrintStream(stream);
Log.setGlobalLogLevel(Log.LogLevel.DEBUG);
final String words = "Hello World";
log.info(() -> words);
final List<String> list = Files.readAllLines(logFile.toPath());
Assert.assertEquals(Log.getGlobalLogLevel(), Log.LogLevel.DEBUG);
Assert.assertEquals(list.size(), 1);
Assert.assertTrue(list.get(0).contains(words));
} finally {
Log.setGlobalLogLevel(originalLogLevel);
Log.setGlobalPrintStream(originalStream);
}
}

@Test
public void testSupplierIsntCalled() {
final Log.LogLevel originalLogLevel = Log.getGlobalLogLevel();

try {
Log.setGlobalLogLevel(Log.LogLevel.WARNING);
log.info(() -> {
throw new RuntimeException("Shouldn't happen!");
});

} finally {
Log.setGlobalLogLevel(originalLogLevel);
}
}
}

0 comments on commit cea307e

Please sign in to comment.