1054 lines
25 KiB
Go
1054 lines
25 KiB
Go
package main
|
||
|
||
/*
|
||
#include <stdlib.h>
|
||
*/
|
||
import "C"
|
||
import (
|
||
"bufio"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"runtime"
|
||
"sort"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
"unsafe"
|
||
)
|
||
|
||
// LogLevel 日志级别类型
|
||
type LogLevel int
|
||
|
||
const (
|
||
LevelSuccess LogLevel = iota
|
||
LevelInfo
|
||
LevelWarning
|
||
LevelError
|
||
)
|
||
|
||
// SplitType 日志分片方式
|
||
type SplitType int
|
||
|
||
const (
|
||
SplitByMonth SplitType = iota
|
||
SplitByDay
|
||
SplitByHour
|
||
SplitByMinute
|
||
SplitBySecond
|
||
)
|
||
|
||
// RotateType 日志分割方式
|
||
type RotateType int
|
||
|
||
const (
|
||
RotateBySize RotateType = iota
|
||
RotateByCount
|
||
)
|
||
|
||
// 上下文键类型
|
||
type contextKey string
|
||
|
||
const (
|
||
loggerKey contextKey = "logger"
|
||
taskTypeKey contextKey = "task_type"
|
||
)
|
||
|
||
// LevelFileLogger 每个级别的文件日志器
|
||
type LevelFileLogger struct {
|
||
mu sync.RWMutex // 读写锁,保证并发安全
|
||
level LogLevel // 日志级别
|
||
logDir string // 日志目录
|
||
splitType SplitType // 分片方式
|
||
rotateType RotateType // 轮转方式
|
||
maxSize int64 // 最大文件大小
|
||
maxCount int // 最大文件数量
|
||
currentFile *os.File // 当前日志文件
|
||
currentDate string // 当前日期
|
||
writer io.Writer // 写入器
|
||
taskType string // 任务类型
|
||
}
|
||
|
||
// Logger 日志结构体
|
||
type Logger struct {
|
||
mu sync.RWMutex
|
||
baseLogDir string // 基础日志目录
|
||
splitType SplitType // 分片方式
|
||
rotateType RotateType // 轮转方式
|
||
maxSize int64 // 最大文件大小
|
||
maxCount int // 最大文件数量
|
||
level LogLevel // 日志级别
|
||
enableCaller bool // 是否启用调用者信息
|
||
defaultTaskType string // 默认任务类型
|
||
levelLoggers map[string]*LevelFileLogger // key: "level-taskType" 级别日志器映射
|
||
lastUsed time.Time // 最后使用时间
|
||
}
|
||
|
||
// Config 日志配置
|
||
type Config struct {
|
||
LogDir string
|
||
SplitType SplitType
|
||
RotateType RotateType
|
||
MaxSize int64
|
||
MaxCount int
|
||
Level LogLevel
|
||
EnableCaller bool
|
||
DefaultTaskType string // 默认任务类型
|
||
}
|
||
|
||
// DLLConfig 用于JSON解析的配置结构体
|
||
type DLLConfig struct {
|
||
LogDir string `json:"log_dir"`
|
||
SplitType SplitType `json:"split_type"`
|
||
RotateType RotateType `json:"rotate_type"`
|
||
MaxSize int64 `json:"max_size"`
|
||
MaxCount int `json:"max_count"`
|
||
Level LogLevel `json:"level"`
|
||
EnableCaller bool `json:"enable_caller"`
|
||
DefaultTaskType string `json:"default_task_type"`
|
||
}
|
||
|
||
// 日志级别字符串映射
|
||
var levelStrings = map[LogLevel]string{
|
||
LevelSuccess: "SUCCESS",
|
||
LevelInfo: "INFO",
|
||
LevelWarning: "WARNING",
|
||
LevelError: "ERROR",
|
||
}
|
||
|
||
// 级别目录名映射
|
||
var levelDirs = map[LogLevel]string{
|
||
LevelSuccess: "success",
|
||
LevelInfo: "info",
|
||
LevelWarning: "warning",
|
||
LevelError: "error",
|
||
}
|
||
|
||
// 分片格式映射
|
||
var splitFormats = map[SplitType]string{
|
||
SplitByMonth: "2006-01",
|
||
SplitByDay: "2006-01-02",
|
||
SplitByHour: "2006-01-02-15",
|
||
SplitByMinute: "2006-01-02-15-04",
|
||
SplitBySecond: "2006-01-02-15-04-05",
|
||
}
|
||
|
||
// 全局变量
|
||
var (
|
||
loggers = make(map[string]*Logger)
|
||
contexts = make(map[string]context.Context)
|
||
globalMu sync.Mutex
|
||
lastCleanup time.Time
|
||
)
|
||
|
||
// 初始化自动清理
|
||
func init() {
|
||
go autoCleanupRoutine()
|
||
}
|
||
|
||
func autoCleanupRoutine() {
|
||
ticker := time.NewTicker(30 * time.Minute) // 每30分钟清理一次
|
||
defer ticker.Stop()
|
||
|
||
for range ticker.C {
|
||
cleanupExpiredResources() // 清理2小时未使用的资源
|
||
}
|
||
}
|
||
|
||
func cleanupExpiredResources() {
|
||
globalMu.Lock()
|
||
defer globalMu.Unlock()
|
||
|
||
now := time.Now()
|
||
cutoff := now.Add(-2 * time.Hour) // 2小时未使用的资源
|
||
|
||
for handle, logger := range loggers {
|
||
if logger == nil {
|
||
delete(loggers, handle)
|
||
delete(contexts, handle)
|
||
continue
|
||
}
|
||
|
||
// 使用cutoff:清理2小时未使用的logger
|
||
logger.mu.RLock()
|
||
lastUsed := logger.lastUsed
|
||
logger.mu.RUnlock()
|
||
|
||
if lastUsed.Before(cutoff) {
|
||
logger.Close() // 关闭文件资源
|
||
delete(loggers, handle)
|
||
delete(contexts, handle)
|
||
}
|
||
}
|
||
|
||
lastCleanup = now
|
||
}
|
||
|
||
// NewLogger 创建新的日志实例
|
||
func NewLogger(config Config) (*Logger, error) {
|
||
// 确保基础日志目录存在
|
||
if err := os.MkdirAll(config.LogDir, 0755); err != nil {
|
||
return nil, fmt.Errorf("创建日志目录失败: %v", err)
|
||
}
|
||
|
||
// 设置默认任务类型
|
||
if config.DefaultTaskType == "" {
|
||
config.DefaultTaskType = "main"
|
||
}
|
||
|
||
logger := &Logger{
|
||
baseLogDir: config.LogDir,
|
||
splitType: config.SplitType,
|
||
rotateType: config.RotateType,
|
||
maxSize: config.MaxSize,
|
||
maxCount: config.MaxCount,
|
||
level: config.Level,
|
||
enableCaller: config.EnableCaller,
|
||
defaultTaskType: config.DefaultTaskType,
|
||
levelLoggers: make(map[string]*LevelFileLogger),
|
||
}
|
||
|
||
return logger, nil
|
||
}
|
||
|
||
// WithContext 将logger存入context
|
||
func (l *Logger) WithContext(ctx context.Context) context.Context {
|
||
return context.WithValue(ctx, loggerKey, l)
|
||
}
|
||
|
||
// WithTaskType 将任务类型存入context
|
||
func (l *Logger) WithTaskType(ctx context.Context, taskType string) context.Context {
|
||
return context.WithValue(ctx, taskTypeKey, taskType)
|
||
}
|
||
|
||
// FromContext 从context获取logger
|
||
func FromContext(ctx context.Context) *Logger {
|
||
if logger, ok := ctx.Value(loggerKey).(*Logger); ok {
|
||
return logger
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// getTaskTypeFromContext 从context获取任务类型,如果没有则返回默认值
|
||
func (l *Logger) getTaskTypeFromContext(ctx context.Context) string {
|
||
if taskType, ok := ctx.Value(taskTypeKey).(string); ok && taskType != "" {
|
||
return taskType
|
||
}
|
||
return l.defaultTaskType
|
||
}
|
||
|
||
// getCurrentDate 获取当前日期字符串
|
||
func (lfl *LevelFileLogger) getCurrentDate() string {
|
||
format := splitFormats[lfl.splitType]
|
||
return time.Now().Format(format)
|
||
}
|
||
|
||
// getLogFileName 获取日志文件名
|
||
func (lfl *LevelFileLogger) getLogFileName(date string) string {
|
||
fileName := fmt.Sprintf("%s-%s-%s.log", levelStrings[lfl.level], lfl.taskType, date)
|
||
return filepath.Join(lfl.logDir, fileName)
|
||
}
|
||
|
||
// getLevelLoggerKey 生成级别日志器的key
|
||
func getLevelLoggerKey(level LogLevel, taskType string) string {
|
||
return fmt.Sprintf("%d-%s", level, taskType)
|
||
}
|
||
|
||
// getLevelLogger 获取或创建指定任务类型的级别日志器
|
||
func (l *Logger) getLevelLogger(level LogLevel, taskType string) (*LevelFileLogger, error) {
|
||
key := getLevelLoggerKey(level, taskType)
|
||
|
||
// 第一次检查(读锁)
|
||
l.mu.RLock()
|
||
if levelLogger, exists := l.levelLoggers[key]; exists {
|
||
l.mu.RUnlock()
|
||
return levelLogger, nil
|
||
}
|
||
l.mu.RUnlock()
|
||
|
||
// 获取写锁创建新的日志器
|
||
l.mu.Lock()
|
||
defer l.mu.Unlock()
|
||
|
||
// 第二次检查(写锁内),防止并发创建
|
||
if levelLogger, exists := l.levelLoggers[key]; exists {
|
||
return levelLogger, nil
|
||
}
|
||
|
||
// 创建级别目录
|
||
levelDir := filepath.Join(l.baseLogDir, levelDirs[level])
|
||
if err := os.MkdirAll(levelDir, 0755); err != nil {
|
||
return nil, fmt.Errorf("创建级别目录失败: %v", err)
|
||
}
|
||
|
||
levelLogger := &LevelFileLogger{
|
||
level: level,
|
||
logDir: levelDir,
|
||
splitType: l.splitType,
|
||
rotateType: l.rotateType,
|
||
maxSize: l.maxSize,
|
||
maxCount: l.maxCount,
|
||
taskType: taskType,
|
||
}
|
||
|
||
if err := levelLogger.rotateFile(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 使用复合key存储
|
||
l.levelLoggers[key] = levelLogger
|
||
return levelLogger, nil
|
||
}
|
||
|
||
// rotateFile 轮转日志文件
|
||
func (lfl *LevelFileLogger) rotateFile() error {
|
||
lfl.mu.Lock()
|
||
defer lfl.mu.Unlock()
|
||
|
||
currentDate := lfl.getCurrentDate()
|
||
|
||
// 如果日期没变且不需要按大小分割,则不需要轮转
|
||
if lfl.currentDate == currentDate && lfl.currentFile != nil {
|
||
if lfl.rotateType == RotateBySize {
|
||
info, err := lfl.currentFile.Stat()
|
||
if err == nil && info.Size() < lfl.maxSize {
|
||
return nil
|
||
}
|
||
} else {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 关闭当前文件
|
||
if lfl.currentFile != nil {
|
||
err := lfl.currentFile.Close()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 创建新文件
|
||
fileName := lfl.getLogFileName(currentDate)
|
||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||
if err != nil {
|
||
return fmt.Errorf("创建日志文件失败: %v", err)
|
||
}
|
||
|
||
lfl.currentFile = file
|
||
lfl.currentDate = currentDate
|
||
lfl.writer = file
|
||
|
||
// 如果启用了控制台输出,同时输出到控制台
|
||
if os.Getenv("LOG_CONSOLE") == "true" {
|
||
lfl.writer = io.MultiWriter(file, os.Stdout)
|
||
}
|
||
|
||
// 处理文件数量限制
|
||
if lfl.rotateType == RotateByCount {
|
||
lfl.cleanupOldFiles()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// cleanupOldFiles 清理旧日志文件
|
||
func (lfl *LevelFileLogger) cleanupOldFiles() {
|
||
pattern := filepath.Join(lfl.logDir, fmt.Sprintf("%s-%s-*.log", levelStrings[lfl.level], lfl.taskType))
|
||
files, err := filepath.Glob(pattern)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
if len(files) <= lfl.maxCount {
|
||
return
|
||
}
|
||
|
||
// 按修改时间排序,删除最旧的文件
|
||
for i := 0; i < len(files)-lfl.maxCount; i++ {
|
||
err := os.Remove(files[i])
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
// writeLog 写入日志
|
||
func (lfl *LevelFileLogger) writeLog(message string) error {
|
||
// 检查是否需要轮转文件
|
||
if err := lfl.rotateFile(); err != nil {
|
||
return err
|
||
}
|
||
|
||
lfl.mu.RLock()
|
||
defer lfl.mu.RUnlock()
|
||
|
||
if lfl.writer != nil {
|
||
_, err := lfl.writer.Write([]byte(message))
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// getCallerInfo 获取调用者信息
|
||
func (l *Logger) getCallerInfo() string {
|
||
if !l.enableCaller {
|
||
return ""
|
||
}
|
||
|
||
_, file, line, ok := runtime.Caller(3) // 跳过3层调用栈
|
||
if !ok {
|
||
return ""
|
||
}
|
||
|
||
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
|
||
}
|
||
|
||
// log 基础日志方法
|
||
func (l *Logger) log(ctx context.Context, level LogLevel, format string, args ...interface{}) {
|
||
l.mu.Lock()
|
||
l.lastUsed = time.Now()
|
||
l.mu.Unlock()
|
||
|
||
// 级别过滤
|
||
if level < l.level {
|
||
return
|
||
}
|
||
|
||
// 获取任务类型
|
||
taskType := l.getTaskTypeFromContext(ctx)
|
||
|
||
// 获取或创建级别日志器
|
||
levelLogger, err := l.getLevelLogger(level, taskType)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
// 格式化日志条目
|
||
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
|
||
levelStr := levelStrings[level]
|
||
message := fmt.Sprintf(format, args...)
|
||
callerInfo := l.getCallerInfo()
|
||
|
||
var logEntry string
|
||
if callerInfo != "" {
|
||
logEntry = fmt.Sprintf("[%s] [%s] [%s] [%s] %s\n", timestamp, levelStr, taskType, callerInfo, message)
|
||
} else {
|
||
logEntry = fmt.Sprintf("[%s] [%s] [%s] %s\n", timestamp, levelStr, taskType, message)
|
||
}
|
||
|
||
// 写入对应级别的日志文件
|
||
err = levelLogger.writeLog(logEntry)
|
||
}
|
||
|
||
// Success 记录成功日志
|
||
func (l *Logger) Success(ctx context.Context, format string, args ...interface{}) {
|
||
l.log(ctx, LevelSuccess, format, args...)
|
||
}
|
||
|
||
// Info 记录信息日志
|
||
func (l *Logger) Info(ctx context.Context, format string, args ...interface{}) {
|
||
l.log(ctx, LevelInfo, format, args...)
|
||
}
|
||
|
||
// Warning 记录警告日志
|
||
func (l *Logger) Warning(ctx context.Context, format string, args ...interface{}) {
|
||
l.log(ctx, LevelWarning, format, args...)
|
||
}
|
||
|
||
// Error 记录错误日志
|
||
func (l *Logger) Error(ctx context.Context, format string, args ...interface{}) {
|
||
l.log(ctx, LevelError, format, args...)
|
||
}
|
||
|
||
// TimeCost 耗时记录方法
|
||
func (l *Logger) TimeCost(ctx context.Context, operation string) func() {
|
||
start := time.Now()
|
||
return func() {
|
||
cost := time.Since(start)
|
||
l.Info(ctx, "%s 耗时: %s", operation, cost.String())
|
||
}
|
||
}
|
||
|
||
// TimeCostMs 返回毫秒级耗时(直接返回耗时值)
|
||
func (l *Logger) TimeCostMs(ctx context.Context, operation string) func() float64 {
|
||
start := time.Now()
|
||
return func() float64 {
|
||
cost := time.Since(start)
|
||
ms := float64(cost.Nanoseconds()) / 1e6
|
||
l.Info(ctx, "%s 耗时: %.3fms", operation, ms)
|
||
return ms
|
||
}
|
||
}
|
||
|
||
// Close 关闭所有日志文件
|
||
func (l *Logger) Close() error {
|
||
l.mu.Lock()
|
||
defer l.mu.Unlock()
|
||
|
||
var lastErr error
|
||
for _, levelLogger := range l.levelLoggers {
|
||
levelLogger.mu.Lock()
|
||
if levelLogger.currentFile != nil {
|
||
if err := levelLogger.currentFile.Close(); err != nil {
|
||
lastErr = err
|
||
}
|
||
}
|
||
levelLogger.mu.Unlock()
|
||
}
|
||
|
||
// 清空映射
|
||
l.levelLoggers = make(map[string]*LevelFileLogger)
|
||
return lastErr
|
||
}
|
||
|
||
// ============================ DLL 导出函数 ============================
|
||
|
||
//export CreateLogger
|
||
func CreateLogger(configJSON *C.char) *C.char {
|
||
jsonStr := C.GoString(configJSON)
|
||
|
||
var config DLLConfig
|
||
if err := json.Unmarshal([]byte(jsonStr), &config); err != nil {
|
||
return C.CString("错误: " + err.Error())
|
||
}
|
||
|
||
loggerConfig := Config{
|
||
LogDir: config.LogDir,
|
||
SplitType: config.SplitType,
|
||
RotateType: config.RotateType,
|
||
MaxSize: config.MaxSize,
|
||
MaxCount: config.MaxCount,
|
||
Level: config.Level,
|
||
EnableCaller: config.EnableCaller,
|
||
DefaultTaskType: config.DefaultTaskType,
|
||
}
|
||
|
||
logger, err := NewLogger(loggerConfig)
|
||
if err != nil {
|
||
return C.CString("错误: " + err.Error())
|
||
}
|
||
|
||
globalMu.Lock()
|
||
defer globalMu.Unlock()
|
||
|
||
handle := fmt.Sprintf("logger_%d", time.Now().UnixNano())
|
||
loggers[handle] = logger
|
||
|
||
// 创建初始上下文
|
||
ctx := context.Background()
|
||
ctx = logger.WithContext(ctx)
|
||
contexts[handle] = ctx
|
||
|
||
return C.CString(handle)
|
||
}
|
||
|
||
//export CreateContextWithTaskType
|
||
func CreateContextWithTaskType(loggerHandle *C.char, taskType *C.char) *C.char {
|
||
handle := C.GoString(loggerHandle)
|
||
taskTypeStr := C.GoString(taskType)
|
||
|
||
globalMu.Lock()
|
||
defer globalMu.Unlock()
|
||
|
||
logger, exists := loggers[handle]
|
||
if !exists {
|
||
return C.CString("错误: 无效的logger句柄")
|
||
}
|
||
|
||
baseCtx, exists := contexts[handle]
|
||
if !exists {
|
||
return C.CString("错误: 无效的上下文")
|
||
}
|
||
|
||
// 创建带任务类型的新上下文
|
||
ctx := logger.WithTaskType(baseCtx, taskTypeStr)
|
||
|
||
ctxHandle := fmt.Sprintf("ctx_%d", time.Now().UnixNano())
|
||
contexts[ctxHandle] = ctx
|
||
|
||
return C.CString(ctxHandle)
|
||
}
|
||
|
||
//export LogInfo
|
||
func LogInfo(ctxHandle *C.char, message *C.char) {
|
||
handle := C.GoString(ctxHandle)
|
||
msg := C.GoString(message)
|
||
|
||
globalMu.Lock()
|
||
ctx, exists := contexts[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return
|
||
}
|
||
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Info(ctx, "%s", msg)
|
||
}
|
||
}
|
||
|
||
//export LogError
|
||
func LogError(ctxHandle *C.char, message *C.char) {
|
||
handle := C.GoString(ctxHandle)
|
||
msg := C.GoString(message)
|
||
|
||
globalMu.Lock()
|
||
ctx, exists := contexts[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return
|
||
}
|
||
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Error(ctx, "%s", msg)
|
||
}
|
||
}
|
||
|
||
//export LogWarning
|
||
func LogWarning(ctxHandle *C.char, message *C.char) {
|
||
handle := C.GoString(ctxHandle)
|
||
msg := C.GoString(message)
|
||
|
||
globalMu.Lock()
|
||
ctx, exists := contexts[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return
|
||
}
|
||
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Warning(ctx, "%s", msg)
|
||
}
|
||
}
|
||
|
||
//export LogSuccess
|
||
func LogSuccess(ctxHandle *C.char, message *C.char) {
|
||
handle := C.GoString(ctxHandle)
|
||
msg := C.GoString(message)
|
||
|
||
globalMu.Lock()
|
||
ctx, exists := contexts[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return
|
||
}
|
||
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Success(ctx, "%s", msg)
|
||
}
|
||
}
|
||
|
||
//export FreeString
|
||
func FreeString(str *C.char) {
|
||
// 这是专门用于释放C.CString返回的内存
|
||
C.free(unsafe.Pointer(str))
|
||
}
|
||
|
||
//export CloseAllLoggers
|
||
func CloseAllLoggers() *C.char {
|
||
globalMu.Lock()
|
||
defer globalMu.Unlock()
|
||
|
||
var errorCount int
|
||
var lastError string
|
||
|
||
// 关闭所有日志器
|
||
for handle, logger := range loggers {
|
||
if err := logger.Close(); err != nil {
|
||
errorCount++
|
||
lastError = err.Error()
|
||
}
|
||
delete(loggers, handle)
|
||
delete(contexts, handle)
|
||
}
|
||
|
||
if errorCount > 0 {
|
||
return C.CString(fmt.Sprintf("关闭了%d个logger,其中%d个出错,最后错误: %s",
|
||
len(loggers), errorCount, lastError))
|
||
}
|
||
|
||
return C.CString("成功关闭所有logger")
|
||
}
|
||
|
||
/*
|
||
// 便捷函数 - 通过上下文使用
|
||
func Success(ctx context.Context, format string, args ...interface{}) {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Success(ctx, format, args...)
|
||
}
|
||
}
|
||
|
||
func Info(ctx context.Context, format string, args ...interface{}) {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Info(ctx, format, args...)
|
||
}
|
||
}
|
||
|
||
func Warning(ctx context.Context, format string, args ...interface{}) {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Warning(ctx, format, args...)
|
||
}
|
||
}
|
||
|
||
func Error(ctx context.Context, format string, args ...interface{}) {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
logger.Error(ctx, format, args...)
|
||
}
|
||
}
|
||
|
||
func TimeCost(ctx context.Context, operation string) func() {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
return logger.TimeCost(ctx, operation)
|
||
}
|
||
return func() {}
|
||
}
|
||
|
||
func TimeCostMs(ctx context.Context, operation string) func() float64 {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
return logger.TimeCostMs(ctx, operation)
|
||
}
|
||
return func() float64 { return 0 }
|
||
}
|
||
|
||
// WithTaskType 便捷函数 - 将任务类型存入context
|
||
func WithTaskType(ctx context.Context, taskType string) context.Context {
|
||
if logger := FromContext(ctx); logger != nil {
|
||
return logger.WithTaskType(ctx, taskType)
|
||
}
|
||
return ctx
|
||
}
|
||
*/
|
||
|
||
// ============================ 日志读取功能 ============================
|
||
|
||
// LogEntry 表示一个日志条目
|
||
type LogEntry struct {
|
||
Timestamp string `json:"timestamp"`
|
||
Level string `json:"level"`
|
||
TaskType string `json:"task_type"`
|
||
Caller string `json:"caller,omitempty"`
|
||
Message string `json:"message"`
|
||
Time time.Time `json:"-"`
|
||
}
|
||
|
||
// ReadLogsConfig 读取日志的配置
|
||
type ReadLogsConfig struct {
|
||
Level LogLevel `json:"level"`
|
||
TaskType string `json:"task_type"`
|
||
StartTime string `json:"start_time"` // 格式: 2006-01-02 15:04:05
|
||
EndTime string `json:"end_time"` // 格式: 2006-01-02 15:04:05
|
||
MaxEntries int `json:"max_entries"` // 最大返回条目数
|
||
}
|
||
|
||
// parseLogLine 解析单行日志
|
||
func parseLogLine(line string) (*LogEntry, error) {
|
||
// 正则表达式:匹配四个 [...] 块,后面跟任意消息
|
||
re := regexp.MustCompile(`^\[(.*?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$`)
|
||
matches := re.FindStringSubmatch(line)
|
||
|
||
if len(matches) != 6 {
|
||
return nil, fmt.Errorf("log line does not match expected format")
|
||
}
|
||
|
||
timestampStr := matches[1]
|
||
levelStr := matches[2]
|
||
taskType := matches[3]
|
||
var caller, message string
|
||
if len(matches) == 6 {
|
||
// 有调用者信息 [caller]
|
||
caller = matches[4]
|
||
message = matches[5]
|
||
} else {
|
||
// 无调用者信息(只有三个 [])
|
||
caller = ""
|
||
message = matches[4]
|
||
}
|
||
|
||
// 解析时间戳(支持带毫秒格式:2006-01-02 15:04:05.000)
|
||
var logTime time.Time
|
||
var err error
|
||
if strings.Contains(timestampStr, ".") {
|
||
logTime, err = time.Parse("2006-01-02 15:04:05.000", timestampStr)
|
||
} else {
|
||
logTime, err = time.Parse("2006-01-02 15:04:05", timestampStr)
|
||
}
|
||
if err != nil {
|
||
// 如果解析失败,保留字符串但 Time 为零值(或可跳过)
|
||
logTime = time.Time{}
|
||
}
|
||
|
||
return &LogEntry{
|
||
Timestamp: timestampStr,
|
||
Level: levelStr,
|
||
TaskType: taskType,
|
||
Caller: caller,
|
||
Message: message,
|
||
Time: logTime,
|
||
}, nil
|
||
}
|
||
|
||
// readLogFile 读取单个日志文件
|
||
func (l *Logger) readLogFile(filePath string, config ReadLogsConfig) ([]LogEntry, error) {
|
||
file, err := os.Open(filePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer file.Close()
|
||
|
||
// 预解析时间范围
|
||
var startTime, endTime time.Time
|
||
var hasStartTime, hasEndTime bool
|
||
|
||
if config.StartTime != "" {
|
||
if t, err := time.Parse("2006-01-02 15:04:05", config.StartTime); err == nil {
|
||
startTime = t
|
||
hasStartTime = true
|
||
}
|
||
}
|
||
if config.EndTime != "" {
|
||
if t, err := time.Parse("2006-01-02 15:04:05", config.EndTime); err == nil {
|
||
endTime = t
|
||
hasEndTime = true
|
||
}
|
||
}
|
||
|
||
var entries []LogEntry
|
||
scanner := bufio.NewScanner(file)
|
||
|
||
for scanner.Scan() {
|
||
line := scanner.Text()
|
||
entry, err := parseLogLine(line)
|
||
if err != nil {
|
||
continue // 跳过无法解析的行
|
||
}
|
||
|
||
// 过滤条件
|
||
if config.Level != -1 && levelStrings[config.Level] != entry.Level {
|
||
continue
|
||
}
|
||
|
||
if config.TaskType != "" && config.TaskType != entry.TaskType {
|
||
continue
|
||
}
|
||
|
||
// 时间过滤(仅当 Time 有效时才比较)
|
||
if !entry.Time.IsZero() {
|
||
if hasStartTime && entry.Time.Before(startTime) {
|
||
continue
|
||
}
|
||
if hasEndTime && entry.Time.After(endTime) {
|
||
continue
|
||
}
|
||
}
|
||
|
||
entries = append(entries, *entry)
|
||
|
||
// 检查最大条目数
|
||
if config.MaxEntries > 0 && len(entries) >= config.MaxEntries {
|
||
break
|
||
}
|
||
}
|
||
|
||
if err := scanner.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return entries, nil
|
||
}
|
||
|
||
// GetLogs 获取日志条目
|
||
func (l *Logger) GetLogs(config ReadLogsConfig) ([]LogEntry, error) {
|
||
var allEntries []LogEntry
|
||
|
||
// 确定要搜索的级别目录
|
||
levelsToSearch := []LogLevel{}
|
||
if config.Level == -1 {
|
||
// 搜索所有级别
|
||
for level := range levelDirs {
|
||
levelsToSearch = append(levelsToSearch, level)
|
||
}
|
||
} else {
|
||
levelsToSearch = append(levelsToSearch, config.Level)
|
||
}
|
||
|
||
for _, level := range levelsToSearch {
|
||
levelDir := filepath.Join(l.baseLogDir, levelDirs[level])
|
||
|
||
// 构建文件匹配模式
|
||
pattern := filepath.Join(levelDir, "*.log")
|
||
if config.TaskType != "" {
|
||
pattern = filepath.Join(levelDir, fmt.Sprintf("%s-%s-*.log", levelStrings[level], config.TaskType))
|
||
}
|
||
|
||
files, err := filepath.Glob(pattern)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
// 按文件名排序(通常是时间顺序)
|
||
sort.Strings(files)
|
||
|
||
for _, file := range files {
|
||
entries, err := l.readLogFile(file, config)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
allEntries = append(allEntries, entries...)
|
||
|
||
// 检查最大条目数
|
||
if config.MaxEntries > 0 && len(allEntries) >= config.MaxEntries {
|
||
break
|
||
}
|
||
}
|
||
|
||
if config.MaxEntries > 0 && len(allEntries) >= config.MaxEntries {
|
||
break
|
||
}
|
||
}
|
||
|
||
// 按时间排序
|
||
sort.Slice(allEntries, func(i, j int) bool {
|
||
return allEntries[i].Time.Before(allEntries[j].Time)
|
||
})
|
||
|
||
// 限制返回数量
|
||
if config.MaxEntries > 0 && len(allEntries) > config.MaxEntries {
|
||
allEntries = allEntries[:config.MaxEntries]
|
||
}
|
||
|
||
return allEntries, nil
|
||
}
|
||
|
||
// ============================ DLL 导出函数 ============================
|
||
|
||
//export GetLogs
|
||
func GetLogs(loggerHandle *C.char, configJSON *C.char) *C.char {
|
||
handle := C.GoString(loggerHandle)
|
||
jsonStr := C.GoString(configJSON)
|
||
|
||
globalMu.Lock()
|
||
logger, exists := loggers[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return C.CString(`{"error": "无效的logger句柄"}`)
|
||
}
|
||
|
||
var config ReadLogsConfig
|
||
if err := json.Unmarshal([]byte(jsonStr), &config); err != nil {
|
||
return C.CString(`{"error": "配置解析错误: ` + err.Error() + `"}`)
|
||
}
|
||
|
||
// 设置默认值
|
||
if config.MaxEntries == 0 {
|
||
config.MaxEntries = 1000 // 默认最大1000条
|
||
}
|
||
|
||
entries, err := logger.GetLogs(config)
|
||
if err != nil {
|
||
return C.CString(`{"error": "读取日志失败: ` + err.Error() + `"}`)
|
||
}
|
||
|
||
result := map[string]interface{}{
|
||
"count": len(entries),
|
||
"entries": entries,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(result)
|
||
if err != nil {
|
||
return C.CString(`{"error": "JSON序列化失败: ` + err.Error() + `"}`)
|
||
}
|
||
|
||
return C.CString(string(jsonData))
|
||
}
|
||
|
||
//export GetLogFiles
|
||
func GetLogFiles(loggerHandle *C.char) *C.char {
|
||
handle := C.GoString(loggerHandle)
|
||
|
||
globalMu.Lock()
|
||
logger, exists := loggers[handle]
|
||
globalMu.Unlock()
|
||
|
||
if !exists {
|
||
return C.CString(`{"error": "无效的logger句柄"}`)
|
||
}
|
||
|
||
type LogFileInfo struct {
|
||
Level string `json:"level"`
|
||
TaskType string `json:"task_type"`
|
||
FileName string `json:"file_name"`
|
||
FileSize int64 `json:"file_size"`
|
||
ModTime string `json:"mod_time"`
|
||
}
|
||
|
||
var logFiles []LogFileInfo
|
||
|
||
for _, levelDirName := range levelDirs {
|
||
levelDir := filepath.Join(logger.baseLogDir, levelDirName)
|
||
|
||
pattern := filepath.Join(levelDir, "*.log")
|
||
files, err := filepath.Glob(pattern)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
for _, file := range files {
|
||
info, err := os.Stat(file)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
// 从文件名解析级别和任务类型
|
||
baseName := filepath.Base(file)
|
||
parts := strings.Split(strings.TrimSuffix(baseName, ".log"), "-")
|
||
if len(parts) >= 3 {
|
||
logFile := LogFileInfo{
|
||
Level: parts[0],
|
||
TaskType: parts[1],
|
||
FileName: baseName,
|
||
FileSize: info.Size(),
|
||
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
|
||
}
|
||
logFiles = append(logFiles, logFile)
|
||
}
|
||
}
|
||
}
|
||
|
||
result := map[string]interface{}{
|
||
"count": len(logFiles),
|
||
"files": logFiles,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(result)
|
||
if err != nil {
|
||
return C.CString(`{"error": "JSON序列化失败: ` + err.Error() + `"}`)
|
||
}
|
||
|
||
return C.CString(string(jsonData))
|
||
}
|
||
|
||
// LOGGER_VERSION 版本号
|
||
const (
|
||
LOGGER_VERSION = "v1"
|
||
)
|
||
|
||
// 获取版本信息
|
||
//
|
||
//export GetVersion
|
||
func GetVersion() *C.char {
|
||
return C.CString(LOGGER_VERSION)
|
||
}
|
||
|
||
// main 函数 - DLL必须的入口
|
||
func main() {
|
||
}
|