240 lines
5.1 KiB
Go
240 lines
5.1 KiB
Go
package accesslogs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||
"github.com/TeaOSLab/EdgeDNS/internal/remotelogs"
|
||
"gopkg.in/natefinch/lumberjack.v2"
|
||
)
|
||
|
||
const (
|
||
defaultDNSLogDir = "/var/log/edge/edge-dns"
|
||
envDNSLogDir = "EDGE_DNS_LOG_DIR"
|
||
)
|
||
|
||
var (
|
||
sharedDNSFileWriter *DNSFileWriter
|
||
sharedDNSFileWriterOnce sync.Once
|
||
)
|
||
|
||
// SharedDNSFileWriter 返回 DNS 本地日志写入器(单例).
|
||
func SharedDNSFileWriter() *DNSFileWriter {
|
||
sharedDNSFileWriterOnce.Do(func() {
|
||
sharedDNSFileWriter = NewDNSFileWriter()
|
||
})
|
||
return sharedDNSFileWriter
|
||
}
|
||
|
||
// DNSFileWriter 将 DNS 访问日志以 JSON Lines 写入本地文件,供 Fluent Bit 采集。
|
||
// 文件轮转由 lumberjack 内建完成。
|
||
type DNSFileWriter struct {
|
||
dir string
|
||
mu sync.Mutex
|
||
file *lumberjack.Logger
|
||
rotateConfig *serverconfigs.AccessLogRotateConfig
|
||
inited bool
|
||
}
|
||
|
||
// NewDNSFileWriter 创建 DNS 本地日志写入器.
|
||
func NewDNSFileWriter() *DNSFileWriter {
|
||
logDir := resolveDefaultDNSLogDir()
|
||
return &DNSFileWriter{
|
||
dir: logDir,
|
||
rotateConfig: serverconfigs.NewDefaultAccessLogRotateConfig(),
|
||
}
|
||
}
|
||
|
||
func resolveDefaultDNSLogDir() string {
|
||
logDir := strings.TrimSpace(os.Getenv(envDNSLogDir))
|
||
if logDir == "" {
|
||
return defaultDNSLogDir
|
||
}
|
||
return logDir
|
||
}
|
||
|
||
func resolveDNSDirFromPolicyPath(policyPath string) string {
|
||
policyPath = strings.TrimSpace(policyPath)
|
||
if policyPath == "" {
|
||
return ""
|
||
}
|
||
|
||
if strings.HasSuffix(policyPath, "/") || strings.HasSuffix(policyPath, "\\") {
|
||
return filepath.Clean(policyPath)
|
||
}
|
||
|
||
baseName := filepath.Base(policyPath)
|
||
if strings.Contains(baseName, ".") || strings.Contains(baseName, "${") {
|
||
return filepath.Clean(filepath.Dir(policyPath))
|
||
}
|
||
|
||
return filepath.Clean(policyPath)
|
||
}
|
||
|
||
// Dir 返回当前日志目录.
|
||
func (w *DNSFileWriter) Dir() string {
|
||
return w.dir
|
||
}
|
||
|
||
// SetDirByPolicyPath 使用公用日志策略 path 更新目录,空值时回退到 EDGE_DNS_LOG_DIR/default。
|
||
func (w *DNSFileWriter) SetDirByPolicyPath(policyPath string) {
|
||
dir := resolveDNSDirFromPolicyPath(policyPath)
|
||
w.SetDir(dir)
|
||
}
|
||
|
||
// SetDir 更新目录并重置文件句柄。
|
||
func (w *DNSFileWriter) SetDir(dir string) {
|
||
if strings.TrimSpace(dir) == "" {
|
||
dir = resolveDefaultDNSLogDir()
|
||
}
|
||
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if dir == w.dir {
|
||
return
|
||
}
|
||
|
||
if w.file != nil {
|
||
_ = w.file.Close()
|
||
w.file = nil
|
||
}
|
||
w.inited = false
|
||
w.dir = dir
|
||
}
|
||
|
||
// SetRotateConfig 更新日志轮转配置并重建 writer。
|
||
func (w *DNSFileWriter) SetRotateConfig(config *serverconfigs.AccessLogRotateConfig) {
|
||
normalized := config.Normalize()
|
||
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if equalDNSRotateConfig(w.rotateConfig, normalized) {
|
||
return
|
||
}
|
||
|
||
if w.file != nil {
|
||
_ = w.file.Close()
|
||
w.file = nil
|
||
}
|
||
w.inited = false
|
||
w.rotateConfig = normalized
|
||
}
|
||
|
||
// EnsureInit 在启动时预创建目录与 access.log.
|
||
func (w *DNSFileWriter) EnsureInit() error {
|
||
if w.dir == "" {
|
||
return nil
|
||
}
|
||
return w.init()
|
||
}
|
||
|
||
func (w *DNSFileWriter) init() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.inited && w.file != nil {
|
||
return nil
|
||
}
|
||
|
||
if w.dir == "" {
|
||
return nil
|
||
}
|
||
|
||
if err := os.MkdirAll(w.dir, 0755); err != nil {
|
||
remotelogs.Error("DNS_ACCESS_LOG_FILE", "mkdir log dir failed: "+err.Error())
|
||
return err
|
||
}
|
||
|
||
rotateConfig := w.rotateConfig.Normalize()
|
||
w.file = &lumberjack.Logger{
|
||
Filename: filepath.Join(w.dir, "access.log"),
|
||
MaxSize: rotateConfig.MaxSizeMB,
|
||
MaxBackups: rotateConfig.MaxBackups,
|
||
MaxAge: rotateConfig.MaxAgeDays,
|
||
Compress: *rotateConfig.Compress,
|
||
LocalTime: *rotateConfig.LocalTime,
|
||
}
|
||
|
||
w.inited = true
|
||
return nil
|
||
}
|
||
|
||
// WriteBatch 批量写入 DNS 访问日志.
|
||
func (w *DNSFileWriter) WriteBatch(logs []*pb.NSAccessLog, clusterId int64) {
|
||
if len(logs) == 0 || w.dir == "" {
|
||
return
|
||
}
|
||
if err := w.init(); err != nil {
|
||
return
|
||
}
|
||
|
||
w.mu.Lock()
|
||
file := w.file
|
||
w.mu.Unlock()
|
||
if file == nil {
|
||
return
|
||
}
|
||
|
||
for _, logItem := range logs {
|
||
ingestLog := FromNSAccessLog(logItem, clusterId)
|
||
if ingestLog == nil {
|
||
continue
|
||
}
|
||
line, err := json.Marshal(ingestLog)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
_, _ = file.Write(append(line, '\n'))
|
||
}
|
||
}
|
||
|
||
// Reopen 关闭并重建日志 writer(供兼容调用).
|
||
func (w *DNSFileWriter) Reopen() error {
|
||
w.mu.Lock()
|
||
if w.file != nil {
|
||
_ = w.file.Close()
|
||
w.file = nil
|
||
}
|
||
w.inited = false
|
||
w.mu.Unlock()
|
||
return w.init()
|
||
}
|
||
|
||
// Close 关闭日志文件.
|
||
func (w *DNSFileWriter) Close() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.file == nil {
|
||
return nil
|
||
}
|
||
|
||
err := w.file.Close()
|
||
w.file = nil
|
||
w.inited = false
|
||
if err != nil {
|
||
remotelogs.Error("DNS_ACCESS_LOG_FILE", fmt.Sprintf("close access.log failed: %v", err))
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func equalDNSRotateConfig(left *serverconfigs.AccessLogRotateConfig, right *serverconfigs.AccessLogRotateConfig) bool {
|
||
if left == nil || right == nil {
|
||
return left == right
|
||
}
|
||
return left.MaxSizeMB == right.MaxSizeMB &&
|
||
left.MaxBackups == right.MaxBackups &&
|
||
left.MaxAgeDays == right.MaxAgeDays &&
|
||
*left.Compress == *right.Compress &&
|
||
*left.LocalTime == *right.LocalTime
|
||
}
|