mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
feat(partnerctl): add --log-level option for run command and wire runtime log level to Partner-Core
This commit is contained in:
37
.idea/misc.xml
generated
37
.idea/misc.xml
generated
@@ -1,27 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<list size="20">
|
||||
<list size="21">
|
||||
<item index="0" class="java.lang.String" itemvalue="lombok.Data" />
|
||||
<item index="1" class="java.lang.String" itemvalue="net.bytebuddy.implementation.bind.annotation.RuntimeType" />
|
||||
<item index="2" class="java.lang.String" itemvalue="picocli.CommandLine.Command" />
|
||||
<item index="3" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
|
||||
<item index="4" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
|
||||
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
|
||||
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
|
||||
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
|
||||
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
|
||||
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
|
||||
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
|
||||
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
|
||||
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
|
||||
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
|
||||
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
|
||||
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
|
||||
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
|
||||
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
|
||||
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
|
||||
<item index="19" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
|
||||
<item index="3" class="java.lang.String" itemvalue="picocli.CommandLine.Mixin" />
|
||||
<item index="4" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Capability" />
|
||||
<item index="5" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore" />
|
||||
<item index="6" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod" />
|
||||
<item index="7" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.CoordinateManager" />
|
||||
<item index="8" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.capability.annotation.Coordinated" />
|
||||
<item index="9" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.component.annotation.Init" />
|
||||
<item index="10" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AfterExecute" />
|
||||
<item index="11" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentRunningModule" />
|
||||
<item index="12" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule" />
|
||||
<item index="13" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.BeforeExecute" />
|
||||
<item index="14" class="java.lang.String" itemvalue="work.slhaf.partner.api.agent.factory.module.annotation.Init" />
|
||||
<item index="15" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CapabilityMethod" />
|
||||
<item index="16" class="java.lang.String" itemvalue="work.slhaf.partner.api.capability.annotation.CoordinateManager" />
|
||||
<item index="17" class="java.lang.String" itemvalue="work.slhaf.partner.api.register.capability.annotation.Capability" />
|
||||
<item index="18" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore" />
|
||||
<item index="19" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod" />
|
||||
<item index="20" class="java.lang.String" itemvalue="work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent" />
|
||||
</list>
|
||||
<writeAnnotations>
|
||||
<writeAnnotation name="work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<configuration>
|
||||
<property name="PARTNER_HOME" value="${PARTNER_HOME:-${user.home}/.partner}"/>
|
||||
<property name="PARTNER_LOG_DIR" value="${PARTNER_HOME}/state/trace/log"/>
|
||||
<property name="LOG_LEVEL" value="${PARTNER_LOG_LEVEL}:-${partner.log.level:-INFO}"/>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
@@ -24,7 +25,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<root level="${LOG_LEVEL}">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="PARTNER_FILE"/>
|
||||
</root>
|
||||
|
||||
@@ -4,11 +4,7 @@ import picocli.CommandLine
|
||||
import work.slhaf.partner.ctl.commands.control.*
|
||||
import work.slhaf.partner.ctl.i18n.I18n.text
|
||||
import work.slhaf.partner.ctl.support.CommandInterrupted
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "run",
|
||||
@@ -26,6 +22,14 @@ class RunCommand : Runnable {
|
||||
)
|
||||
var background: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-l", "--log-level"],
|
||||
descriptionKey = "cli.log.option.level.description",
|
||||
defaultValue = "INFO",
|
||||
converter = [LogLevelConverter::class],
|
||||
)
|
||||
lateinit var logLevel: LogLevel
|
||||
|
||||
override fun run() {
|
||||
val home = resolvePartnerHome()
|
||||
val partnerJar = resolvePartnerJar(home)
|
||||
@@ -35,98 +39,10 @@ class RunCommand : Runnable {
|
||||
}
|
||||
|
||||
if (background) {
|
||||
runInBackground(home, partnerJar)
|
||||
runInBackground(home, partnerJar, logLevel)
|
||||
} else {
|
||||
runInForeground(home, partnerJar)
|
||||
runInForeground(home, partnerJar, logLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun runInForeground(home: Path, partnerJar: Path) {
|
||||
val logFile = resolveLogFile(home)
|
||||
Files.createDirectories(logFile.parent)
|
||||
|
||||
val process = createPartnerProcessBuilder(home, partnerJar)
|
||||
.inheritIO()
|
||||
.start()
|
||||
|
||||
appendControlLog(logFile, text("control.run.log.foregroundStarting", process.pid(), partnerJar))
|
||||
|
||||
val shutdownHook = Thread {
|
||||
if (process.isAlive) {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook)
|
||||
|
||||
val exitCode = process.waitFor()
|
||||
runCatching { Runtime.getRuntime().removeShutdownHook(shutdownHook) }
|
||||
appendControlLog(logFile, text("control.run.log.exited", exitCode))
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw CommandInterrupted(text("control.run.error.exited", exitCode), exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runInBackground(home: Path, partnerJar: Path) {
|
||||
val pidFile = resolvePidFile(home)
|
||||
val logFile = resolveLogFile(home)
|
||||
val existingProcess = findPartnerProcesses(home, partnerJar).firstOrNull()
|
||||
|
||||
if (existingProcess != null) {
|
||||
throw CommandInterrupted(text("control.run.error.alreadyRunning", existingProcess.pid()))
|
||||
}
|
||||
|
||||
Files.createDirectories(pidFile.parent)
|
||||
Files.createDirectories(logFile.parent)
|
||||
|
||||
val process = createPartnerProcessBuilder(home, partnerJar)
|
||||
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
.redirectError(ProcessBuilder.Redirect.DISCARD)
|
||||
.start()
|
||||
|
||||
appendControlLog(logFile, text("control.run.log.backgroundStarting", process.pid(), partnerJar))
|
||||
|
||||
Thread.sleep(BACKGROUND_START_CHECK_MILLIS)
|
||||
if (!process.isAlive) {
|
||||
val exitCode = process.exitValue()
|
||||
appendControlLog(logFile, text("control.run.log.exited", exitCode))
|
||||
Files.deleteIfExists(pidFile)
|
||||
throw CommandInterrupted(text("control.run.error.exited", exitCode), exitCode)
|
||||
}
|
||||
|
||||
Files.writeString(
|
||||
pidFile,
|
||||
process.pid().toString(),
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING,
|
||||
)
|
||||
|
||||
println(text("control.run.success.backgroundStarted", process.pid()))
|
||||
println(text("control.run.info.logFile", logFile))
|
||||
}
|
||||
|
||||
private fun createPartnerProcessBuilder(home: Path, partnerJar: Path): ProcessBuilder {
|
||||
return ProcessBuilder("java", "-DPARTNER_HOME=$home", "-jar", partnerJar.toString())
|
||||
.apply {
|
||||
environment()["PARTNER_HOME"] = home.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendControlLog(logFile: Path, message: String) {
|
||||
Files.createDirectories(logFile.parent)
|
||||
Files.newOutputStream(
|
||||
logFile,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.APPEND,
|
||||
).use { output ->
|
||||
writeControlLog(output, message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeControlLog(output: OutputStream, message: String) {
|
||||
output.write("[partnerctl ${LocalDateTime.now()}] $message\n".toByteArray())
|
||||
output.flush()
|
||||
}
|
||||
|
||||
private const val BACKGROUND_START_CHECK_MILLIS = 500L
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package work.slhaf.partner.ctl.commands.control
|
||||
|
||||
import picocli.CommandLine
|
||||
import work.slhaf.partner.ctl.i18n.I18n.text
|
||||
import work.slhaf.partner.ctl.support.CommandInterrupted
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.time.LocalDateTime
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
@@ -25,7 +31,8 @@ fun resolvePidFile(home: Path): Path {
|
||||
}
|
||||
|
||||
fun resolveLogFile(home: Path): Path {
|
||||
return home.resolve("state").resolve("trace").resolve("log").resolve("partner-core.log").toAbsolutePath().normalize()
|
||||
return home.resolve("state").resolve("trace").resolve("log").resolve("partner-core.log").toAbsolutePath()
|
||||
.normalize()
|
||||
}
|
||||
|
||||
fun findPartnerProcesses(home: Path, partnerJar: Path): List<ProcessHandle> {
|
||||
@@ -95,3 +102,115 @@ private fun isPartnerProcess(process: ProcessHandle, partnerJar: Path): Boolean
|
||||
|
||||
return arguments.any { it == jarPath } || commandLine.contains(jarPath)
|
||||
}
|
||||
|
||||
|
||||
fun runInForeground(home: Path, partnerJar: Path, logLevel: LogLevel) {
|
||||
val logFile = resolveLogFile(home)
|
||||
Files.createDirectories(logFile.parent)
|
||||
|
||||
val process = createPartnerProcessBuilder(home, partnerJar, logLevel)
|
||||
.inheritIO()
|
||||
.start()
|
||||
|
||||
appendControlLog(logFile, text("control.run.log.foregroundStarting", process.pid(), partnerJar))
|
||||
|
||||
val shutdownHook = Thread {
|
||||
if (process.isAlive) {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook)
|
||||
|
||||
val exitCode = process.waitFor()
|
||||
runCatching { Runtime.getRuntime().removeShutdownHook(shutdownHook) }
|
||||
appendControlLog(logFile, text("control.run.log.exited", exitCode))
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw CommandInterrupted(text("control.run.error.exited", exitCode), exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun runInBackground(home: Path, partnerJar: Path, logLevel: LogLevel) {
|
||||
val pidFile = resolvePidFile(home)
|
||||
val logFile = resolveLogFile(home)
|
||||
val existingProcess = findPartnerProcesses(home, partnerJar).firstOrNull()
|
||||
|
||||
if (existingProcess != null) {
|
||||
throw CommandInterrupted(text("control.run.error.alreadyRunning", existingProcess.pid()))
|
||||
}
|
||||
|
||||
Files.createDirectories(pidFile.parent)
|
||||
Files.createDirectories(logFile.parent)
|
||||
|
||||
val process = createPartnerProcessBuilder(home, partnerJar, logLevel)
|
||||
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
.redirectError(ProcessBuilder.Redirect.DISCARD)
|
||||
.start()
|
||||
|
||||
appendControlLog(logFile, text("control.run.log.backgroundStarting", process.pid(), partnerJar))
|
||||
|
||||
Thread.sleep(BACKGROUND_START_CHECK_MILLIS)
|
||||
if (!process.isAlive) {
|
||||
val exitCode = process.exitValue()
|
||||
appendControlLog(logFile, text("control.run.log.exited", exitCode))
|
||||
Files.deleteIfExists(pidFile)
|
||||
throw CommandInterrupted(text("control.run.error.exited", exitCode), exitCode)
|
||||
}
|
||||
|
||||
Files.writeString(
|
||||
pidFile,
|
||||
process.pid().toString(),
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING,
|
||||
)
|
||||
|
||||
println(text("control.run.success.backgroundStarted", process.pid()))
|
||||
println(text("control.run.info.logFile", logFile))
|
||||
}
|
||||
|
||||
private fun createPartnerProcessBuilder(home: Path, partnerJar: Path, logLevel: LogLevel): ProcessBuilder {
|
||||
return ProcessBuilder(
|
||||
"java",
|
||||
"-Dpartner.log.level=${logLevel.name.uppercase()}",
|
||||
"-DPARTNER_HOME=$home",
|
||||
"-jar",
|
||||
partnerJar.toString()
|
||||
)
|
||||
.apply {
|
||||
environment()["PARTNER_HOME"] = home.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendControlLog(logFile: Path, message: String) {
|
||||
Files.createDirectories(logFile.parent)
|
||||
Files.newOutputStream(
|
||||
logFile,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.APPEND,
|
||||
).use { output ->
|
||||
writeControlLog(output, message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeControlLog(output: OutputStream, message: String) {
|
||||
output.write("[partnerctl ${LocalDateTime.now()}] $message\n".toByteArray())
|
||||
output.flush()
|
||||
}
|
||||
|
||||
private const val BACKGROUND_START_CHECK_MILLIS = 500L
|
||||
|
||||
enum class LogLevel {
|
||||
TRACE, DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
class LogLevelConverter : CommandLine.ITypeConverter<LogLevel> {
|
||||
override fun convert(value: String): LogLevel {
|
||||
return LogLevel.entries.firstOrNull {
|
||||
it.name.equals(value, ignoreCase = true)
|
||||
} ?: throw CommandLine.TypeConversionException(
|
||||
"invalid log level '$value'. Valid values: ${
|
||||
LogLevel.entries.joinToString(", ") { it.name.lowercase() }
|
||||
}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ cli.shutdown.option.force.description=Forcefully kill matching Partner process i
|
||||
cli.log.description=Show Partner logs.
|
||||
cli.log.option.tail.description=Number of log lines to show before exiting or following.
|
||||
cli.log.option.follow.description=Follow appended log output.
|
||||
cli.log.option.level.description=Set Partner runtime log level. Available values: TRACE, DEBUG, INFO, WARN, ERROR. Default: INFO.
|
||||
cli.chat.description=Start an interactive chat client.
|
||||
cli.config.description=Manage Partner configuration.
|
||||
cli.module.description=Manage Partner modules.
|
||||
|
||||
@@ -2,12 +2,13 @@ cli.partnerctl.description=Partner 命令行工具。
|
||||
cli.init.description=初始化 Partner agent。
|
||||
cli.run.description=启动 Partner agent。
|
||||
cli.run.option.background.description=后台运行 Partner。
|
||||
cli.shutdown.description=停止 Partner agent。
|
||||
cli.shutdown.option.timeout.description=优雅停止后等待的秒数,超时后失败或强制停止。
|
||||
cli.shutdown.option.force.description=如果匹配的 Partner 进程没有在超时前退出,则强制结束进程。
|
||||
cli.log.description=查看 Partner 日志。
|
||||
cli.log.option.tail.description=退出或 follow 前显示的日志行数。
|
||||
cli.log.option.follow.description=持续跟随新增日志输出。
|
||||
cli.log.option.level.description=指定 Partner 运行时日志等级。可选值:TRACE、DEBUG、INFO、WARN、ERROR。默认值:INFO。
|
||||
cli.shutdown.description=停止 Partner agent。
|
||||
cli.chat.description=启动交互式聊天客户端。
|
||||
cli.config.description=管理 Partner 配置。
|
||||
cli.module.description=管理 Partner 模块。
|
||||
|
||||
Reference in New Issue
Block a user