first commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// Package log реализует ASCII-логгер метрик с ротацией по дням.
|
||||
// Формат: "2026-06-25T10:00:00Z pbx-01 calls=42 load=0.85 idle_cpu=35 fail=0.5 score=73"
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pulse-lets-go/internal/models"
|
||||
)
|
||||
|
||||
// Logger — потокобезопасный логгер метрик в ASCII-файл с дневной ротацией.
|
||||
type Logger struct {
|
||||
mu sync.Mutex
|
||||
dir string
|
||||
file *os.File
|
||||
today string // YYYY-MM-DD последнего открытого файла
|
||||
}
|
||||
|
||||
// NewLogger создаёт логгер, записывающий в dataDir/.
|
||||
// Файлы именуются как metrics.YYYY-MM-DD.log.
|
||||
func NewLogger(dataDir string) (*Logger, error) {
|
||||
if err := os.MkdirAll(dataDir, 0750); err != nil {
|
||||
return nil, fmt.Errorf("создание data/: %w", err)
|
||||
}
|
||||
l := &Logger{dir: dataDir}
|
||||
if err := l.ensureFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// Log записывает снапшот метрик ноды в лог-файл.
|
||||
func (l *Logger) Log(ns *models.NodeState) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if err := l.ensureFile(); err != nil {
|
||||
return // тихая ошибка — не блокируем метрики из-за логгера
|
||||
}
|
||||
|
||||
line := l.formatLine(ns)
|
||||
l.file.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
// formatLine форматирует строку лога.
|
||||
func (l *Logger) formatLine(ns *models.NodeState) string {
|
||||
t := time.Unix(ns.TS, 0).UTC().Format(time.RFC3339)
|
||||
return fmt.Sprintf("%s %s calls=%d load=%.2f idle_cpu=%.1f fail=%.1f score=%.1f disabled=%t",
|
||||
t, ns.NodeID, ns.ActiveCalls, ns.LoadAvg, ns.IdleCPU, ns.CallFailureRate, ns.Score, ns.Disabled)
|
||||
}
|
||||
|
||||
// Close закрывает файл лога.
|
||||
func (l *Logger) Close() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.file != nil {
|
||||
return l.file.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureFile открывает файл на текущий день, создавая новый, если день сменился.
|
||||
// Вызывающий держит mu.
|
||||
func (l *Logger) ensureFile() error {
|
||||
today := time.Now().UTC().Format("2006-01-02")
|
||||
if l.file != nil && l.today == today {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l.file != nil {
|
||||
l.file.Close()
|
||||
}
|
||||
|
||||
path := filepath.Join(l.dir, "metrics."+today+".log")
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
|
||||
if err != nil {
|
||||
return fmt.Errorf("открытие лог-файла %s: %w", path, err)
|
||||
}
|
||||
|
||||
l.file = f
|
||||
l.today = today
|
||||
return nil
|
||||
}
|
||||
|
||||
// TrimPrefix обрезает "metrics." и ".log" из имени файла (для API истории).
|
||||
func TrimPrefix(filename string) string {
|
||||
return strings.TrimPrefix(strings.TrimSuffix(filename, ".log"), "metrics.")
|
||||
}
|
||||
Reference in New Issue
Block a user