94 lines
2.7 KiB
Go
94 lines
2.7 KiB
Go
// 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.")
|
||
}
|