Files
Daniel 3cd8bb5c99 🎨 CLI supports setting the logging level
Signed-off-by: Daniel <845765@qq.com>
2026-07-03 11:50:37 +08:00

173 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
"github.com/spf13/cobra"
)
var (
workspacePath string
outputFormat string
dryRun bool
logLevel string
)
var rootCmd = &cobra.Command{
Use: "SiYuan-Kernel",
Version: util.Ver,
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
// CLI 单次命令没有后台 cron 周期性 flush SQL 队列(server 模式才有 job.StartCron),进程在 main 返回后
// 即退出,内存里的 SQL 索引队列会随进程丢失(操作虽已落 index.queue,但要等下次启动 recoverIndexQueue
// 才恢复)。这里在命令执行完后统一落库,保证写完即可搜索。
name := cmd.Name()
// serve 子命令有自己的长驻退出流程(HandleSignal → model.Close 会 flush),不在此处理;
// workspace 子命令在 PersistentPreRunE 中跳过了数据库初始化,此时 sql 包未就绪,调用会 panic。
if name == "serve" || (cmd.Parent() != nil && cmd.Parent().Name() == "workspace") {
return nil
}
model.FlushTxQueue()
sql.FlushQueue()
return nil
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// workspace 子命令不需要工作空间校验
if cmd.Parent() != nil && cmd.Parent().Name() == "workspace" {
return nil
}
// 默认工作目录取内核可执行文件所在目录的上一级(打包后的 resources/appearance/、stage/ 所在目录),
// 而非内核可执行文件所在目录本身(resources/kernel/)。resolveWorkingDir() 会校验 appearance/langs 实际存在,
// 兼容开发态等多种目录布局。
if workingDir := resolveWorkingDir(); workingDir != "" {
util.WorkingDir = workingDir
}
langsDir := filepath.Join(util.WorkingDir, "appearance", "langs")
if _, err := os.Stat(langsDir); os.IsNotExist(err) {
return fmt.Errorf("appearance files not found at [%s]", langsDir)
}
// 设置工作空间路径
if workspacePath == "" {
workspacePath = os.Getenv("SIYUAN_WORKSPACE_PATH")
}
if workspacePath == "" {
workspacePath = filepath.Join(util.HomeDir, "SiYuan")
}
if _, err := os.Stat(workspacePath); os.IsNotExist(err) {
return fmt.Errorf("directory not found: %s", workspacePath)
}
if !util.IsWorkspaceDir(workspacePath) {
return fmt.Errorf("not a valid workspace: %s", workspacePath)
}
util.Mode = "prod"
util.InitWorkspace(workspacePath, util.WorkingDir)
logging.SetLogPath(filepath.Join(util.TempDir, "siyuan-cli.log"))
logging.SetLogToStdout(false)
// CLI 单次命令默认 warn 级别(siyuan-cli.log 只保留警告及以上),避免内核初始化的大量 Info/Debug 日志噪声;
// 用户可通过 --log-level 显式覆盖。把级别记入 util.CLILogLevel,使随后的 model.InitConf 不再用 conf.json 覆盖。
// 注意 serve 子命令走自己的 PersistentPreRunE,不受此默认值影响,仍跟随 conf.json 的 system.logLevel。
effectiveLevel := logLevel
if "" == effectiveLevel {
effectiveLevel = "warn"
}
logging.SetLogLevel(effectiveLevel)
util.CLILogLevel = effectiveLevel
model.InitConf()
sql.InitDatabase(false)
sql.InitHistoryDatabase(false)
sql.InitAssetContentDatabase(false)
sql.SetCaseSensitive(model.Conf.Search.CaseSensitive)
sql.SetIndexAssetPath(model.Conf.Search.IndexAssetPath)
// 让 CLI 一次性命令(如 search -m 4)也能命中语义搜索:StartEmbeddingIndexer 是死循环不能用于会立即退出的进程,这里只把开关置真
model.PrepareEmbeddingSearch()
return nil
},
}
// resolveWorkingDir 从内核可执行文件路径出发,探测若干候选目录,返回首个包含 appearance/langs 的目录作为
// 工作目录(打包后为 resources/,开发态视目录布局而定);找不到返回空串。rootCmd.PersistentPreRunE 与
// serve 子命令的 --wd 默认值都走这个函数,确保两条启动路径行为一致。
func resolveWorkingDir() string {
if exePath, err := os.Executable(); err == nil {
if resolved, err2 := filepath.EvalSymlinks(exePath); err2 == nil {
exePath = resolved
}
exeDir := filepath.Dir(exePath)
candidates := []string{
filepath.Join(exeDir, ".."), // resources/kernel/ → resources/ (production)
filepath.Join(exeDir, "..", "app"), // kernel/cli/ → kernel/ → app/
filepath.Join(exeDir, "app"), // kernel/ → app/
filepath.Join(exeDir, "..", "..", "app"), // kernel/cli/cmd/... → .../app/
}
// 添加 macOS app bundle 路径
if runtime.GOOS == "darwin" {
candidates = append(candidates,
filepath.Join(exeDir, "..", "..", "..", "..", "Resources"),
)
}
for _, d := range candidates {
langsDir := filepath.Join(d, "appearance", "langs")
if fi, err := os.Stat(langsDir); err == nil && fi.IsDir() {
return d
}
}
}
return ""
}
func init() {
rootCmd.Use = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
rootCmd.Short = "SiYuan Kernel v" + util.Ver
rootCmd.Long = "SiYuan Kernel v" + util.Ver + ". Manage workspace data directly or start the HTTP server."
rootCmd.PersistentFlags().StringVarP(&workspacePath, "workspace", "w", "", "workspace path")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "output format: table | json")
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "dry run mode: validate and print what would happen without making changes")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "v", "", "log level: off | trace | debug | info | warn | error | fatal (defaults to conf.json system.logLevel)")
}
func Execute() error {
return rootCmd.Execute()
}
func HasSubCommand(name string) bool {
for _, c := range rootCmd.Commands() {
if c.Name() == name {
return true
}
}
return false
}