如何在基于swift-argument-parser的异步命令行工具中初始化swift-log
解决方案:自定义根命令的
main方法 直接在根命令结构体中实现static func main()方法,在解析命令行参数前完成日志系统的一次性初始化,这是最直接可靠的方式。
修改你的根命令代码如下:
import Foundation import ArgumentParser import Logging @main struct Cfddns: AsyncParsableCommand { static let configuration = CommandConfiguration( subcommands: [SecretCommand.self], defaultSubcommand: SecretCommand.self) static func main() async { // 仅执行一次的全局初始化逻辑 LoggingSystem.bootstrap { label in var handler = StreamLogHandler.standardOutput(label: label) handler.logLevel = .info // 在这里添加你的自定义日志配置(比如格式、元数据等) return handler } // 调用ArgumentParser默认的命令处理逻辑 do { try await self.main() } catch { Self.exit(withError: error) } } }
为什么这个方案可行?
- 这个
static func main()会替代ArgumentParser自动生成的程序入口,启动时只会执行一次,完全满足日志系统仅初始化一次的要求。 - 无需修改任何子命令代码,也不用依赖单例或复杂条件判断,逻辑清晰且维护成本低。
之前方案失效的原因
- 根命令
run()不执行:使用子命令时,ArgumentParser会直接路由到对应子命令的run(),根命令的run()不会被触发,因此无法在这里放初始化代码。 init()多次执行:ArgumentParser在解析命令、生成帮助信息等场景中,会多次实例化命令结构体,导致init()被调用多次,即使加条件判断也可能因为并发或重复实例化多次执行。- 子命令
run()初始化:虽能生效,但需要在所有子命令的run()中添加判断,容易遗漏,维护风险高。
备选方案:全局惰性变量初始化
可以定义一个全局惰性变量,利用其仅初始化一次的特性完成日志配置:
import Logging // 惰性变量仅在首次被访问时初始化 private let logInitializer: Void = { LoggingSystem.bootstrap { label in StreamLogHandler.standardOutput(label: label) } }() // 在子命令的run()中引用变量触发初始化 struct SecretCommand: AsyncParsableCommand { mutating func run() async throws { _ = logInitializer // 触发全局日志初始化 // 后续业务逻辑... } }
不过这个方案需要确保所有子命令都引用该变量,否则可能无法触发初始化,可靠性不如自定义main方法。
内容的提问来源于stack exchange,提问作者gwcoffey
相关产品推荐
相关产品推荐

