From ff61ba53ae40585dc44458bd407b7a6f931bc46d Mon Sep 17 00:00:00 2001 From: Moses Nakamura Date: Fri, 3 Jan 2014 22:53:10 +0000 Subject: [PATCH] [split] util-logging: Gets correct method and class name in c.t.u.LogRecord RB_ID=248087 --- util-logging/pom.xml | 6 ++ .../com/twitter/logging/LazyLogRecord.scala | 7 ++- .../scala/com/twitter/logging/LogRecord.scala | 62 +++++++++++++++++-- util-logging/src/test/scala/BUILD | 1 + .../com/twitter/logging/LogRecordTest.scala | 46 ++++++++++++++ 5 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 util-logging/src/test/scala/com/twitter/logging/LogRecordTest.scala diff --git a/util-logging/pom.xml b/util-logging/pom.xml index 8e46de374e..def5c0eb43 100644 --- a/util-logging/pom.xml +++ b/util-logging/pom.xml @@ -22,6 +22,12 @@ 1.6.9 provided + + org.scalatest + scalatest_2.9.2 + 1.9.1 + test + com.twitter diff --git a/util-logging/src/main/scala/com/twitter/logging/LazyLogRecord.scala b/util-logging/src/main/scala/com/twitter/logging/LazyLogRecord.scala index c4a9756c68..43ef54efdd 100644 --- a/util-logging/src/main/scala/com/twitter/logging/LazyLogRecord.scala +++ b/util-logging/src/main/scala/com/twitter/logging/LazyLogRecord.scala @@ -18,7 +18,10 @@ package com.twitter.logging import java.util.{logging => javalog} -class LazyLogRecord(level: javalog.Level, messageGenerator: => AnyRef) extends LogRecord(level, "") { +class LazyLogRecord( + level: javalog.Level, + messageGenerator: => AnyRef +) extends LogRecord(level, "") { // for each logged line, generate this string only once, regardless of how many handlers there are: override lazy val getMessage = messageGenerator.toString } @@ -30,4 +33,4 @@ class LazyLogRecordUnformatted(level: javalog.Level, message: String, items: Any extends LazyLogRecord(level, { message.format(items: _*) }) { require(items.size > 0) val preformatted = message -} \ No newline at end of file +} diff --git a/util-logging/src/main/scala/com/twitter/logging/LogRecord.scala b/util-logging/src/main/scala/com/twitter/logging/LogRecord.scala index ee0ae28594..8e2889eb82 100644 --- a/util-logging/src/main/scala/com/twitter/logging/LogRecord.scala +++ b/util-logging/src/main/scala/com/twitter/logging/LogRecord.scala @@ -19,10 +19,62 @@ package com.twitter.logging import java.util.{logging => javalog} /** - * Wrapper around {@link java.util.logging.LogRecord}. + * Wrapper around {@link java.util.logging.LogRecord}. Should only be accessed from a single thread. * - * The only difference is around log time where messages by Java are formatted using - * {@link java.text.MessageFormat}, whereas this class formats using a regular - * {@link java.text.StringFormat}. + * Messages are formatted by Java's LogRecord using {@link java.text.MessageFormat} whereas + * this class uses a regular {@link java.text.StringFormat} + * + * This class takes {@link com.twitter.logging.Logger} into account when inferring the + * `sourceMethod` and `sourceClass` names. */ -class LogRecord(level: javalog.Level, msg: String) extends javalog.LogRecord(level, msg) +class LogRecord(level: javalog.Level, msg: String) extends javalog.LogRecord(level, msg) { + private[this] var inferred = false + private[this] var sourceClassName: String = null + private[this] var sourceMethodName: String = null + + // May be incorrect if called lazily + override def getSourceClassName(): String = { + if (!inferred) + infer() + sourceClassName + } + + // May be incorrect if called lazily + override def getSourceMethodName(): String = { + if (!inferred) + infer() + sourceMethodName + } + + override def setSourceClassName(name: String) { + inferred = true + sourceClassName = name + } + + override def setSourceMethodName(name: String) { + inferred = true + sourceMethodName = name + } + + private[this] def infer() { + // TODO: there is a small optimization we can do in jdk7 with new JavaLangAccess + val stack = Thread.currentThread.getStackTrace() + + def notTwitterString(elt: StackTraceElement): Boolean = + elt.getClassName != LogRecord.twitterString + + // Find the first non-Logger StackTraceElement after the first occurrence of Logger. + val elt = stack dropWhile notTwitterString find notTwitterString + + val (cName, mName) = elt match { + case Some(element) => (element.getClassName, element.getMethodName) + case None => (super.getSourceClassName, super.getSourceMethodName) + } + setSourceMethodName(mName) + setSourceClassName(cName) + } +} + +object LogRecord { + private[logging] val twitterString = "com.twitter.logging.Logger" +} diff --git a/util-logging/src/test/scala/BUILD b/util-logging/src/test/scala/BUILD index d31f82b596..d8e280b190 100644 --- a/util-logging/src/test/scala/BUILD +++ b/util-logging/src/test/scala/BUILD @@ -3,6 +3,7 @@ junit_tests(name='scala', pants('3rdparty:junit'), pants('3rdparty:mockito-all'), pants('3rdparty:specs'), + pants('3rdparty:scalatest'), pants('util/util-core/src/main/scala'), pants('util/util-logging/src/main/scala'), ], diff --git a/util-logging/src/test/scala/com/twitter/logging/LogRecordTest.scala b/util-logging/src/test/scala/com/twitter/logging/LogRecordTest.scala new file mode 100644 index 0000000000..03f46d4cdb --- /dev/null +++ b/util-logging/src/test/scala/com/twitter/logging/LogRecordTest.scala @@ -0,0 +1,46 @@ +package com.twitter.logging + +import java.util.logging.{Level => JLevel, LogRecord => JRecord} +import org.junit.runner.RunWith +import org.scalatest.FunSuite +import org.scalatest.junit.JUnitRunner + +@RunWith(classOf[JUnitRunner]) +class LogRecordTest extends FunSuite { + test("LogRecord should getMethod properly") { + Logger.withLoggers(Nil) { + new LogRecordTestHelper({ r: JRecord => r.getSourceMethodName() }) { + def makingLogRecord() { + logger.log(Level.INFO, "OK") + assert(handler.get === "makingLogRecord") + } + makingLogRecord() + } + } + } + + test("LogRecord should getClass properly") { + Logger.withLoggers(Nil) { + new Foo { + assert(handler.get === "com.twitter.logging.Foo") + } + } + } +} + +abstract class LogRecordTestHelper(formats: JRecord => String) { + val formatter = new Formatter { + override def format(r: JRecord): String = formats(r) + } + val handler = new StringHandler(formatter) + val logger = Logger.get("") + logger.addHandler(handler) +} + +class Foo extends LogRecordTestHelper({ r: JRecord => r.getSourceClassName() }) { + def makingLogRecord() { + logger.log(Level.INFO, "OK") + } + + makingLogRecord() +}