Skip to content

Instantly share code, notes, and snippets.

@Fyzxs
Last active January 19, 2017 01:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fyzxs/0950c21522febcb634a38ed1eeefb400 to your computer and use it in GitHub Desktop.
Save Fyzxs/0950c21522febcb634a38ed1eeefb400 to your computer and use it in GitHub Desktop.
Refactoring the FyzLog for https://blog.quantityandconversion.com
private static void println(final int level, final int logLevel, final String msgFormat, final Object... args) {
...
}
private static void log(final int level, final int logLevel, final String msgFormat, final Object... args) {
...
}
private String configureSystemTest(final String prefix, final int level) {
systemOutRule.clearLog();
FyzLog.doPrint = true;
final String logLevel = prefix + "@" + getLevelTag(level) + "/ ";
final String tag = "FYZ:TheFyzLogTests ";
final String thread = "[main] ";
return logLevel + tag + thread;
}
package com.quantityandconversion.log;
import android.util.Log;
import java.util.Locale;
public class Logger {
private static final String TAG_PREFIX = "FYZ:";
private static void println(final int level, final String msgFormat, final Object... args) {
if(msgFormat == null) throw new IllegalArgumentException("FyzLog message can not be null");
final StackTraceElement frame = getCallingStackTraceElement();
final String output =
getLevelTag(level) + "@" + getLevelTag(logLevel) + "/ " +
createTag(frame) + " " +
createMessage(frame, String.format(Locale.US, msgFormat, args));
System.out.println(output);
}
private static String getLevelTag(final int level) {
switch (level) {
case Log.VERBOSE:
return "V";
case Log.DEBUG:
return "D";
case Log.INFO:
return "I";
case Log.WARN:
return "W";
case Log.ERROR:
return "E";
case Log.ASSERT:
return "WTF";
default:
return "?";
}
}
private static String createTag(final StackTraceElement frame) {
final String fullClassName = frame.getClassName();
final String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
return TAG_PREFIX + className;
}
private static String createMessage(final StackTraceElement frame, final String msg) {
return String.format(Locale.US,
"[%s] %s : %s",
Thread.currentThread().getName(),
frame.getMethodName(),
msg);
}
private static StackTraceElement getCallingStackTraceElement() {
boolean hitLogger = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
final boolean isLogger = ste.getClassName().startsWith(FyzLog.class.getName());
hitLogger = hitLogger || isLogger;
if (hitLogger && !isLogger) {
return ste;
}
}
return new StackTraceElement(FyzLog.class.getName(),
"getCallingStackTraceElement",
null,
-1);
}
}
public static void wtf(final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.ASSERT, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
new Logger().println(Log.ASSERT, logLevel, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
@Test
public void massiveTestOfAllPrintMessageCombinations() {
//Tracks how many tests we performed
int ctr = 0;//Looping over the available Log calls
for (final String s : new String[]{"V", "D", "I", "W", "E", "WTF"}) {
//Looping over each logLevel, with an extra setting on each side
for (int i = Log.VERBOSE - 1; i <= Log.ASSERT + 1; i++) {
//Arrange
final String prefix = configureSystemTest(s, i);
final String msg = message();
final String method = Thread.currentThread().getStackTrace()[1].getMethodName() + " : ";
//Set the Log Level
FyzLog.logLevel = i;
//Prep the expected value
final String expected = prefix + method + msg;
//Act
switch (s) {
case "V":
FyzLog.v(msg);
break;
case "D":
FyzLog.d(msg);
break;
case "I":
FyzLog.i(msg);
break;
case "W":
FyzLog.w(msg);
break;
case "E":
FyzLog.e(msg);
break;
case "WTF":
FyzLog.wtf(msg);
break;
}
//Assert
final String target = systemOutRule.getLog();
assertEquals("Failed at [s=" + s + "][i=" + i + "]", expected + "\n", target);
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * (6 + 2));
}
public class LogLevel {
public final static LogLevel VERBOSE = new LogLevel(Log.VERBOSE, "V");
public final static LogLevel DEBUG = new LogLevel(Log.DEBUG, "D");
public final static LogLevel INFO = new LogLevel(Log.INFO, "I");
public final static LogLevel WARN = new LogLevel(Log.WARN, "W");
public final static LogLevel ERROR = new LogLevel(Log.ERROR, "E");
public final static LogLevel ASSERT = new LogLevel(Log.ASSERT, "WTF");
private final int level;
private final String tag;
public LogLevel(final int level, final String tag) {
this.level = level;
this.tag = tag;
}
public String tag() {
return tag;
}
public boolean logAt(final LogLevel other) {
return this.level >= other.level;
}
}
/* package */ void println(final int level, final int logLevel, final String msgFormat, final Object... args) {
...
}
@Test
public void massiveAndroidLogging() {
//Tracks how many tests we performed
int ctr = 0;
final LogLevel[] logLevels = new LogLevel[]{LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.ASSERT};
//Looping over the available Log calls
for (final LogLevel currentLogLevel : logLevels) {
//Set the Log Level
FyzLog.logLevel = currentLogLevel;
//Looping over each logLevel
for (final LogLevel attemptingLogLevel : logLevels) {
//Get the tag
//Determine if logging will be attempted, which will result in an exception
final boolean shouldTryToLog = attemptingLogLevel.logAt(currentLogLevel);
//Show what's about to be performed. If this breaks, it's HUGELY useful.
System.out.println("[currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "][shouldTryToLog=" + shouldTryToLog + "]");
//INVOKE
try {
if (attemptingLogLevel == LogLevel.VERBOSE)
FyzLog.v(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.DEBUG)
FyzLog.d(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.INFO)
FyzLog.i(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.WARN)
FyzLog.w(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ERROR)
FyzLog.e(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ASSERT)
FyzLog.wtf(messageFormat(), messageArgs());
//If we got here, we did not expect an exception, prove it
assertFalse("[currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "]", shouldTryToLog);
} catch (final RuntimeException e) {
//If we got here, we expected to, prove it
assertThat(shouldTryToLog).isTrue();
//Since this is kinda a catch all exception, make sure it's what we wanted
assertThat(e.getMessage()).isEqualTo("Method " + attemptingLogLevel.tag().toLowerCase() + " in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.");
}
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * 6);
}
@Test
public void massiveAndroidLogging() {
//Tracks how many tests we performed
int ctr = 0;
//Used to check if an exception should be thrown.
final int offset = Log.VERBOSE;
//The variation in the exception message
final String[] tags = new String[]{"v", "d", "i", "w", "e", "wtf"};
//Looping over the available Log calls
for (int idx = 0; idx < tags.length; idx++) {
//Looping over each logLevel, with an extra setting on each side
for (int i = Log.VERBOSE - 1; i <= Log.ASSERT + 1; i++) {
//Set the Log Level
FyzLog.logLevel = i;
//Get the tag
final String tag = tags[idx];
//Determine if logging will be attempted, which will result in an exception
final boolean shouldTryToLog = i <= idx + offset;
//Show what's about to be performed. If this breaks, it's HUGELY useful.
System.out.println("[idx=" + idx + ";" + tag + "][i=" + i + "][shouldTryToLog=" + shouldTryToLog + "]");
//INVOKE
try {
switch (tag) {
case "v":
FyzLog.v(messageFormat(), messageArgs());
break;
case "d":
FyzLog.d(messageFormat(), messageArgs());
break;
case "i":
FyzLog.i(messageFormat(), messageArgs());
break;
case "w":
FyzLog.w(messageFormat(), messageArgs());
break;
case "e":
FyzLog.e(messageFormat(), messageArgs());
break;
case "wtf":
FyzLog.wtf(messageFormat(), messageArgs());
break;
}
//If we got here, we did not expect an exception, prove it
assertFalse("[idx=" + idx + ";" + tag + "][i=" + i + "]", shouldTryToLog);
} catch (final RuntimeException e) {
//If we got here, we expected to, prove it
assertThat(shouldTryToLog).isTrue();
//Since this is kinda a catch all exception, make sure it's what we wanted
assertThat(e.getMessage()).isEqualTo("Method " + tag + " in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.");
}
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * (6 + 2));
}
/*
* Copyright (c) 2016 Fyzxs
*
* Licensed under The MIT License (MIT)
*/
package com.quantityandconversion.log;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import java.util.Locale;
/**
* Fyzxs Log(ger)
* <p>
* This lightweight static class wraps the Android logger.
* It allows customization of the logging process.
* <p>
* Most importantly, it allows control of the logging for
* unit testing purposes.
*/
public final class FyzLog {
//Understands handling a user request to log
/**
* This is the tag prefix that will be displayed in the tag component of the {@link Log#v(String, String) Android Log} methods
*/
private static final String TAG_PREFIX = "FYZ:";
/**
* Write Log Message to {@link System#out#println}
*/
@VisibleForTesting
public static boolean doPrint = false;
/**
* Controls the level of logging.
* This can be configured during build to limit logging messages to {@link Log#ERROR} or other levels.
* Default value is {@link Log#VERBOSE the lowest setting}
*/
@VisibleForTesting
public static int logLevel = Log.VERBOSE;
/**
* The {@link Log#VERBOSE} level logging
*
* @param msg The message to log
*/
public static void v(@NonNull final String msg) {
v(msg, (Object[]) null);
}
/**
* The {@link Log#VERBOSE} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void v(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.VERBOSE, msgFormat, args);
} else {
log(Log.VERBOSE, msgFormat, args);
}
}
/**
* The {@link Log#DEBUG} level logging
*
* @param msg The message to log
*/
public static void d(@NonNull final String msg) {
d(msg, (Object[]) null);
}
/**
* The {@link Log#DEBUG} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void d(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.DEBUG, msgFormat, args);
} else {
log(Log.DEBUG, msgFormat, args);
}
}
/**
* The {@link Log#INFO} level logging
*
* @param msg The message to log
*/
public static void i(@NonNull final String msg) {
i(msg, (Object[]) null);
}
/**
* The {@link Log#INFO} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void i(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.INFO, msgFormat, args);
} else {
log(Log.INFO, msgFormat, args);
}
}
/**
* The {@link Log#WARN} level logging
*
* @param msg The message to log
*/
public static void w(@NonNull final String msg) {
w(msg, (Object[]) null);
}
/**
* The {@link Log#WARN} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void w(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.WARN, msgFormat, args);
} else {
log(Log.WARN, msgFormat, args);
}
}
//endregion
//region error
/**
* The {@link Log#ERROR} level logging
*
* @param msg The message to log
*/
public static void e(@NonNull final String msg) {
e(msg, (Object[]) null);
}
/**
* The {@link Log#ERROR} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void e(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.ERROR, msgFormat, args);
} else {
log(Log.ERROR, msgFormat, args);
}
}
/**
* The {@link Log#ASSERT} level logging
*
* @param msg The message to log
*/
public static void wtf(@NonNull final String msg) {
wtf(msg, (Object[]) null);
}
/**
* The {@link Log#ASSERT} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
println(Log.ASSERT, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
/**
* Always writes the message to {@link System#out} if invoked
*
* @param level The Invoking Log Level
* @param msgFormat the message format string
* @param args the args to format in
*/
private static void println(final int level, final String msgFormat, final Object... args) {
if(msgFormat == null) throw new IllegalArgumentException("FyzLog message can not be null");
final StackTraceElement frame = getCallingStackTraceElement();
final String output =
getLevelTag(level) + "@" + getLevelTag(logLevel) + "/ " +
createTag(frame) + " " +
createMessage(frame, String.format(Locale.US, msgFormat, args));
System.out.println(output);
}
/**
* Helper method for the println
*
* @param level the invoking level of logging
* @return Letter Identifier
*/
private static String getLevelTag(final int level) {
switch (level) {
case Log.VERBOSE:
return "V";
case Log.DEBUG:
return "D";
case Log.INFO:
return "I";
case Log.WARN:
return "W";
case Log.ERROR:
return "E";
case Log.ASSERT:
return "WTF";
default:
return "?";
}
}
/**
* IFF the logLevel is high enough, logs to Android.
* <p>
* This will drop a null msg.
*
* @param level The invoking level of logging
* @param msgFormat the message format string
* @param args the args to format in
*/
private static void log(final int level, final String msgFormat, final Object... args) {
if (level >= logLevel && msgFormat != null) {
final StackTraceElement frame = getCallingStackTraceElement();
final String tag = createTag(frame);
final String msg = String.format(Locale.US, msgFormat, args);
final String message = createMessage(frame, msg);
switch (level) {
case Log.VERBOSE:
Log.v(tag, message);
break;
case Log.DEBUG:
Log.d(tag, message);
break;
case Log.INFO:
Log.i(tag, message);
break;
case Log.WARN:
Log.w(tag, message);
break;
case Log.ERROR:
Log.e(tag, message);
break;
case Log.ASSERT:
Log.wtf(tag, message);
break;
}
}
}
/**
* Creates the tag to be used
*
* @param frame {@link StackTraceElement} to build the tag from
* @return Tag to use for the logged message
*/
private static String createTag(final StackTraceElement frame) {
final String fullClassName = frame.getClassName();
final String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
return TAG_PREFIX + className;
}
/**
* Builds the final message to be logged.
* The format will be
* InvokingMethodName [currentThreadName] providedMessage
*
* @param frame The {@link StackTraceElement} to pull the method name from
* @param msg the message to prefix the method name to
* @return The final message string
*/
private static String createMessage(final StackTraceElement frame, final String msg) {
return String.format(Locale.US,
"[%s] %s : %s",
Thread.currentThread().getName(),
frame.getMethodName(),
msg);
}
/**
* Gets the {@link StackTraceElement} for the method that invoked logging
*
* @return {@link StackTraceElement} for the caller into {@link FyzLog}
*/
private static StackTraceElement getCallingStackTraceElement() {
boolean hitLogger = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
final boolean isLogger = ste.getClassName().startsWith(FyzLog.class.getName());
hitLogger = hitLogger || isLogger;
if (hitLogger && !isLogger) {
return ste;
}
}
return new StackTraceElement(FyzLog.class.getName(),
"getCallingStackTraceElement",
null,
-1);
}
}
/*
* Copyright (c) 2016 Fyzxs
*
* Licensed under The MIT License (MIT)
*/
package com.quantityandconversion.log;
import android.util.Log;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* !!! DO NOT USE THIS TEST CLASS AS AN EXAMPLE !!!
* <p>
* Predominate issues with this test class
* <p>
* + Prefixed with 'The'
* A proper name for this class would be "FyzLogTests".
* Doing so triggers a condition internal, which shouldn't be re-written to bypass unit tests.
* <p>
* + Using System.out
* Don't do this.
*/
@SuppressWarnings("ConstantConditions")
public class TheFyzLogTests {
@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
@Rule
public ExpectedException thrown = ExpectedException.none();
/**
* This MUST reflect the {@link FyzLog#getLevelTag(int)} method
*/
private static String getLevelTag(final int level) {
switch (level) {
case Log.VERBOSE:
return "V";
case Log.DEBUG:
return "D";
case Log.INFO:
return "I";
case Log.WARN:
return "W";
case Log.ERROR:
return "E";
case Log.ASSERT:
return "WTF";
default:
return "?";
}
}
@Before
public void setup() {
FyzLog.logLevel = Log.VERBOSE;
FyzLog.doPrint = false;
systemOutRule.clearLog();
}
private String configureSystemTest(final String prefix, final int level) {
systemOutRule.clearLog();
FyzLog.doPrint = true;
final String logLevel = prefix + "@" + getLevelTag(level) + "/ ";
final String tag = "FYZ:TheFyzLogTests ";
final String thread = "[main] ";
return logLevel + tag + thread;
}
private String message() {
return "the message";
}
private String messageFormat() {
return "%s %d %b %s";
}
private Object[] messageArgs() {
return new Object[]{"it", 2357, true, "is"};
}
@Test
public void massiveTestOfAllPrintMessageCombinations() {
//Tracks how many tests we performed
int ctr = 0;//Looping over the available Log calls
for (final String s : new String[]{"V", "D", "I", "W", "E", "WTF"}) {
//Looping over each logLevel, with an extra setting on each side
for (int i = Log.VERBOSE - 1; i <= Log.ASSERT + 1; i++) {
//Arrange
final String prefix = configureSystemTest(s, i);
final String msg = message();
final String method = Thread.currentThread().getStackTrace()[1].getMethodName() + " : ";
//Set the Log Level
FyzLog.logLevel = i;
//Prep the expected value
final String expected = prefix + method + msg;
//Act
switch (s) {
case "V":
FyzLog.v(msg);
break;
case "D":
FyzLog.d(msg);
break;
case "I":
FyzLog.i(msg);
break;
case "W":
FyzLog.w(msg);
break;
case "E":
FyzLog.e(msg);
break;
case "WTF":
FyzLog.wtf(msg);
break;
}
//Assert
final String target = systemOutRule.getLog();
assertEquals("Failed at [s=" + s + "][i=" + i + "]", expected + "\n", target);
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * (6 + 2));
}
@Test
public void printMessageThrowsNullPointerExceptionGivenNull() {
//Tracks how many tests we performed
int ctr = 0;
//We're printing
FyzLog.doPrint = true;
//Looping over each logLevel
for (int i = Log.VERBOSE; i <= Log.ASSERT; i++) {
try {
switch (i) {
case Log.VERBOSE:
FyzLog.v(null);
break;
case Log.DEBUG:
FyzLog.d(null);
break;
case Log.INFO:
FyzLog.i(null);
break;
case Log.WARN:
FyzLog.w(null);
break;
case Log.ERROR:
FyzLog.e(null);
break;
case Log.ASSERT:
FyzLog.wtf(null);
break;
}
//We're expecting exceptions, make sure we don't get here
fail("Should Have Thrown");
} catch (final IllegalArgumentException e) {
//Make sure it's the exception we wanted
assertThat(e.getMessage()).isEqualTo("FyzLog message can not be null");
}
//inc the test performed count
ctr++;
}
//Verify we performed the expected tests
assertThat(ctr).isEqualTo(6);
}
@Test
public void massiveTestOfAllPrintMessageFormatCombinations() {
//Tracks how many tests we performed
int ctr = 0;
//Looping over the available Log calls
for (final String s : new String[]{"V", "D", "I", "W", "E", "WTF"}) {
//Looping over each logLevel, with an extra setting on each side
for (int i = Log.VERBOSE - 1; i <= Log.ASSERT + 1; i++) {
//Arrange
final String prefix = configureSystemTest(s, i);
final String msg = messageFormat();
final String method = Thread.currentThread().getStackTrace()[1].getMethodName() + " : ";
//Set the Log Level
FyzLog.logLevel = i;
//Prep the expected value
final String expected = prefix + method + msg;
//Act
switch (s) {
case "V":
FyzLog.v(messageFormat(), messageArgs());
break;
case "D":
FyzLog.d(messageFormat(), messageArgs());
break;
case "I":
FyzLog.i(messageFormat(), messageArgs());
break;
case "W":
FyzLog.w(messageFormat(), messageArgs());
break;
case "E":
FyzLog.e(messageFormat(), messageArgs());
break;
case "WTF":
FyzLog.wtf(messageFormat(), messageArgs());
break;
}
//Assert
final String target = systemOutRule.getLog();
assertEquals("Failed at [s=" + s + "][i=" + i + "]",
target,
String.format(expected, messageArgs()) + "\n");
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * (6 + 2));
}
/**
* Can't (easily) assert that it formats, so this just tests logging or not functionality of the
* log level setting.
*/
@Test
public void massiveAndroidLogging() {
//Tracks how many tests we performed
int ctr = 0;
//Used to check if an exception should be thrown.
final int offset = Log.VERBOSE;
//The variation in the exception message
final String[] tags = new String[]{"v", "d", "i", "w", "e", "wtf"};
//Looping over the available Log calls
for (int idx = 0; idx < tags.length; idx++) {
//Looping over each logLevel, with an extra setting on each side
for (int i = Log.VERBOSE - 1; i <= Log.ASSERT + 1; i++) {
//Set the Log Level
FyzLog.logLevel = i;
//Get the tag
final String tag = tags[idx];
//Determine if logging will be attempted, which will result in an exception
final boolean shouldTryToLog = i <= idx + offset;
//Show what's about to be performed. If this breaks, it's HUGELY useful.
System.out.println("[idx=" + idx + ";" + tag + "][i=" + i + "][shouldTryToLog=" + shouldTryToLog + "]");
//INVOKE
try {
switch (tag) {
case "v":
FyzLog.v(messageFormat(), messageArgs());
break;
case "d":
FyzLog.d(messageFormat(), messageArgs());
break;
case "i":
FyzLog.i(messageFormat(), messageArgs());
break;
case "w":
FyzLog.w(messageFormat(), messageArgs());
break;
case "e":
FyzLog.e(messageFormat(), messageArgs());
break;
case "wtf":
FyzLog.wtf(messageFormat(), messageArgs());
break;
}
//If we got here, we did not expect an exception, prove it
assertFalse("[idx=" + idx + ";" + tag + "][i=" + i + "]", shouldTryToLog);
} catch (final RuntimeException e) {
//If we got here, we expected to, prove it
assertThat(shouldTryToLog).isTrue();
//Since this is kinda a catch all exception, make sure it's what we wanted
assertThat(e.getMessage()).isEqualTo("Method " + tag + " in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.");
}
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * (6 + 2));
}
@Test
public void androidLogDoesNothingGivenNull() {
FyzLog.doPrint = false;
systemOutRule.clearLog();
FyzLog.logLevel = Log.VERBOSE - 1;
//All calls should bail w/o doing anything
FyzLog.v(null);
FyzLog.d(null);
FyzLog.i(null);
FyzLog.w(null);
FyzLog.e(null);
FyzLog.wtf(null);
//Also should not have logged via printing
assertThat(systemOutRule.getLog()).isNullOrEmpty();
assertTrue("It should have gotten here", true);
}
}
/*
* Copyright (c) 2016 Fyzxs
*
* Licensed under The MIT License (MIT)
*/
package com.quantityandconversion.log;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import java.util.Locale;
/**
* Fyzxs Log(ger)
* <p>
* This lightweight static class wraps the Android logger.
* It allows customization of the logging process.
* <p>
* Most importantly, it allows control of the logging for
* unit testing purposes.
*/
public final class FyzLog {
//Understands handling a user request to log
/**
* This is the tag prefix that will be displayed in the tag component of the {@link Log#v(String, String) Android Log} methods
*/
private static final String TAG_PREFIX = "FYZ:";
/**
* Write Log Message to {@link System#out#println}
*/
@VisibleForTesting
public static boolean doPrint = false;
/**
* Controls the level of logging.
* This can be configured during build to limit logging messages to {@link Log#ERROR} or other levels.
* Default value is {@link Log#VERBOSE the lowest setting}
*/
@VisibleForTesting
public static int logLevel = Log.VERBOSE;
/**
* The {@link Log#VERBOSE} level logging
*
* @param msg The message to log
*/
public static void v(@NonNull final String msg) {
v(msg, (Object[]) null);
}
/**
* The {@link Log#VERBOSE} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void v(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.VERBOSE, logLevel, msgFormat, args);
} else {
log(Log.VERBOSE, msgFormat, args);
}
}
/**
* The {@link Log#DEBUG} level logging
*
* @param msg The message to log
*/
public static void d(@NonNull final String msg) {
d(msg, (Object[]) null);
}
/**
* The {@link Log#DEBUG} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void d(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.DEBUG, logLevel, msgFormat, args);
} else {
log(Log.DEBUG, msgFormat, args);
}
}
/**
* The {@link Log#INFO} level logging
*
* @param msg The message to log
*/
public static void i(@NonNull final String msg) {
i(msg, (Object[]) null);
}
/**
* The {@link Log#INFO} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void i(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.INFO, logLevel, msgFormat, args);
} else {
log(Log.INFO, msgFormat, args);
}
}
/**
* The {@link Log#WARN} level logging
*
* @param msg The message to log
*/
public static void w(@NonNull final String msg) {
w(msg, (Object[]) null);
}
/**
* The {@link Log#WARN} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void w(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.WARN, logLevel, msgFormat, args);
} else {
log(Log.WARN, msgFormat, args);
}
}
//endregion
//region error
/**
* The {@link Log#ERROR} level logging
*
* @param msg The message to log
*/
public static void e(@NonNull final String msg) {
e(msg, (Object[]) null);
}
/**
* The {@link Log#ERROR} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void e(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.ERROR, logLevel, msgFormat, args);
} else {
log(Log.ERROR, msgFormat, args);
}
}
/**
* The {@link Log#ASSERT} level logging
*
* @param msg The message to log
*/
public static void wtf(@NonNull final String msg) {
wtf(msg, (Object[]) null);
}
/**
* The {@link Log#ASSERT} level logging with string formatting
*
* @param msgFormat the format string
* @param args the args to format in
*/
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.ASSERT, logLevel, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
/**
* IFF the logLevel is high enough, logs to Android.
* <p>
* This will drop a null msg.
*
* @param level The invoking level of logging
* @param msgFormat the message format string
* @param args the args to format in
*/
private static void log(final int level, final String msgFormat, final Object... args) {
if (level >= logLevel && msgFormat != null) {
final StackTraceElement frame = getCallingStackTraceElement();
final String tag = createTag(frame);
final String msg = String.format(Locale.US, msgFormat, args);
final String message = createMessage(frame, msg);
switch (level) {
case Log.VERBOSE:
Log.v(tag, message);
break;
case Log.DEBUG:
Log.d(tag, message);
break;
case Log.INFO:
Log.i(tag, message);
break;
case Log.WARN:
Log.w(tag, message);
break;
case Log.ERROR:
Log.e(tag, message);
break;
case Log.ASSERT:
Log.wtf(tag, message);
break;
}
}
}
/**
* Creates the tag to be used
*
* @param frame {@link StackTraceElement} to build the tag from
* @return Tag to use for the logged message
*/
private static String createTag(final StackTraceElement frame) {
final String fullClassName = frame.getClassName();
final String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
return TAG_PREFIX + className;
}
/**
* Builds the final message to be logged.
* The format will be
* InvokingMethodName [currentThreadName] providedMessage
*
* @param frame The {@link StackTraceElement} to pull the method name from
* @param msg the message to prefix the method name to
* @return The final message string
*/
private static String createMessage(final StackTraceElement frame, final String msg) {
return String.format(Locale.US,
"[%s] %s : %s",
Thread.currentThread().getName(),
frame.getMethodName(),
msg);
}
/**
* Gets the {@link StackTraceElement} for the method that invoked logging
*
* @return {@link StackTraceElement} for the caller into {@link FyzLog}
*/
private static StackTraceElement getCallingStackTraceElement() {
boolean hitLogger = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
final boolean isLogger = ste.getClassName().startsWith(FyzLog.class.getName());
hitLogger = hitLogger || isLogger;
if (hitLogger && !isLogger) {
return ste;
}
}
return new StackTraceElement(FyzLog.class.getName(),
"getCallingStackTraceElement",
null,
-1);
}
}
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.println(LogLevel.ASSERT, logLevel, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.SystemOut.println(Log.ASSERT, logLevel, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
/* package */ class Logger {
/* package */ static final Logger SystemOut = new Logger();
private Logger(){}
...
}
/*
* Copyright (c) 2016 Fyzxs
*
* Licensed under The MIT License (MIT)
*/
package com.quantityandconversion.log;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* !!! DO NOT USE THIS TEST CLASS AS AN EXAMPLE !!!
* <p>
* Predominate issues with this test class
* <p>
* + Prefixed with 'The'
* A proper name for this class would be "FyzLogTests".
* Doing so triggers a condition internal, which shouldn't be re-written to bypass unit tests.
* <p>
* + Using System.out
* Don't do this.
*/
@SuppressWarnings("ConstantConditions")
public class TheFyzLogTests {
@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setup() {
FyzLog.logLevel = LogLevel.VERBOSE;
FyzLog.doPrint = false;
systemOutRule.clearLog();
}
private String configureSystemTest(final LogLevel currentLogLevel, final LogLevel attemptingLogLevel) {
systemOutRule.clearLog();
FyzLog.doPrint = true;
final String logLevel = attemptingLogLevel.tag() + "@" + currentLogLevel.tag() + "/ ";
final String tag = "FYZ:TheFyzLogTests ";
final String thread = "[main] ";
return logLevel + tag + thread;
}
private String message() {
return "the message";
}
private String messageFormat() {
return "%s %d %b %s";
}
private Object[] messageArgs() {
return new Object[]{"it", 2357, true, "is"};
}
@Test
public void massiveTestOfAllPrintMessageCombinations() {
//Tracks how many tests we performed
int ctr = 0;
final LogLevel[] logLevels = new LogLevel[]{LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.ASSERT};
for (final LogLevel currentLogLevel : logLevels) {
//Set the Log Level
FyzLog.logLevel = currentLogLevel;
//Looping over each logLevel
for (final LogLevel attemptingLogLevel : logLevels) {
//Arrange
final String prefix = configureSystemTest(currentLogLevel, attemptingLogLevel);
final String msg = message();
final String method = Thread.currentThread().getStackTrace()[1].getMethodName() + " : ";
//Prep the expected value
final String expected = prefix + method + msg;
//Act
if (attemptingLogLevel == LogLevel.VERBOSE) FyzLog.v(msg);
else if (attemptingLogLevel == LogLevel.DEBUG) FyzLog.d(msg);
else if (attemptingLogLevel == LogLevel.INFO) FyzLog.i(msg);
else if (attemptingLogLevel == LogLevel.WARN) FyzLog.w(msg);
else if (attemptingLogLevel == LogLevel.ERROR) FyzLog.e(msg);
else if (attemptingLogLevel == LogLevel.ASSERT) FyzLog.wtf(msg);
//Assert
final String target = systemOutRule.getLog();
assertEquals("Failed at [currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "]", expected + "\n", target);
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * 6);
}
@Test
public void printMessageThrowsNullPointerExceptionGivenNull() {
//Tracks how many tests we performed
int ctr = 0;
//We're printing
FyzLog.doPrint = true;
//Looping over each logLevel
for (final LogLevel logLevel : new LogLevel[]{LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.ASSERT}) {
try {
if (logLevel == LogLevel.VERBOSE) FyzLog.v(null);
else if (logLevel == LogLevel.DEBUG) FyzLog.d(null);
else if (logLevel == LogLevel.INFO) FyzLog.i(null);
else if (logLevel == LogLevel.WARN) FyzLog.w(null);
else if (logLevel == LogLevel.ERROR) FyzLog.e(null);
else if (logLevel == LogLevel.ASSERT) FyzLog.wtf(null);
//We're expecting exceptions, make sure we don't get here
fail("Should Have Thrown");
} catch (final IllegalArgumentException e) {
//Make sure it's the exception we wanted
assertThat(e.getMessage()).isEqualTo("FyzLog message can not be null");
}
//inc the test performed count
ctr++;
}
//Verify we performed the expected tests
assertThat(ctr).isEqualTo(6);
}
@Test
public void massiveTestOfAllPrintMessageFormatCombinations() {
//Tracks how many tests we performed
int ctr = 0;
//Looping over the available Log calls
final LogLevel[] logLevels = new LogLevel[]{LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.ASSERT};
for (final LogLevel currentLogLevel : logLevels) {
//Set the Log Level
FyzLog.logLevel = currentLogLevel;
//Looping over each logLevel
for (final LogLevel attemptingLogLevel : logLevels) {
//Arrange
final String prefix = configureSystemTest(currentLogLevel, attemptingLogLevel);
final String msg = messageFormat();
final String method = Thread.currentThread().getStackTrace()[1].getMethodName() + " : ";
//Prep the expected value
final String expected = prefix + method + msg;
//Act
if (attemptingLogLevel == LogLevel.VERBOSE)
FyzLog.v(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.DEBUG)
FyzLog.d(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.INFO)
FyzLog.i(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.WARN)
FyzLog.w(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ERROR)
FyzLog.e(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ASSERT)
FyzLog.wtf(messageFormat(), messageArgs());
//Assert
final String target = systemOutRule.getLog();
assertEquals("Failed at [currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "]",
target,
String.format(expected, messageArgs()) + "\n");
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * 6);
}
/**
* Can't (easily) assert that it formats, so this just tests logging or not functionality of the
* log level setting.
*/
@Test
public void massiveAndroidLogging() {
//Tracks how many tests we performed
int ctr = 0;
final LogLevel[] logLevels = new LogLevel[]{LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.ASSERT};
//Looping over the available Log calls
for (final LogLevel currentLogLevel : logLevels) {
//Set the Log Level
FyzLog.logLevel = currentLogLevel;
//Looping over each logLevel
for (final LogLevel attemptingLogLevel : logLevels) {
//Determine if logging will be attempted, which will result in an exception
final boolean shouldTryToLog = attemptingLogLevel.logAt(currentLogLevel);
//Show what's about to be performed. If this breaks, it's HUGELY useful.
System.out.println("[currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "][shouldTryToLog=" + shouldTryToLog + "]");
//INVOKE
try {
if (attemptingLogLevel == LogLevel.VERBOSE)
FyzLog.v(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.DEBUG)
FyzLog.d(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.INFO)
FyzLog.i(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.WARN)
FyzLog.w(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ERROR)
FyzLog.e(messageFormat(), messageArgs());
else if (attemptingLogLevel == LogLevel.ASSERT)
FyzLog.wtf(messageFormat(), messageArgs());
//If we got here, we did not expect an exception, prove it
assertFalse("[currentLogLevel=" + currentLogLevel.tag() + "][attemptingLogLevel=" + attemptingLogLevel.tag() + "]", shouldTryToLog);
} catch (final RuntimeException e) {
//If we got here, we expected to, prove it
assertThat(shouldTryToLog).isTrue();
//Since this is kinda a catch all exception, make sure it's what we wanted
assertThat(e.getMessage()).isEqualTo("Method " + attemptingLogLevel.tag().toLowerCase() + " in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.");
}
//A test ran, inc
ctr++;
}
}
//Confirm we ran the expected number of tests
assertThat(ctr).isEqualTo(6 * 6);
}
@Test
public void androidLogDoesNothingGivenNull() {
FyzLog.doPrint = false;
systemOutRule.clearLog();
FyzLog.logLevel = LogLevel.VERBOSE;
//All calls should bail w/o doing anything
FyzLog.v(null);
FyzLog.d(null);
FyzLog.i(null);
FyzLog.w(null);
FyzLog.e(null);
FyzLog.wtf(null);
//Also should not have logged via printing
assertThat(systemOutRule.getLog()).isNullOrEmpty();
assertTrue("It should have gotten here", true);
}
}
/* package */ class Logger { ... }
/* package */ static void println(final int level, final int logLevel, final String msgFormat, final Object... args){
...
}
public static void wtf(@NonNull final String msgFormat, final Object... args) {
if (doPrint) {
Logger.println(Log.ASSERT, logLevel, msgFormat, args);
} else {
log(Log.ASSERT, msgFormat, args);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment