Merge pull request #751 from samwafgo/feat_task_log

feat:add task log
This commit is contained in:
samwafgo
2026-04-13 14:45:45 +08:00
committed by GitHub
9 changed files with 657 additions and 10 deletions
+81
View File
@@ -1,10 +1,12 @@
package api
import (
"SamWaf/common/tasklog"
"SamWaf/global"
"SamWaf/model/common/response"
"SamWaf/model/request"
"errors"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -177,3 +179,82 @@ func (w *WafTaskApi) ManualExecuteApi(c *gin.Context) {
response.FailWithMessage("解析失败", c)
}
}
// GetTaskLogApi 获取任务日志
// @Summary 获取定时任务日志
// @Description 读取指定任务方法的日志内容,支持增量加载(通过 offset 参数)
// @Tags 定时任务管理
// @Accept json
// @Produce json
// @Param task_method query string true "任务方法名"
// @Param lines query int false "最大行数(offset=0时生效,默认200,最大1000)"
// @Param offset query int64 false "文件偏移量(0=从头读取,>0=增量读取)"
// @Success 200 {object} response.Response "获取成功"
// @Security ApiKeyAuth
// @Router /wafhost/task/log [get]
func (w *WafTaskApi) GetTaskLogApi(c *gin.Context) {
taskMethod := c.Query("task_method")
if taskMethod == "" {
response.FailWithMessage("task_method 不能为空", c)
return
}
linesStr := c.DefaultQuery("lines", "200")
lines, err := strconv.Atoi(linesStr)
if err != nil || lines <= 0 {
lines = 200
}
if lines > 1000 {
lines = 1000
}
offsetStr := c.DefaultQuery("offset", "0")
offset, err := strconv.ParseInt(offsetStr, 10, 64)
if err != nil || offset < 0 {
offset = 0
}
if tasklog.GlobalTaskLogManager == nil {
response.FailWithMessage("任务日志管理器未初始化", c)
return
}
result, err := tasklog.GlobalTaskLogManager.ReadLog(taskMethod, lines, offset)
if err != nil {
response.FailWithMessage("读取任务日志失败: "+err.Error(), c)
return
}
response.OkWithDetailed(result, "获取成功", c)
}
// ClearTaskLogApi 清空任务日志
// @Summary 清空定时任务日志
// @Description 清空指定任务方法的日志文件内容
// @Tags 定时任务管理
// @Accept json
// @Produce json
// @Param task_method query string true "任务方法名"
// @Success 200 {object} response.Response "清空成功"
// @Security ApiKeyAuth
// @Router /wafhost/task/log/clear [get]
func (w *WafTaskApi) ClearTaskLogApi(c *gin.Context) {
taskMethod := c.Query("task_method")
if taskMethod == "" {
response.FailWithMessage("task_method 不能为空", c)
return
}
if tasklog.GlobalTaskLogManager == nil {
response.FailWithMessage("任务日志管理器未初始化", c)
return
}
err := tasklog.GlobalTaskLogManager.ClearLog(taskMethod)
if err != nil {
response.FailWithMessage("清空任务日志失败: "+err.Error(), c)
return
}
response.OkWithMessage("清空成功", c)
}
+7
View File
@@ -3,6 +3,7 @@ package main
import (
"SamWaf/cache"
"SamWaf/common/gwebsocket"
"SamWaf/common/tasklog"
"SamWaf/common/zlog"
"SamWaf/enums"
"SamWaf/global"
@@ -51,6 +52,7 @@ import (
dlp "github.com/bytedance/godlp"
"github.com/kardianos/service"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gorm.io/gorm"
)
@@ -323,6 +325,11 @@ func (m *wafSystenService) run() {
//初始化一次系统参数信息
waftask.TaskLoadSetting(true)
// 初始化任务日志管理器(需在 TaskLoadSetting 后执行,以获取正确的 retainDays 配置)
taskLogDir := filepath.Join(utils.GetCurrentDir(), "logs", "task")
tasklog.InitGlobalTaskLogManager(taskLogDir, int(global.GCONFIG_TASK_LOG_RETAIN_DAYS))
// 将 TaskZapCore 挂到全局 zlog,使任务执行期间的 zlog 输出同步写入任务日志文件
zlog.AddCore(tasklog.NewTaskZapCore(zapcore.DebugLevel))
//启动通知相关程序
global.GNOTIFY_KAKFA_SERVICE = wafnotify.InitNotifyKafkaEngine(global.GCONFIG_RECORD_KAFKA_ENABLE, global.GCONFIG_RECORD_KAFKA_URL, global.GCONFIG_RECORD_KAFKA_TOPIC) //kafka
// 日志文件写入
+489
View File
@@ -0,0 +1,489 @@
package tasklog
import (
"crypto/md5"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
// ---- goroutine 上下文:goroutine ID -> task method ----
var goroutineTaskMap sync.Map // key: int64(goroutine ID), value: string(task method)
// goroutineID 解析当前 goroutine 的 ID(通过 runtime.Stack)
func goroutineID() int64 {
var buf [64]byte
n := runtime.Stack(buf[:], false)
s := strings.TrimPrefix(string(buf[:n]), "goroutine ")
idx := strings.IndexByte(s, ' ')
if idx < 0 {
return -1
}
id, _ := strconv.ParseInt(s[:idx], 10, 64)
return id
}
// SetCurrentTask 在当前 goroutine 上注册正在执行的任务方法名
func SetCurrentTask(taskMethod string) {
goroutineTaskMap.Store(goroutineID(), taskMethod)
}
// ClearCurrentTask 清除当前 goroutine 的任务上下文
func ClearCurrentTask() {
goroutineTaskMap.Delete(goroutineID())
}
// GetCurrentTask 获取当前 goroutine 关联的任务方法名(若无则返回 "", false)
func GetCurrentTask() (string, bool) {
v, ok := goroutineTaskMap.Load(goroutineID())
if !ok {
return "", false
}
return v.(string), true
}
// ---- 自定义 zap Core:将 zlog 输出路由到任务日志文件 ----
// TaskZapCore 是一个 zapcore.Core 实现
// 它在当前 goroutine 有任务上下文时,把日志同步写入对应的任务日志文件
type TaskZapCore struct {
minLevel zapcore.Level
}
// NewTaskZapCore 创建 TaskZapCore,minLevel 决定哪些级别的日志写入任务文件
func NewTaskZapCore(minLevel zapcore.Level) zapcore.Core {
return &TaskZapCore{minLevel: minLevel}
}
func (c *TaskZapCore) Enabled(level zapcore.Level) bool {
return level >= c.minLevel
}
func (c *TaskZapCore) With(_ []zapcore.Field) zapcore.Core {
return c
}
func (c *TaskZapCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if !c.Enabled(entry.Level) {
return ce
}
if GlobalTaskLogManager == nil {
return ce
}
if _, ok := GetCurrentTask(); !ok {
return ce
}
return ce.AddCore(entry, c)
}
func (c *TaskZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
if GlobalTaskLogManager == nil {
return nil
}
taskMethod, ok := GetCurrentTask()
if !ok {
return nil
}
level := strings.ToUpper(entry.Level.String())
msg := entry.Message
if len(fields) > 0 {
enc := zapcore.NewMapObjectEncoder()
for _, f := range fields {
f.AddTo(enc)
}
var extras []string
for k, v := range enc.Fields {
extras = append(extras, fmt.Sprintf("%s=%v", k, v))
}
if len(extras) > 0 {
msg = msg + " " + strings.Join(extras, " ")
}
}
GlobalTaskLogManager.Log(taskMethod, level, msg)
return nil
}
func (c *TaskZapCore) Sync() error {
return nil
}
// ---- TaskLogManager ----
// rateLimitState 记录某个任务的速率限制状态
type rateLimitState struct {
lastWriteTime time.Time
suppressedCount int64
}
// TaskLogManager 任务日志管理器,为每个任务方法维护独立的日志文件
type TaskLogManager struct {
logDir string
retainDays int // 全局默认保留天数
// writer 相关(受 mu 保护)
mu sync.RWMutex
writers map[string]*lumberjack.Logger
taskRetainDays map[string]int // md5(taskMethod) -> 覆盖保留天数(0 = 使用全局 retainDays)
// 速率限制相关(受各自 mutex 保护)
policyMu sync.RWMutex
policies map[string]time.Duration // taskMethod -> 最小写入间隔(0 = 不限制)
rateMu sync.Mutex
rateStates map[string]*rateLimitState // taskMethod -> 速率限制状态
}
// GlobalTaskLogManager 全局任务日志管理器单例
var GlobalTaskLogManager *TaskLogManager
// InitGlobalTaskLogManager 初始化全局任务日志管理器
func InitGlobalTaskLogManager(logDir string, retainDays int) {
if retainDays <= 0 {
retainDays = 30
}
GlobalTaskLogManager = &TaskLogManager{
logDir: logDir,
retainDays: retainDays,
writers: make(map[string]*lumberjack.Logger),
taskRetainDays: make(map[string]int),
policies: make(map[string]time.Duration),
rateStates: make(map[string]*rateLimitState),
}
_ = os.MkdirAll(logDir, 0755)
}
// methodMD5 计算任务方法名的 MD5,用作文件名
func methodMD5(taskMethod string) string {
h := md5.New()
_, _ = io.WriteString(h, taskMethod)
return fmt.Sprintf("%x", h.Sum(nil))
}
// GetLogFilePath 根据任务方法名返回其日志文件的完整路径
func (m *TaskLogManager) GetLogFilePath(taskMethod string) string {
return filepath.Join(m.logDir, methodMD5(taskMethod)+".log")
}
// getEffectiveRetainDays 获取任务实际使用的保留天数(优先使用任务级别覆盖值)
func (m *TaskLogManager) getEffectiveRetainDays(key string) int {
if days, ok := m.taskRetainDays[key]; ok && days > 0 {
return days
}
return m.retainDays
}
// getWriter 获取或创建对应任务方法的 lumberjack 写入器(内部加锁)
func (m *TaskLogManager) getWriter(taskMethod string) *lumberjack.Logger {
key := methodMD5(taskMethod)
m.mu.RLock()
w, ok := m.writers[key]
m.mu.RUnlock()
if ok {
return w
}
m.mu.Lock()
defer m.mu.Unlock()
if w, ok = m.writers[key]; ok {
return w
}
logFilePath := filepath.Join(m.logDir, key+".log")
w = &lumberjack.Logger{
Filename: logFilePath,
MaxSize: 50,
MaxBackups: 10,
MaxAge: m.getEffectiveRetainDays(key),
Compress: false,
}
m.writers[key] = w
return w
}
// SetTaskRetainDays 为指定任务设置独立的日志保留天数(覆盖全局配置)
// days <= 0 表示使用全局默认值
func (m *TaskLogManager) SetTaskRetainDays(taskMethod string, days int) {
if m == nil {
return
}
key := methodMD5(taskMethod)
m.mu.Lock()
defer m.mu.Unlock()
if days > 0 {
m.taskRetainDays[key] = days
} else {
delete(m.taskRetainDays, key)
}
// 如果 writer 已存在,同步更新其 MaxAge
if w, ok := m.writers[key]; ok {
w.MaxAge = m.getEffectiveRetainDays(key)
}
}
// SetTaskLogPolicy 为指定任务方法设置最小写入间隔(速率限制)
// minInterval == 0 表示不限制;> 0 时 INFO/DEBUG 按间隔压缩,ERROR/WARN 仍直写
func (m *TaskLogManager) SetTaskLogPolicy(taskMethod string, minInterval time.Duration) {
if m == nil {
return
}
m.policyMu.Lock()
m.policies[taskMethod] = minInterval
m.policyMu.Unlock()
}
// getPolicy 获取任务的最小写入间隔(0 = 不限)
func (m *TaskLogManager) getPolicy(taskMethod string) time.Duration {
m.policyMu.RLock()
d := m.policies[taskMethod]
m.policyMu.RUnlock()
return d
}
// Log 写入一条任务日志
// ERROR / WARN 始终直接写入;INFO / DEBUG 受速率限制策略约束
func (m *TaskLogManager) Log(taskMethod, level, message string) {
if m == nil {
return
}
now := time.Now()
w := m.getWriter(taskMethod)
isHighPriority := level == "ERROR" || level == "WARN"
minInterval := m.getPolicy(taskMethod)
if minInterval <= 0 || isHighPriority {
// 高优先级日志:先把积压的压缩摘要冲出来,再写本条
if isHighPriority {
m.flushSuppressed(taskMethod, w, now)
}
_, _ = w.Write([]byte(formatLogLine(now, level, taskMethod, message)))
return
}
// INFO / DEBUG 受速率限制
m.rateMu.Lock()
state, ok := m.rateStates[taskMethod]
if !ok {
state = &rateLimitState{}
m.rateStates[taskMethod] = state
}
if state.lastWriteTime.IsZero() || now.Sub(state.lastWriteTime) >= minInterval {
suppressed := state.suppressedCount
state.lastWriteTime = now
state.suppressedCount = 0
m.rateMu.Unlock()
if suppressed > 0 {
summary := formatLogLine(now, "INFO", taskMethod,
fmt.Sprintf("(已压缩 %d 条日志)", suppressed))
_, _ = w.Write([]byte(summary))
}
_, _ = w.Write([]byte(formatLogLine(now, level, taskMethod, message)))
} else {
state.suppressedCount++
m.rateMu.Unlock()
}
}
// flushSuppressed 在写高优先级日志前,把积压的压缩摘要冲出来
func (m *TaskLogManager) flushSuppressed(taskMethod string, w *lumberjack.Logger, now time.Time) {
m.rateMu.Lock()
state, ok := m.rateStates[taskMethod]
if !ok || state.suppressedCount == 0 {
m.rateMu.Unlock()
return
}
suppressed := state.suppressedCount
state.suppressedCount = 0
state.lastWriteTime = now
m.rateMu.Unlock()
_, _ = w.Write([]byte(formatLogLine(now, "INFO", taskMethod,
fmt.Sprintf("(已压缩 %d 条日志)", suppressed))))
}
// formatLogLine 格式化一行日志
func formatLogLine(t time.Time, level, taskMethod, message string) string {
return fmt.Sprintf("[%s] [%s] [%s] %s\n",
t.Format("2006-01-02 15:04:05"),
level,
taskMethod,
message,
)
}
// LogResult 是 ReadLog 的返回值
type LogResult struct {
Content string `json:"content"`
NewOffset int64 `json:"new_offset"`
LogFile string `json:"log_file"`
FileSize int64 `json:"file_size"`
}
// ReadLog 读取任务日志
// offset=0 从头读取;offset>0 增量读取新增内容
func (m *TaskLogManager) ReadLog(taskMethod string, lines int, offset int64) (*LogResult, error) {
if m == nil {
return &LogResult{}, nil
}
logFilePath := m.GetLogFilePath(taskMethod)
f, err := os.Open(logFilePath)
if err != nil {
if os.IsNotExist(err) {
return &LogResult{LogFile: logFilePath}, nil
}
return nil, fmt.Errorf("打开日志文件失败: %w", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("获取日志文件信息失败: %w", err)
}
fileSize := fi.Size()
if offset > fileSize {
offset = 0
}
if offset > 0 {
if offset == fileSize {
return &LogResult{NewOffset: fileSize, LogFile: logFilePath, FileSize: fileSize}, nil
}
_, err = f.Seek(offset, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("定位日志文件失败: %w", err)
}
buf := make([]byte, fileSize-offset)
n, err := f.Read(buf)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("读取日志文件失败: %w", err)
}
return &LogResult{
Content: string(buf[:n]),
NewOffset: offset + int64(n),
LogFile: logFilePath,
FileSize: fileSize,
}, nil
}
data, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("读取日志文件失败: %w", err)
}
content := string(data)
if lines > 0 {
content = tailLines(content, lines)
}
return &LogResult{
Content: content,
NewOffset: fileSize,
LogFile: logFilePath,
FileSize: fileSize,
}, nil
}
// tailLines 返回文本最后 n 行
func tailLines(text string, n int) string {
if n <= 0 {
return text
}
lines := strings.Split(text, "\n")
if len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
if len(lines) <= n {
return strings.Join(lines, "\n") + "\n"
}
return strings.Join(lines[len(lines)-n:], "\n") + "\n"
}
// ClearLog 清空指定任务的日志文件
func (m *TaskLogManager) ClearLog(taskMethod string) error {
if m == nil {
return nil
}
logFilePath := m.GetLogFilePath(taskMethod)
key := methodMD5(taskMethod)
m.mu.Lock()
if w, ok := m.writers[key]; ok {
_ = w.Close()
delete(m.writers, key)
}
m.mu.Unlock()
f, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("清空日志文件失败: %w", err)
}
return f.Close()
}
// CleanExpiredLogs 扫描日志目录,删除超过各任务保留策略的过期备份文件
func (m *TaskLogManager) CleanExpiredLogs() {
if m == nil {
return
}
// 使用全局 retainDays 作为兜底清理基准(单个任务的短保留由 lumberjack MaxAge 自行处理)
cutoff := time.Now().AddDate(0, 0, -m.retainDays)
entries, err := os.ReadDir(m.logDir)
if err != nil {
return
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasSuffix(name, ".log") &&
!strings.HasSuffix(name, ".gz") &&
!strings.Contains(name, ".log-") {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
_ = os.Remove(filepath.Join(m.logDir, name))
}
}
}
// UpdateRetainDays 动态更新全局默认保留天数,并刷新未被任务级别覆盖的 writer
func (m *TaskLogManager) UpdateRetainDays(days int) {
if m == nil || days <= 0 {
return
}
m.mu.Lock()
defer m.mu.Unlock()
m.retainDays = days
for key, w := range m.writers {
// 只更新没有任务级别覆盖的 writer
if _, hasOverride := m.taskRetainDays[key]; !hasOverride {
w.MaxAge = days
}
}
}
+20 -7
View File
@@ -19,6 +19,9 @@ import (
// zlog.Warn("hello", zap.String("name", "Kevin"), zap.Any("arbitraryObj", dummyObject))
var logger *zap.Logger
// baseCores 保存 InitZLog 创建的基础 cores,供 AddCore 重建时使用
var baseCores []zapcore.Core
// InitZLog 初始化zlog
func InitZLog(debugEnable bool, outputFormat string) {
encoderConfig := zap.NewProductionEncoderConfig()
@@ -35,22 +38,32 @@ func InitZLog(debugEnable bool, outputFormat string) {
fileWriteSyncer := getFileLogWriter()
if debugEnable == true {
core := zapcore.NewTee(
// 调试默认
baseCores = []zapcore.Core{
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel),
)
logger = zap.New(core)
}
} else {
core := zapcore.NewTee(
baseCores = []zapcore.Core{
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.InfoLevel),
zapcore.NewCore(encoder, fileWriteSyncer, zapcore.InfoLevel),
zapcore.NewCore(encoder, fileWriteSyncer, zapcore.ErrorLevel),
zapcore.NewCore(encoder, fileWriteSyncer, zapcore.FatalLevel),
)
logger = zap.New(core)
}
}
logger = zap.New(zapcore.NewTee(baseCores...))
}
// AddCore 在现有 logger 上追加一个额外的 zapcore.Core(通常在 zlog 初始化之后调用)
// 追加后原有的所有 baseCores 依然保留,新 core 并联写入
func AddCore(extra zapcore.Core) {
if logger == nil || extra == nil {
return
}
all := make([]zapcore.Core, 0, len(baseCores)+1)
all = append(all, baseCores...)
all = append(all, extra)
logger = zap.New(zapcore.NewTee(all...))
}
func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
+3
View File
@@ -73,6 +73,9 @@ var (
// 开放平台配置
GCONFIG_OPEN_PLATFORM_ENABLED int64 = 0 // 开放平台开关,默认关闭 有需要的开启 1启用 0禁用
// 任务日志配置
GCONFIG_TASK_LOG_RETAIN_DAYS int64 = 30 // 任务日志保留天数(默认 30 天)
// 日志文件写入配置 (额外输出,不影响SQLite存储)
GCONFIG_LOG_FILE_WRITE_ENABLE int64 = 0 // 日志文件写入开关 (0关闭 1开启)
GCONFIG_LOG_FILE_WRITE_PATH string = "logs/access.log" // 日志文件路径
+2
View File
@@ -17,4 +17,6 @@ func (receiver *WafTaskRouter) InitWafTaskRouter(group *gin.RouterGroup) {
router.POST("/api/v1/wafhost/task/edit", api.ModifyApi)
router.GET("/api/v1/wafhost/task/del", api.DelApi)
router.GET("/api/v1/wafhost/task/manual_exec", api.ManualExecuteApi)
router.GET("/api/v1/wafhost/task/log", api.GetTaskLogApi)
router.GET("/api/v1/wafhost/task/log/clear", api.ClearTaskLogApi)
}
+20 -2
View File
@@ -1,11 +1,13 @@
package waftask
import (
"SamWaf/common/tasklog"
"SamWaf/common/zlog"
"SamWaf/utils"
"fmt"
"runtime/debug"
"sync"
"time"
)
// TaskFunc 定义任务执行的函数类型
@@ -53,19 +55,35 @@ func (tr *TaskRegistry) ExecuteTask(taskName string) {
// 如果任务锁正在占用,输出提示
if !taskMutex.TryLock() { // 如果锁已经被占用
zlog.Error(fmt.Sprintf("%s 任务方法 '%s' 正在执行 跳过当前执行", innerName, taskName))
msg := fmt.Sprintf("%s 任务方法 '%s' 正在执行 跳过当前执行", innerName, taskName)
zlog.Error(msg)
tasklog.GlobalTaskLogManager.Log(taskName, "WARN", "任务正在执行中,跳过本次触发")
return
}
defer taskMutex.Unlock() // 确保任务完成后释放锁
// 注册当前 goroutine 的任务上下文,使 zlog 输出同步路由到任务日志文件
tasklog.SetCurrentTask(taskName)
defer tasklog.ClearCurrentTask()
// 捕获任务执行中的异常并打印详细信息
defer func() {
if err := recover(); err != nil {
// 处理异常,打印错误详情
errMsg := fmt.Sprintf("任务执行出错: %v", err)
zlog.Error(fmt.Sprintf("任务 '%s' 执行出错: %v 调试信息:%s", taskName, err, debug.Stack()))
debug.PrintStack()
tasklog.GlobalTaskLogManager.Log(taskName, "ERROR", errMsg)
}
}()
startTime := time.Now()
tasklog.GlobalTaskLogManager.Log(taskName, "INFO", "任务开始执行")
taskFunc()
elapsed := time.Since(startTime)
tasklog.GlobalTaskLogManager.Log(taskName, "INFO",
fmt.Sprintf("任务执行完成,耗时 %v", elapsed.Round(time.Millisecond)))
}()
} else {
zlog.Error(fmt.Sprintf("%s 任务方法 '%s' 未找到", innerName, taskName))
+25 -1
View File
@@ -1,14 +1,16 @@
package waftask
import (
"SamWaf/common/tasklog"
"SamWaf/common/zlog"
"SamWaf/enums"
"fmt"
"github.com/go-co-op/gocron"
"strconv"
"strings"
"sync"
"time"
"github.com/go-co-op/gocron"
)
// TaskScheduler 用于封装任务调度器
@@ -81,9 +83,31 @@ func (ts *TaskScheduler) ScheduleTask(unit string, interval int, at string, task
ts.taskTags[taskMethod] = jobTag
ts.mu.Unlock()
// 按任务执行频率设置日志策略(速率限制 + 保留天数)
applyTaskLogPolicy(taskMethod, unit, interval)
zlog.Debug(fmt.Sprintf("Task scheduled: %v every %d %s\n", job, interval, unit))
return nil
}
// applyTaskLogPolicy 根据任务执行频率,为其设置日志速率限制和保留策略:
// - 秒级任务:INFO/DEBUG 每 60s 最多一次;日志仅保留 1 天
// - 分钟级(间隔 ≤ 2 分钟):INFO/DEBUG 每 30s 最多一次;保留天数使用全局默认
// - 其他任务:不限制,保留天数使用全局默认
func applyTaskLogPolicy(taskMethod string, unit string, interval int) {
if tasklog.GlobalTaskLogManager == nil {
return
}
switch unit {
case enums.TASK_SECOND:
tasklog.GlobalTaskLogManager.SetTaskLogPolicy(taskMethod, 60*time.Second)
tasklog.GlobalTaskLogManager.SetTaskRetainDays(taskMethod, 1)
case enums.TASK_MIN:
if interval <= 2 {
tasklog.GlobalTaskLogManager.SetTaskLogPolicy(taskMethod, 30*time.Second)
}
}
}
func (ts *TaskScheduler) Start() {
ts.Scheduler.StartAsync()
}
+10
View File
@@ -1,6 +1,7 @@
package waftask
import (
"SamWaf/common/tasklog"
"SamWaf/common/zlog"
"SamWaf/global"
"SamWaf/iplocation"
@@ -173,6 +174,12 @@ func setConfigIntValue(name string, value int64, change int) {
syncLogFileWriterConfig()
case "open_platform_enabled":
global.GCONFIG_OPEN_PLATFORM_ENABLED = value
case "task_log_retain_days":
global.GCONFIG_TASK_LOG_RETAIN_DAYS = value
// 动态更新任务日志管理器的保留天数
if tasklog.GlobalTaskLogManager != nil {
tasklog.GlobalTaskLogManager.UpdateRetainDays(int(value))
}
default:
zlog.Warn("Unknown config item:", name)
}
@@ -437,4 +444,7 @@ func TaskLoadSetting(initLoad bool) {
// 开放平台配置
updateConfigIntItem(initLoad, "openplatform", "open_platform_enabled", global.GCONFIG_OPEN_PLATFORM_ENABLED, "开放平台开关(1启用 0禁用)", "options", "0|禁用,1|启用", configMap)
// 任务日志配置
updateConfigIntItem(initLoad, "task", "task_log_retain_days", global.GCONFIG_TASK_LOG_RETAIN_DAYS, "任务日志保留天数(默认30天)", "int", "", configMap)
}