[2.x] fix: sbt -debug doesn't display loading log (#8819)

* Fix #4979: apply -debug (and other level options) at startup so loading shows debug log

- Parse log level from configuration.arguments in StandardMain.initialState
- Pass initialLevel to GlobalLogging.initial so console appender uses it from first log
- Set Keys.logLevel and BasicKeys.explicitGlobalLogLevels in initial state when level option present
- Add initialLevel parameter to GlobalLogging.initial (default Level.Info) for backward compatibility
- Add InitialLogLevelSpec tests for logLevelFromArguments
- Add docs/fix-4979-manual-verification.md for manual reproduction
This commit is contained in:
bitloi 2026-02-26 13:50:31 -05:00 committed by GitHub
parent f04093bd9f
commit 33ac10c1ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 14 deletions

View File

@ -84,11 +84,19 @@ object GlobalLogging {
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging,
newBackingFile: => File,
console: ConsoleOut
): GlobalLogging =
initial(newAppender, newBackingFile, console, Level.Info)
def initial(
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging,
newBackingFile: => File,
console: ConsoleOut,
initialLevel: Level.Value = Level.Info
): GlobalLogging = {
val loggerName = generateName
val log = LoggerContext.globalContext.logger(loggerName, None, None)
val appender = ConsoleAppender(ConsoleAppender.generateName(), console)
LoggerContext.globalContext.addAppender(loggerName, appender -> Level.Info)
LoggerContext.globalContext.addAppender(loggerName, appender -> initialLevel)
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
}
}

View File

@ -246,17 +246,33 @@ object StandardMain {
ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving "))
ConsoleOut.setGlobalProxy(console)
private def initialGlobalLogging(file: Option[File]): GlobalLogging = {
def createTemp(attempt: Int = 0): File = Retry {
private[sbt] def logLevelFromArguments(args: Seq[String]): Level.Value =
val earlyCmd = BasicCommandStrings.EarlyCommand
val levelOptions = Level.values.toSeq.flatMap: v =>
List("-" + v.toString, "--" + v.toString)
def levelFromArg(arg: String): Option[Level.Value] =
if arg.startsWith(earlyCmd + "(") && arg.endsWith(")") then
val inner = arg.slice(earlyCmd.length + 1, arg.length - 1).trim
Level.values.find(_.toString == inner)
else if levelOptions.contains(arg) then
Level.values.find(v => arg == "-" + v.toString || arg == "--" + v.toString)
else None
args.flatMap(levelFromArg).headOption.getOrElse(Level.Info)
private def initialGlobalLogging(file: Option[File], initialLevel: Level.Value): GlobalLogging =
def createTemp(attempt: Int = 0): File = Retry:
file.foreach(f => if (!f.exists()) IO.createDirectory(f))
File.createTempFile("sbt-global-log", ".log", file.orNull)
}
GlobalLogging.initial(
MainAppender.globalDefault(ConsoleOut.globalProxy),
createTemp(),
ConsoleOut.globalProxy
ConsoleOut.globalProxy,
initialLevel
)
}
private def initialGlobalLogging(file: Option[File]): GlobalLogging =
initialGlobalLogging(file, Level.Info)
def initialGlobalLogging(file: File): GlobalLogging = initialGlobalLogging(Option(file))
@deprecated("use version that takes file argument", "1.4.0")
def initialGlobalLogging: GlobalLogging = initialGlobalLogging(None)
@ -265,8 +281,7 @@ object StandardMain {
configuration: xsbti.AppConfiguration,
initialDefinitions: Seq[Command],
preCommands: Seq[String]
): State = {
// This is to workaround https://github.com/sbt/io/issues/110
): State =
if (!sys.props.contains("jna.nosys")) sys.props.put("jna.nosys", "true")
import BasicCommandStrings.{ DashDashDetachStdio, DashDashServer, isEarlyCommand }
@ -274,11 +289,18 @@ object StandardMain {
configuration.arguments
.map(_.trim)
.filterNot(c => c == DashDashDetachStdio || c == DashDashServer)
.toSeq
val initialLevel = logLevelFromArguments(userCommands)
val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand)
val commands = (earlyCommands ++ normalCommands).toList map { x =>
Exec(x, None)
}
val initAttrs = BuiltinCommands.initialAttributes
val commands = (earlyCommands ++ normalCommands).toList.map(x => Exec(x, None))
val baseAttrs = BuiltinCommands.initialAttributes
val initAttrs =
if initialLevel == Level.Info then baseAttrs
else
baseAttrs
.put(Keys.logLevel.key, initialLevel)
.put(BasicKeys.explicitGlobalLogLevels, true)
val logDir = BuildPaths.globalLoggingStandard(configuration.baseDirectory)
val s = State(
configuration,
initialDefinitions,
@ -287,12 +309,12 @@ object StandardMain {
commands,
State.newHistory,
initAttrs,
initialGlobalLogging(BuildPaths.globalLoggingStandard(configuration.baseDirectory)),
initialGlobalLogging(Option(logDir), initialLevel),
None,
State.Continue
)
s.initializeClassLoaderCache
}
end initialState
}
import sbt.BasicCommandStrings.*

View File

@ -0,0 +1,38 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
import sbt.util.Level
object InitialLogLevelSpec extends verify.BasicTestSuite:
test("logLevelFromArguments returns Debug when -debug is in arguments"):
assert(StandardMain.logLevelFromArguments(Seq("-debug")) == Level.Debug)
test("logLevelFromArguments returns Debug when --debug is in arguments"):
assert(StandardMain.logLevelFromArguments(Seq("compile", "--debug")) == Level.Debug)
test("logLevelFromArguments returns Debug when early(debug) is in arguments"):
assert(StandardMain.logLevelFromArguments(Seq("early(debug)")) == Level.Debug)
assert(StandardMain.logLevelFromArguments(Seq("compile", "early(debug)")) == Level.Debug)
test("logLevelFromArguments returns Info when no level option in arguments"):
assert(StandardMain.logLevelFromArguments(Seq()) == Level.Info)
assert(StandardMain.logLevelFromArguments(Seq("compile", "run")) == Level.Info)
test("logLevelFromArguments uses first level option when multiple present"):
assert(StandardMain.logLevelFromArguments(Seq("-warn", "-debug")) == Level.Warn)
assert(StandardMain.logLevelFromArguments(Seq("-error", "-info")) == Level.Error)
test("logLevelFromArguments supports all level options"):
assert(StandardMain.logLevelFromArguments(Seq("-info")) == Level.Info)
assert(StandardMain.logLevelFromArguments(Seq("--warn")) == Level.Warn)
assert(StandardMain.logLevelFromArguments(Seq("--error")) == Level.Error)
end InitialLogLevelSpec