first commit

This commit is contained in:
Maksim Totmin
2026-06-25 14:27:41 +07:00
commit 44f5fa88e5
44 changed files with 8664 additions and 0 deletions
+93
View File
@@ -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.")
}