// Copyright (c) 2026 S.A. (SNOWARE). Written for SCU Team. // Licensed under MulanPubL-2.0. // You may obtain a copy at: http://license.coscl.org.cn/MulanPubL-2.0 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND. // Package logger 基于 zerolog 的日志初始化,支持控制台输出(可读格式)+ 文件输出(JSON 结构化)。 // 日志目录优先级:exe 同目录 log/ → %APPDATA%/SRemoteConnectClient/log/ → 当前工作目录 log/ → 仅控制台输出。 package logger import ( "io" "os" "path/filepath" "time" "github.com/rs/zerolog" ) // Log 包级全局日志实例,供外部包使用。 var Log zerolog.Logger // Init 初始化全局日志。 // 尝试在 exe 目录下创建 log/,若无写权限(多用户安装场景)则回退到 %APPDATA%。 // 日志按日期命名,控制台使用可读格式(带颜色),文件使用 JSON 结构化格式。 func Init() { consoleWriter := zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: "2006-01-02 15:04:05", NoColor: false, } logFile := openLogFile() if logFile == nil { // 连 %APPDATA% 都写不了,仅控制台输出 Log = zerolog.New(consoleWriter). With().Timestamp().Caller().Logger(). Level(zerolog.DebugLevel) return } multi := io.MultiWriter(consoleWriter, logFile) Log = zerolog.New(multi). With().Timestamp().Caller().Logger(). Level(zerolog.DebugLevel) } // openLogFile 按优先级尝试打开日志文件。 // 返回 nil 表示无法创建日志文件,此时程序仅输出到控制台。 func openLogFile() *os.File { logDir, err := resolveLogDir() if err != nil { return nil } today := time.Now().Format("2006-01-02") logPath := filepath.Join(logDir, today+".log") f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil } return f } // resolveLogDir 解析日志目录。 // 优先使用 exe 同目录下的 log/;若不可写则使用 %APPDATA%/SRemoteConnectClient/log/。 // [PORTBARR] func resolveLogDir() (string, error) { // 1. 尝试 exe 同目录 exePath, _ := os.Executable() if exePath != "" { dir := filepath.Join(filepath.Dir(exePath), "log") if err := os.MkdirAll(dir, 0755); err == nil { // 验证确实可写 testFile := filepath.Join(dir, ".write_test") if f, err := os.Create(testFile); err == nil { f.Close() os.Remove(testFile) return dir, nil } } } // 2. 回退到 %APPDATA%/SRemoteConnectClient/log appData := os.Getenv("APPDATA") if appData == "" { appData = os.Getenv("LOCALAPPDATA") } if appData != "" { dir := filepath.Join(appData, "SRemoteConnectClient", "log") if err := os.MkdirAll(dir, 0755); err == nil { return dir, nil } } // 3. 最后尝试当前工作目录 cwd, _ := os.Getwd() dir := filepath.Join(cwd, "log") if err := os.MkdirAll(dir, 0755); err == nil { return dir, nil } return "", os.ErrPermission }