diff --git a/README.md b/README.md
index a6e3299..8bc9a89 100644
--- a/README.md
+++ b/README.md
@@ -144,15 +144,27 @@ setLogger()
### Using the `@TraceLogLevel` annotation
Trace events in a `TraceEventListener` can be annotated with the `@TraceLogLevel` annotation.
-This annotation allows you to specify a log level, using parameter `logLevel` that will
+
+#### `@TraceLoglevel(logLevel=...)`
+
+`@TraceLoglevel` allows you to specify a log level, using parameter `logLevel` that will
be used to output the trace event to a standard logger using a specific log level,
such as `INFO`, `DEBUG`, `ERROR`, etc.
-It also offers the option to specify logging a full stack trace of a logged exception, using
+#### `@TraceLoglevel(logStackTrace=true|false)`
+
+`@TraceLoglevel` also offers the option to specify logging a full stack trace of a logged exception, using
the optional parameter `logStackTrace`. This applies to the last argument of an event (if it
is a `Throwable` object). By default, the stack trace is included.
-Finally, it offers the option, using `includeOwnerClass` to add the class owning the event
+#### `@TraceLoglevel(logCaller=true|false)`
+
+`@TraceLoglevel` also offers the option to specify logging the filename and line number of
+the caller of an event. By default, the caller is not provided.
+
+#### `@TraceLoglevel(logOwnerClass=true|false)`
+
+`@TraceLoglevel` offers the option, using `logOwnerClass` to add the class owning the event
interface to the log message, or omit that. By default, the owner class is not included.
Example:
@@ -419,6 +431,10 @@ Contributors: Timon Kanters, Jeroen Erik Jensen
## Release notes
+### 1.0.9
+
+* Added `logCaller` to `@TraceLoglevel` add the caller filename and line number to the logger.
+
### 1.0.8
* Added `logStackTrace` to annotation `@TraceLogLevel`.
diff --git a/pom.xml b/pom.xml
index 50f2712..4be1603 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
com.tomtom.kotlin
kotlin-tools
- 1.0.8
+ 1.0.9
pom
Kotlin Tools
diff --git a/traceevents/pom.xml b/traceevents/pom.xml
index 4817f06..1490bb0 100644
--- a/traceevents/pom.xml
+++ b/traceevents/pom.xml
@@ -24,7 +24,7 @@
com.tomtom.kotlin
kotlin-tools
- 1.0.8
+ 1.0.9
traceevents
diff --git a/traceevents/src/main/java/com/tomtom/kotlin/traceevents/TraceLogLevel.kt b/traceevents/src/main/java/com/tomtom/kotlin/traceevents/TraceLogLevel.kt
index 4117fd1..3a72a14 100644
--- a/traceevents/src/main/java/com/tomtom/kotlin/traceevents/TraceLogLevel.kt
+++ b/traceevents/src/main/java/com/tomtom/kotlin/traceevents/TraceLogLevel.kt
@@ -26,11 +26,20 @@ import com.tomtom.kotlin.traceevents.TraceLog.LogLevel
* @param logStackTrace Specifies whether a stack trace should be included if the last parameter
* of the event is derived from [Throwable]. If false, only the exception message is shown.
* Default value is true.
+ *
+ * @param logCaller Specifies whether the filename and line number of the caller
+ * should be logged or not.
+ * Default is false.
+ *
+ * @param logOwnerClass Specifies whether owner class of the event definition should be logged
+ * or not.
+ * Default is false.
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class TraceLogLevel(
val logLevel: LogLevel,
val logStackTrace: Boolean = true,
- val includeOwnerClass: Boolean = false
+ val logCaller: Boolean = false,
+ val logOwnerClass: Boolean = false
)
diff --git a/traceevents/src/main/java/com/tomtom/kotlin/traceevents/Tracer.kt b/traceevents/src/main/java/com/tomtom/kotlin/traceevents/Tracer.kt
index 743436a..fde38eb 100644
--- a/traceevents/src/main/java/com/tomtom/kotlin/traceevents/Tracer.kt
+++ b/traceevents/src/main/java/com/tomtom/kotlin/traceevents/Tracer.kt
@@ -32,6 +32,7 @@ import java.time.format.DateTimeFormatter
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
+
/**
* GENERAL DOCUMENTATION ON TRACE EVENTS
* -------------------------------------
@@ -217,8 +218,10 @@ class Tracer private constructor(
method.getDeclaredAnnotation(TraceLogLevel::class.java)?.logLevel ?: LogLevel.DEBUG
val logStackTraceAnnotation =
method.getDeclaredAnnotation(TraceLogLevel::class.java)?.logStackTrace ?: true
+ val logCaller =
+ method.getDeclaredAnnotation(TraceLogLevel::class.java)?.logCaller ?: false
val includeOwnerClassAnnotation =
- method.getDeclaredAnnotation(TraceLogLevel::class.java)?.includeOwnerClass ?: false
+ method.getDeclaredAnnotation(TraceLogLevel::class.java)?.logOwnerClass ?: false
/**
* Skip event when the method is a standard (possibly auto-generated) class method.
@@ -260,6 +263,7 @@ class Tracer private constructor(
"event=${createLogMessage(
event,
includeTime = false,
+ logCaller = logCaller,
logStackTrace = logStackTraceAnnotation,
includeOwnerClass = includeOwnerClassAnnotation
)}"
@@ -301,6 +305,7 @@ class Tracer private constructor(
"Event lost, event=${createLogMessage(
event,
includeTime = true,
+ logCaller = true,
logStackTrace = true,
includeOwnerClass = true
)}"
@@ -351,6 +356,7 @@ class Tracer private constructor(
companion object {
val TAG = Tracer::class.simpleName!!
+ const val STACK_TRACE_DEPTH = 5L
/**
* Names of simple, predefined log functions (from standard loggers).
@@ -616,6 +622,7 @@ class Tracer private constructor(
internal fun createLogMessage(
traceEvent: TraceEvent,
includeTime: Boolean,
+ logCaller: Boolean,
logStackTrace: Boolean,
includeOwnerClass: Boolean
): String {
@@ -628,8 +635,11 @@ class Tracer private constructor(
convertToStringUsingRegistry(it)
}})"
)
+ if (logCaller) {
+ sb.append(", caller=${getCaller()}")
+ }
if (includeOwnerClass) {
- sb.append(", from ${traceEvent.ownerClass}")
+ sb.append(", ownerClass=${traceEvent.ownerClass}")
}
if (logStackTrace && !traceEvent.args.isEmpty()) {
(traceEvent.args.last() as? Throwable)?.let {
@@ -654,6 +664,47 @@ class Tracer private constructor(
e.message
}
+ private fun getCaller(): String {
+
+ /**
+ * For Java 9, we suggest the following optimization:
+ *
+ * Use the [StackWalker] instead of [Thread.getStackTrace], because it's much, much
+ * faster to not get all frames.
+ *
+ * Get the stack up to a maximum of [STACK_TRACE_DEPTH] levels deep. Our caller must be
+ * in those first calls. If it's not, the unit tests fails and informs the developer
+ * the reconsider the value of [STACK_TRACE_DEPTH].
+ *
+ *
+ * val stack = StackWalker.getInstance().walk {
+ * frames -> frames.limit(STACK_TRACE_DEPTH).collect(Collectors.toList())
+ *
+ *
+ * For now, use the slower Java 8 version:
+ */
+ val stack = Throwable().stackTrace
+
+ // Find our own 'invoke' function call on the stack that called this function.
+ var i = 0
+ while (i < stack.size &&
+ stack[i].className != "com.tomtom.kotlin.traceevents.Tracer" &&
+ stack[i].methodName != "invoke"
+ ) i++
+
+ // The function call 2 levels deeper is the actual caller function.
+ return if (i < stack.size - 2) {
+
+ // Skip the com.sun.proxy function call and get the info from the next item.
+ val item = stack[i + 2]
+ "${item.fileName}:${item.methodName}(${item.lineNumber})"
+ } else {
+
+ // This shouldn't happen, but we certainly shouldn't throw here.
+ "(error: can't find `invoke` function on stack)"
+ }
+ }
+
init {
// Always switch on synchronous logging by default.
diff --git a/traceevents/src/test/java/com/tomtom/kotlin/traceevents/LogTest.kt b/traceevents/src/test/java/com/tomtom/kotlin/traceevents/LogTest.kt
index bca8d44..f18535d 100644
--- a/traceevents/src/test/java/com/tomtom/kotlin/traceevents/LogTest.kt
+++ b/traceevents/src/test/java/com/tomtom/kotlin/traceevents/LogTest.kt
@@ -16,7 +16,9 @@ package com.tomtom.kotlin.traceevents
import com.tomtom.kotlin.traceevents.TraceLog.LogLevel
import com.tomtom.kotlin.traceevents.TraceLog.Logger
+import org.junit.Before
import org.junit.Test
+import kotlin.system.measureTimeMillis
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -51,13 +53,21 @@ class LogTest {
@TraceLogLevel(LogLevel.ERROR)
fun allArgs(aInt: Int?, aString: String?, aArray: Array?, aList: List?)
- @TraceLogLevel(LogLevel.DEBUG, logStackTrace = true, includeOwnerClass = true)
+ @TraceLogLevel(LogLevel.DEBUG, logCaller = true)
+ fun withCaller(message: String)
+
+ @TraceLogLevel(LogLevel.DEBUG, logStackTrace = true, logOwnerClass = true)
fun withStackTrace(e: Throwable?)
- @TraceLogLevel(LogLevel.DEBUG, logStackTrace = false, includeOwnerClass = false)
+ @TraceLogLevel(LogLevel.DEBUG, logStackTrace = false, logOwnerClass = false)
fun withoutStackTrace(e: Throwable?)
}
+ @Before
+ fun setUp() {
+ TraceLog.setLogger()
+ }
+
@Test
fun `specific consumer`() {
val logger = LoggerCalled()
@@ -214,7 +224,7 @@ class LogTest {
sut.withStackTrace(null)
}
val expected =
- "$TIME: [DEBUG] LogTest: event=withStackTrace(null), from com.tomtom.kotlin.traceevents.LogTest\n"
+ "$TIME: [DEBUG] LogTest: event=withStackTrace(null), ownerClass=com.tomtom.kotlin.traceevents.LogTest\n"
assertEquals(expected, actual)
}
@@ -226,7 +236,7 @@ class LogTest {
// Cut off just enough to NOT include line numbers of source code as they may change.
val prefix =
- "$TIME: [DEBUG] LogTest: event=withStackTrace(java.lang.IllegalStateException: error1), from com.tomtom.kotlin.traceevents.LogTest\n" +
+ "$TIME: [DEBUG] LogTest: event=withStackTrace(java.lang.IllegalStateException: error1), ownerClass=com.tomtom.kotlin.traceevents.LogTest\n" +
"java.lang.IllegalStateException: error1\n" +
"\tat com.tomtom.kotlin.traceevents.LogTest\$log event with exception to stdout with stacktrace\$actual\$1.invoke(LogTest.kt:"
val suffix =
@@ -239,6 +249,20 @@ class LogTest {
assertTrue(replaceNumber(actual, NUMBER).endsWith(suffix))
}
+ @Test
+ fun `log event with caller`() {
+ val actual = captureStdoutReplaceTime(TIME) {
+ sut.withCaller("test")
+ }
+
+ println(actual)
+ // Cut off just enough to NOT include line numbers of source code as they may change.
+ val expected =
+ "$TIME: [DEBUG] LogTest: event=withCaller(test), caller=LogTest.kt:invoke($NUMBER)\n"
+ assertEquals(expected, replaceNumber(actual, NUMBER),
+ "Perhaps you should increase STACK_TRACE_DEPTH?")
+ }
+
companion object {
const val TIME = "[TIME]"
const val NUMBER = "[NUMBER]"