2026-06-25 14:27:41 +07:00

94 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.")
}