-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PrintStreamLogTarget prints location information
- Loading branch information
Showing
8 changed files
with
256 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 0 additions & 11 deletions
11
src/main/java/com/obsidiandynamics/zerolog/util/CallingClass.java
This file was deleted.
Oops, something went wrong.
108 changes: 108 additions & 0 deletions
108
src/main/java/com/obsidiandynamics/zerolog/util/Stacks.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package com.obsidiandynamics.zerolog.util; | ||
|
||
/** | ||
* Utilities for working with the call stack. | ||
*/ | ||
public final class Stacks extends SecurityManager { | ||
private static final Stacks instance = new Stacks(); | ||
|
||
private Stacks() {} | ||
|
||
/** | ||
* Obtains the class at the given stack depth. | ||
* | ||
* @param depth The stack depth. | ||
* @return The {@link Class} at the given depth. | ||
*/ | ||
public static Class<?> classForDepth(int depth) { | ||
return instance.getClassContext()[depth]; | ||
} | ||
|
||
/** | ||
* Obtains a {@link StackTraceElement} at the location immediately preceding the | ||
* entrypoint. | ||
* | ||
* @param entrypoint The entrypoint. | ||
* @return The preceding {@link StackTraceElement}, or {@code null} if no matching | ||
* location could be obtained. | ||
*/ | ||
public static StackTraceElement locate(String entrypoint) { | ||
final StackTraceElement[] elements = Thread.currentThread().getStackTrace(); | ||
final int index = indexOf(entrypoint, elements); | ||
return index != -1 && index < elements.length - 1 ? elements[index + 1] : null; | ||
} | ||
|
||
private static int indexOf(String entrypoint, StackTraceElement[] elements) { | ||
for (int i = 1; i < elements.length; i++) { | ||
final StackTraceElement element = elements[i]; | ||
if (element.getClassName().equals(entrypoint)) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
/** | ||
* Formats the location in the form <simple class name>.<method name>:<line number>.<p> | ||
* | ||
* If a {@code null} element is provided, the resulting location will be returned | ||
* as the string {@code "?.?:?"}. | ||
* | ||
* @param element The stack trace element; may be {@code null}. | ||
* @return The formatted location. | ||
*/ | ||
public static String formatLocation(StackTraceElement element) { | ||
if (element != null) { | ||
final String simpleName = getSimpleClassName(element.getClassName()); | ||
return simpleName + "." + element.getMethodName() + ":" + element.getLineNumber(); | ||
} else { | ||
return "?.?:?"; | ||
} | ||
} | ||
|
||
/** | ||
* Returns the simple name of the underlying class as given in the source code or | ||
* an empty string if the underlying class is anonymous. This implementation is | ||
* significantly faster than {@link Class#getSimpleName()} as it doesn't require a | ||
* class lookup.<p> | ||
* | ||
* This implementation is a close approximation {@link Class#getSimpleName()}; the | ||
* differences are:<br> | ||
* 1. This implementation ignores array component types.<br> | ||
* 2. Extraneous $ characters are mistaken for an inner/local/anonymous class demarcator.<br> | ||
* 3. This implementation is more lenient, allowing certain invalid characters.<br> | ||
* | ||
* @param className The class name. | ||
* @return The simple name of the given class. | ||
*/ | ||
public static String getSimpleClassName(String className) { | ||
final int lastDollar = className.lastIndexOf('$'); | ||
if (lastDollar != -1) { | ||
// inner, local or anonymous class | ||
final int length = className.length(); | ||
int index = lastDollar + 1; | ||
|
||
// skip over any leading digits that follow '$' in anonymous classes | ||
while (index < length && isAsciiDigit(className.charAt(index))) { | ||
index++; | ||
} | ||
return className.substring(index); | ||
} else { | ||
// top-level class | ||
final int lastDot = className.lastIndexOf('.'); | ||
return lastDot != -1 ? className.substring(lastDot + 1) : className; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the given character is an ASCII digit. Differs from {@link Character#isDigit()} | ||
* in that the latter considers the Unicode character set and answers {@code true} to some | ||
* non-ASCII digits. | ||
* | ||
* @param ch The character to test. | ||
* @return True if the character is an ASCII digit. | ||
*/ | ||
private static boolean isAsciiDigit(char ch) { | ||
return '0' <= ch && ch <= '9'; | ||
} | ||
} |
39 changes: 0 additions & 39 deletions
39
src/test/java/com/obsidiandynamics/zerolog/util/CallingClassTest.java
This file was deleted.
Oops, something went wrong.
134 changes: 134 additions & 0 deletions
134
src/test/java/com/obsidiandynamics/zerolog/util/StacksTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package com.obsidiandynamics.zerolog.util; | ||
|
||
import static org.junit.Assert.*; | ||
|
||
import java.util.concurrent.atomic.*; | ||
|
||
import org.junit.*; | ||
|
||
import com.obsidiandynamics.assertion.*; | ||
|
||
public final class StacksTest { | ||
private static final class Inner { | ||
static final Class<?> staticField = Stacks.classForDepth(1); | ||
} | ||
|
||
@Test | ||
public void testConformance() { | ||
Assertions.assertUtilityClassWellDefined(Stacks.class); | ||
} | ||
|
||
@Test | ||
public void testCallerDepth1() { | ||
final Class<?> cls = Stacks.classForDepth(1); | ||
assertEquals(StacksTest.class, cls); | ||
} | ||
|
||
private static class Nested { | ||
static Class<?> get() { | ||
return Stacks.classForDepth(2); | ||
} | ||
} | ||
|
||
@Test | ||
public void testCallerDepth2() { | ||
final Class<?> cls = Nested.get(); | ||
assertEquals(StacksTest.class, cls); | ||
} | ||
|
||
@Test | ||
public void testStaticField() { | ||
assertEquals(Inner.class, Inner.staticField); | ||
} | ||
|
||
@Test | ||
public void testLocationFound() { | ||
final StackTraceElement element = Stacks.locate(Stacks.class.getName()); | ||
assertNotNull(element); | ||
assertEquals(StacksTest.class.getName(), element.getClassName()); | ||
assertEquals("testLocationFound", element.getMethodName()); | ||
} | ||
|
||
@Test | ||
public void testLocationNotFound() { | ||
assertNull(Stacks.locate("foo")); | ||
} | ||
|
||
@Test | ||
public void testLocationNotFoundTooShallow() throws Throwable { | ||
final AtomicReference<Throwable> error = new AtomicReference<>(); | ||
final Thread thread = new Thread(() -> { | ||
try { | ||
assertNull(Stacks.locate(Thread.class.getName())); | ||
} catch (Throwable e) { | ||
error.set(e); | ||
} | ||
}); | ||
thread.start(); | ||
thread.join(); | ||
if (error.get() != null) { | ||
throw error.get(); | ||
} | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameTopLevel() { | ||
final Class<?> cls = StacksTest.class; | ||
assertEquals(cls.getSimpleName(), Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameTopLevelInvalidChars() { | ||
final String className = "package.$+Class"; | ||
// the getSimpleClassName() implementation is a little more lenient than Class.getSimpleName() | ||
assertEquals("+Class", Stacks.getSimpleClassName(className)); | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameInner() { | ||
final Class<?> cls = StacksTest.Inner.class; | ||
assertEquals(cls.getSimpleName(), Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
private static class In0 { | ||
static class In1 {} | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameInner2() { | ||
final Class<?> cls = StacksTest.In0.In1.class; | ||
assertEquals(cls.getSimpleName(), Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameAnonymous() { | ||
final Class<?> cls = new Object() {}.getClass(); | ||
assertEquals(cls.getSimpleName(), Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameLocal() { | ||
class $Local {} | ||
final Class<?> cls = $Local.class; | ||
// this varies from Class.getSimpleName() | ||
assertEquals("Local", Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
@Test | ||
public void testGetSimpleClassNameDefaultPackage() { | ||
final Class<?> cls = int.class; | ||
assertEquals(cls.getSimpleName(), Stacks.getSimpleClassName(cls.getName())); | ||
} | ||
|
||
@Test | ||
public void testFormatLocationNonNull() { | ||
final StackTraceElement element = | ||
new StackTraceElement("pkg.Foo$Bar", "doSomething", null, 42); | ||
assertEquals("Bar.doSomething:42", Stacks.formatLocation(element)); | ||
} | ||
|
||
@Test | ||
public void testFormatLocationNull() { | ||
assertEquals("?.?:?", Stacks.formatLocation(null)); | ||
} | ||
} |