This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch 1.7.x
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/1.7.x by this push:
new f0bdd61795 [1.7.x] fix: detect logger class with StackWalker on JDK 9+
(#3614)
f0bdd61795 is described below
commit f0bdd617951c78735951b95f77424e997465e4e7
Author: PJ Fanning <[email protected]>
AuthorDate: Sun Oct 11 06:17:11 2026 +0100
[1.7.x] fix: detect logger class with StackWalker on JDK 9+ (#3614)
* fix: detect logger class with StackWalker on JDK 9+ in 1.7.x
Motivation:
On JDK 25 with Scala 3, ctx.log picks the enclosing outer object as the
logger class instead of the lambda owner. SecurityManager.getClassContext
does not report the hidden lambda frames there, so the Scala 3 lambda
owner detection in LoggerClass never matches. The JDK 25 / Scala 3.3.x
job of the 1.7 nightly fails in EventSourcedBehaviorLoggingSpec.
Modification:
Read the class stack with java.lang.StackWalker (RETAIN_CLASS_REFERENCE,
SHOW_HIDDEN_FRAMES) when available, through reflection since 1.7.x is
compiled for Java 8. Fall back to the SecurityManager approach on Java 8.
Result:
Logger class detection works on JDK 25 with Scala 3, matching main which
already uses StackWalker.
Tests:
- JDK 25 + Scala 3.3.7: EventSourcedBehaviorLogging* specs fail before (5
failures), pass after; ActorLoggingSpec passes
- JDK 8 + Scala 2.13: EventSourcedBehaviorLogging* specs and
ActorLoggingSpec pass
- actor-typed/mimaReportBinaryIssues: no issues
References:
None - fixes the JDK 25 / Scala 3.3.x failure in the Nightly Builds (1.7)
workflow
* fix: avoid Scala 2.12 missing interpolator lint in LoggerClass
* chore: use OptionVal instead of Option in LoggerClass
---
.../pekko/actor/typed/internal/LoggerClass.scala | 38 ++++++++++++++++++++--
1 file changed, 35 insertions(+), 3 deletions(-)
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
index b73d46b43f..4e5c8849bc 100644
---
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
+++
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/LoggerClass.scala
@@ -25,18 +25,50 @@ import pekko.util.OptionVal
@InternalApi
private[pekko] object LoggerClass {
- // just to get access to the class context
+ // just to get access to the class context, only used on Java 8 where
StackWalker is not available
private final class TrickySecurityManager extends SecurityManager {
def getClassStack: Array[Class[_]] = getClassContext
}
+ // java.lang.StackWalker (Java 9+) is accessed through reflection since this
module is compiled for Java 8.
+ // SHOW_HIDDEN_FRAMES is needed to include the lambda classes, which
SecurityManager.getClassContext
+ // does not report on JDK 24+ (required by the lambda owner detection below
for Scala 3)
+ private val stackWalkerClassStack: OptionVal[() => Array[Class[_]]] =
+ try {
+ val walkerClass = Class.forName("java.lang.StackWalker")
+ // nested class names are built with '$' + name to avoid Scala 2.12's
missing interpolator lint
+ val optionClass = Class.forName(walkerClass.getName + '$' + "Option")
+ val getDeclaringClass = Class.forName(walkerClass.getName + '$' +
"StackFrame").getMethod("getDeclaringClass")
+ val options = new java.util.HashSet[AnyRef]()
+ options.add(optionClass.getField("RETAIN_CLASS_REFERENCE").get(null))
+ options.add(optionClass.getField("SHOW_HIDDEN_FRAMES").get(null))
+ val walker = walkerClass.getMethod("getInstance",
classOf[java.util.Set[_]]).invoke(null, options)
+ val walk = walkerClass.getMethod("walk",
classOf[java.util.function.Function[_, _]])
+ val toClasses = new
java.util.function.Function[java.util.stream.Stream[AnyRef], Array[Class[_]]] {
+ override def apply(frames: java.util.stream.Stream[AnyRef]):
Array[Class[_]] =
+ frames.toArray.map(frame =>
getDeclaringClass.invoke(frame).asInstanceOf[Class[_]])
+ }
+ OptionVal.Some(() => walk.invoke(walker,
toClasses).asInstanceOf[Array[Class[_]]])
+ } catch {
+ case NonFatal(_) => OptionVal.None
+ }
+
+ private def getClassStack: Array[Class[_]] = stackWalkerClassStack match {
+ case OptionVal.Some(walk) =>
+ // drop the reflection frames so that the stack starts at this class,
like getClassContext
+ val trace = walk()
+ val start = trace.indexWhere(_ eq LoggerClass.getClass)
+ if (start > 0) trace.drop(start) else trace
+ case _ =>
+ new TrickySecurityManager().getClassStack
+ }
+
private val defaultPrefixesToSkip = List("scala.runtime",
"org.apache.pekko.actor.typed.internal")
/**
* Try to extract a logger class from the call stack, if not possible the
provided default is used
*/
def detectLoggerClassFromStack(default: Class[_], additionalPrefixesToSkip:
List[String] = Nil): Class[_] = {
- // TODO use stack walker API when we no longer need to support Java 8
try {
def skip(name: String): Boolean = {
def loop(skipList: List[String]): Boolean = skipList match {
@@ -49,7 +81,7 @@ private[pekko] object LoggerClass {
loop(additionalPrefixesToSkip ::: defaultPrefixesToSkip)
}
- val trace = new TrickySecurityManager().getClassStack
+ val trace = getClassStack
var suitableClass: OptionVal[Class[_]] = OptionVal.None
var idx = 1 // skip this method/class and right away
while (suitableClass.isEmpty && idx < trace.length) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]