feat: 日志中心 + GRE协议支持 + 流量监控 + 部署脚本
- 新增日志中心功能 (api/logapi.go + Logs.vue) - 新增GRE协议服务支持 (service/gre/) - 新增流量监控组件 (TrafficChart.vue) - 新增L2TP/IPSec测试用例 - 新增日志轮转服务 (logrotate.go) - 新增部署脚本 (scripts/install.sh, uninstall.sh, service文件) - 更新i18n国际化文件 - 优化后端API和前端路由
This commit is contained in:
1 parent
7de1f70905
commit
a14d9e62e6
25 files changed
+2285
-25
No files matched your search
@@ -0,0 +1,148 @@
|
||||
// Package service 提供日志轮转工具
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RotateConfig 配置日志轮转参数
|
||||
type RotateConfig struct {
|
||||
MaxSize int64 // 单个日志文件最大字节数
|
||||
MaxFiles int // 最多保留的轮转文件数
|
||||
}
|
||||
|
||||
// DefaultRotateConfig 从环境变量读取轮转配置
|
||||
func DefaultRotateConfig(envPrefix string, defaultMaxSize int64, defaultMaxFiles int) RotateConfig {
|
||||
cfg := RotateConfig{
|
||||
MaxSize: defaultMaxSize,
|
||||
MaxFiles: defaultMaxFiles,
|
||||
}
|
||||
if v := os.Getenv(envPrefix + "_MAX_SIZE"); v != "" {
|
||||
if n, err := parseSize(v); err == nil && n > 0 {
|
||||
cfg.MaxSize = n
|
||||
}
|
||||
}
|
||||
if v := os.Getenv(envPrefix + "_MAX_FILES"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
||||
cfg.MaxFiles = n
|
||||
}
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// parseSize 解析带单位的字节数(如 "50MB", "20M", "1048576")
|
||||
func parseSize(s string) (int64, error) {
|
||||
suffixes := map[string]int64{
|
||||
"KB": 1024,
|
||||
"MB": 1024 * 1024,
|
||||
"GB": 1024 * 1024 * 1024,
|
||||
"K": 1024,
|
||||
"M": 1024 * 1024,
|
||||
"G": 1024 * 1024 * 1024,
|
||||
}
|
||||
for suffix, mult := range suffixes {
|
||||
if len(s) > len(suffix) && s[len(s)-len(suffix):] == suffix {
|
||||
n, err := strconv.ParseInt(s[:len(s)-len(suffix)], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n * mult, nil
|
||||
}
|
||||
}
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
// RotateIfNeeded 检查日志文件大小并在需要时轮转
|
||||
func RotateIfNeeded(logPath string, cfg RotateConfig) error {
|
||||
info, err := os.Stat(logPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if info.Size() < cfg.MaxSize {
|
||||
return nil
|
||||
}
|
||||
return rotateFiles(logPath, cfg.MaxFiles)
|
||||
}
|
||||
|
||||
// rotateFiles 执行日志轮转:logPath -> logPath.1, logPath.N-1 -> delete
|
||||
func rotateFiles(logPath string, maxFiles int) error {
|
||||
// 删除最旧的文件
|
||||
oldest := fmt.Sprintf("%s.%d", logPath, maxFiles)
|
||||
os.Remove(oldest) // 忽略不存在的错误
|
||||
|
||||
// 逐个重命名:N-1 -> N, N-2 -> N-1, ... 1 -> 2
|
||||
for i := maxFiles - 1; i >= 1; i-- {
|
||||
src := fmt.Sprintf("%s.%d", logPath, i)
|
||||
dst := fmt.Sprintf("%s.%d", logPath, i+1)
|
||||
if _, err := os.Stat(src); err == nil {
|
||||
os.Rename(src, dst)
|
||||
}
|
||||
}
|
||||
// 当前文件 -> .1
|
||||
if _, err := os.Stat(logPath); err == nil {
|
||||
return os.Rename(logPath, logPath+".1")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RotatingWriter 是一个带轮转功能的 io.Writer
|
||||
type RotatingWriter struct {
|
||||
mu sync.Mutex
|
||||
path string
|
||||
cfg RotateConfig
|
||||
f *os.File
|
||||
}
|
||||
|
||||
// NewRotatingWriter 创建一个带轮转功能的写入器
|
||||
func NewRotatingWriter(path string, cfg RotateConfig) (*RotatingWriter, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rw := &RotatingWriter{path: path, cfg: cfg}
|
||||
if err := rw.open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
func (w *RotatingWriter) open() error {
|
||||
// 先尝试轮转
|
||||
RotateIfNeeded(w.path, w.cfg)
|
||||
f, err := os.OpenFile(w.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.f = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *RotatingWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
n, err := w.f.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
// 每次写入后检查大小(懒轮转)
|
||||
if info, e := w.f.Stat(); e == nil && info.Size() >= w.cfg.MaxSize {
|
||||
w.f.Close()
|
||||
w.open()
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *RotatingWriter) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.f != nil {
|
||||
return w.f.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user