|
|
@@ -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)
|