瀏覽代碼

优化修改代码, 1-使得baseapp.Logger日志输出能动态改变级别和输出目标;2-增强baseapp的SingleInstanceRun()的携程并发调用安全

niujiuru 2 周之前
父節點
當前提交
0d7c77a56c
共有 3 個文件被更改,包括 170 次插入44 次删除
  1. 14 15
      baseapp/app.go
  2. 155 28
      baseapp/log.go
  3. 1 1
      utils/jsonrpc2/server.go

+ 14 - 15
baseapp/app.go

@@ -18,6 +18,7 @@ var Version, BuildTime string // 由Makefile传入
 
 var isExit atomic.Bool
 var exitCh = make(chan struct{})
+var signalReady atomic.Bool
 
 func ModuleInit() {
 	InitPath()
@@ -45,21 +46,16 @@ func SingleInstanceRun() { // 非阻塞单实例运行, 调用此函数后, 安
 	}
 
 	ch := make(chan os.Signal, 1)
-	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
+	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
+	signalReady.Store(true)
+
 	go func() {
-		for s := range ch {
-			switch s {
-			case syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1:
-				Logger.Infof("Received signal: %v", s)
-				mux.Close()
-				os.RemoveAll(lockFile)
-				isExit.Store(true)
-				close(exitCh)
-				return
-			default:
-				Logger.Warnf("Received unexpected signal: %v!", s)
-			}
-		}
+		sig := <-ch
+		Logger.Infof("Received signal: %v", sig)
+		_ = mux.Close()
+		_ = os.Remove(lockFile)
+		isExit.Store(true)
+		close(exitCh)
 	}()
 }
 
@@ -72,7 +68,10 @@ func IsExit2() <-chan struct{} {
 }
 
 func SafeExit() { // 安全退出
+	if !signalReady.Load() {
+		return
+	}
 	pid := os.Getpid()
-	syscall.Kill(pid, syscall.SIGUSR1)
+	syscall.Kill(pid, syscall.SIGTERM)
 	<-exitCh
 }

+ 155 - 28
baseapp/log.go

@@ -1,16 +1,32 @@
+// Package baseapp provides application-level logging facilities.
+//
+// It supports dynamic log level and output target control,
+// hot reloading without restarting the process,
+// and safe lifecycle management for long-running applications.
+//
+// The logger is designed for industrial-grade applications,
+// providing features like:
+//   - Console and file output (with log rotation and compression)
+//   - Atomic-level dynamic log level changes
+//   - Safe multi-threaded usage via internal synchronization
+//
 // Author: NiuJiuRu
 // Email: niujiuru@qq.com
+// Last modified: 2026-06-03
 
 package baseapp
 
 import "C"
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"os"
 	"path/filepath"
 	"strings"
+	"sync"
+	"sync/atomic"
 
 	"github.com/sirupsen/logrus"
 	"gopkg.in/ini.v1"
@@ -27,28 +43,22 @@ const (
 	LEVEL_WARN        = level(logrus.WarnLevel)  // warning
 	LEVEL_ERROR       = level(logrus.ErrorLevel) // error
 	LEVEL_FATAL       = level(logrus.FatalLevel) // fatal, 会结束程序运行, 会自动调用os.Exit(1)
-	LEVEL_PANIC       = level(logrus.PanicLevel) // panic, 会触发程序崩溃, 不处理的话, 程序退出
+	LEVEL_PANIC       = level(logrus.PanicLevel) // panic, 会触发程序崩溃, 不处理的话, =程序退出
 )
 
-func (l *level) fromString(s string) {
-	switch strings.ToLower(s) {
-	case "trace":
-		*l = LEVEL_TRACE
-	case "debug":
-		*l = LEVEL_DEBUG
-	case "warn":
-		*l = LEVEL_WARN
-	case "error":
-		*l = LEVEL_ERROR
-	case "fatal":
-		*l = LEVEL_FATAL
-	case "panic":
-		*l = LEVEL_PANIC
-	default:
+func (l *level) FromString(s string) {
+	lvl, err := logrus.ParseLevel(strings.TrimSpace(s))
+	if err == nil {
+		*l = level(lvl)
+	} else {
 		*l = LEVEL_INFO
 	}
 }
 
+func (l level) String() string {
+	return logrus.Level(l).String()
+}
+
 // 输出目标
 type target uint32
 
@@ -58,8 +68,8 @@ const (
 	TARGET_FILE                       // 输出到文件
 )
 
-func (t *target) fromString(s string) {
-	switch strings.ToLower(s) {
+func (t *target) FromString(s string) {
+	switch strings.ToLower(strings.TrimSpace(s)) {
 	case "console":
 		*t = TARGET_CONSOLE
 	case "file":
@@ -71,6 +81,21 @@ func (t *target) fromString(s string) {
 	}
 }
 
+func (t target) String() string {
+	if t == TARGET_NONE {
+		return "none"
+	}
+
+	parts := make([]string, 0, 2)
+	if (t & TARGET_CONSOLE) != 0 {
+		parts = append(parts, "console")
+	}
+	if (t & TARGET_FILE) != 0 {
+		parts = append(parts, "file")
+	}
+	return strings.Join(parts, ",")
+}
+
 // 输出格式
 type formatter struct{}
 
@@ -113,6 +138,8 @@ type config struct {
 var (
 	Logger        *logrus.Logger
 	logFileWriter *lumberjack.Logger
+	logMu         sync.Mutex
+	initialized   atomic.Bool
 	cfgLog        = &config{
 		Level:      LEVEL_TRACE,
 		Target:     TARGET_CONSOLE,
@@ -121,15 +148,26 @@ var (
 		MaxBackups: 0,
 		Compress:   true,
 	}
+	ErrLoggerNotInitialized = errors.New("logger is not initialized")
 )
 
 func InitLogger(logCfgFile string) {
+	logMu.Lock()
+	defer logMu.Unlock()
+
+	if initialized.Load() {
+		return
+	}
+
 	cfgFile := logCfgFile
 	if cfgFile == "" {
 		cfgFile = filepath.Join(CFG_DIR, "config.ini")
 	}
 
-	cfgIni, err := ini.Load(cfgFile) // 从配置文件中加载相关配置项覆盖默认值
+	levelStr := "info"     // ---初始化设置默认的日志级别---
+	targetStr := "console" // ---初始化设置默认的输出目标---
+
+	cfgIni, err := ini.Load(cfgFile) // 从配置文件中加载设置
 	if err == nil && cfgIni.HasSection("Log") {
 		tmpCfgLog := *cfgLog
 		err = cfgIni.Section("Log").MapTo(&tmpCfgLog)
@@ -139,28 +177,89 @@ func InitLogger(logCfgFile string) {
 			*cfgLog = tmpCfgLog
 		}
 		if cfgIni.Section("Log").HasKey("Level") {
-			cfgLog.Level.fromString(cfgIni.Section("Log").Key("Level").String())
+			levelStr = cfgIni.Section("Log").Key("Level").String()
 		}
 		if cfgIni.Section("Log").HasKey("Target") {
-			cfgLog.Target.fromString(cfgIni.Section("Log").Key("Target").String())
+			targetStr = cfgIni.Section("Log").Key("Target").String()
 		}
 	}
 
 	Logger = logrus.New()
+	setLogLevelInternal(levelStr)       // 1, 设置日志级别
+	Logger.SetFormatter(new(formatter)) // 2, 设置日志格式
+	setLogTargetInternal(targetStr)     // 3, 设置输出目标
+
+	initialized.Store(true) ////// 标记日志模块已初始化完成
+}
+
+func GetLogLevel() (string, error) {
+	logMu.Lock()
+	defer logMu.Unlock()
+
+	if !initialized.Load() {
+		return "", ErrLoggerNotInitialized
+	}
+
+	return cfgLog.Level.String(), nil
+}
+
+func setLogLevelInternal(levelStr string) {
+	var lvl level
+	lvl.FromString(levelStr)
+	cfgLog.Level = lvl
+
 	Logger.SetLevel(logrus.Level(cfgLog.Level))
-	if cfgLog.Level == LEVEL_TRACE || cfgLog.Level == LEVEL_DEBUG {
-		Logger.SetReportCaller(true) // 使得输出日志时能够获取到文件名和行号
+	Logger.SetReportCaller(
+		lvl == LEVEL_TRACE || lvl == LEVEL_DEBUG,
+	) // 使得输出日志时能够获取到文件名和行号
+}
+
+func UpdateLogLevel(levelStr string) error {
+	logMu.Lock()
+	defer logMu.Unlock()
+
+	if !initialized.Load() {
+		return ErrLoggerNotInitialized
 	}
-	Logger.SetFormatter(new(formatter))
+
+	setLogLevelInternal(levelStr)
+	return nil
+}
+
+func GetLogTarget() (string, error) {
+	logMu.Lock()
+	defer logMu.Unlock()
+
+	if !initialized.Load() {
+		return "", ErrLoggerNotInitialized
+	}
+
+	return cfgLog.Target.String(), nil
+}
+
+func setLogTargetInternal(targetStr string) {
+	var t target
+	t.FromString(targetStr)
+	cfgLog.Target = t
 
 	writers := make([]io.Writer, 0)
 	if (cfgLog.Target & TARGET_CONSOLE) != 0 {
 		writers = append(writers, os.Stdout)
 	}
 	if (cfgLog.Target & TARGET_FILE) != 0 {
-		logFile := filepath.Join(LOG_DIR, EXEC_FILENAME+".log")
+		logDir := LOG_DIR
+		if logDir == "" {
+			logDir = "."
+		} else if info, err := os.Stat(logDir); err != nil || !info.IsDir() {
+			logDir = "."
+		}
+		logFile := filepath.Join(logDir, EXEC_FILENAME+".log")
+		old := logFileWriter
 		logFileWriter = &lumberjack.Logger{Filename: logFile, MaxSize: cfgLog.MaxSize, MaxAge: cfgLog.MaxAge,
 			MaxBackups: cfgLog.MaxBackups, Compress: cfgLog.Compress}
+		if old != nil {
+			old.Close()
+		}
 		writers = append(writers, logFileWriter)
 	}
 
@@ -168,27 +267,55 @@ func InitLogger(logCfgFile string) {
 		multiWriter := io.MultiWriter(writers...)
 		Logger.SetOutput(multiWriter)
 	} else {
-		Logger.SetOutput(io.Discard) // 不输出
+		Logger.SetOutput(io.Discard) // 不输出日志
 	}
 }
 
+func UpdateLogTarget(targetStr string) error {
+	logMu.Lock()
+	defer logMu.Unlock()
+
+	if !initialized.Load() {
+		return ErrLoggerNotInitialized
+	}
+
+	setLogTargetInternal(targetStr)
+	return nil
+}
+
 func ExitLogger() {
+	logMu.Lock()
+	defer logMu.Unlock()
+
 	if Logger != nil {
-		Logger.Out = io.Discard
+		Logger.SetOutput(io.Discard) // 不输出日志
 	}
+
 	if logFileWriter != nil {
 		logFileWriter.Close() // 关闭日志文件, 只是将日志提交到内核缓存区, 如果要强制落盘, 避免日志丢失,
 		// 需要修改开源库代码, 在关闭函数中调用"l.file.Sync()"方法并等待一会儿, 20251021 by niujiuru.
 		logFileWriter = nil
 	}
+
+	if initialized.Load() {
+		initialized.Store(false)
+	}
 }
 
 //export LogFromGo
 func LogFromGo(level *C.char, file *C.char, line C.int, message *C.char) {
+	if !initialized.Load() {
+		return
+	}
+
 	goLevel := C.GoString(level)
 	goMessage := C.GoString(message)
 
-	if (cfgLog.Level == LEVEL_TRACE || cfgLog.Level == LEVEL_DEBUG) && line > 0 {
+	logMu.Lock()
+	lvl := cfgLog.Level
+	logMu.Unlock()
+
+	if (lvl == LEVEL_TRACE || lvl == LEVEL_DEBUG) && line > 0 {
 		gofile := filepath.Base(C.GoString(file))
 		goline := int(line)
 		goMessage = fmt.Sprintf("(from %s:%d): %s", gofile, goline, goMessage)

+ 1 - 1
utils/jsonrpc2/server.go

@@ -151,7 +151,7 @@ func (s *RPCServer) ListMethods() []string {
 //
 // Example:
 //
-//	server := jsonrpc2.NewRPCServer("MyRPCServer", baseapp.Logger)
+//	server, _ := jsonrpc2.NewRPCServer("MyRPCServer", baseapp.Logger)
 //	http.Handle("/rpc", server)
 //	http.ListenAndServe(":8080", nil)
 //