Доработка. Журналирование

This commit is contained in:
Maxim
2026-06-09 11:00:20 +03:00
parent ac0e3944ab
commit 354156e557
7 changed files with 537 additions and 44 deletions

View File

@@ -0,0 +1,98 @@
package logger
import (
"encoding/binary"
"os"
"sync"
"time"
)
type DataLogger struct {
file *os.File
mu sync.Mutex
buffer []byte
bufferSize int
flushTick *time.Ticker
closeCh chan struct{}
}
type Sample struct {
Timestamp int64 // microseconds
Value byte
}
func NewDataLogger(basePath string) (*DataLogger, error) {
// путь будет формироваться через rotation
f, err := os.OpenFile(basePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
d := &DataLogger{
file: f,
buffer: make([]byte, 0, 64*1024), // 64KB буфер
bufferSize: 0,
flushTick: time.NewTicker(1 * time.Second),
closeCh: make(chan struct{}),
}
go d.flushLoop()
return d, nil
}
func (d *DataLogger) Write(s Sample) {
d.mu.Lock()
defer d.mu.Unlock()
// Формат: [timestamp uint64][value byte]
tsBuf := make([]byte, 8)
binary.LittleEndian.PutUint64(tsBuf, uint64(s.Timestamp))
d.buffer = append(d.buffer, tsBuf...)
d.buffer = append(d.buffer, s.Value)
d.bufferSize += 9
// Если буфер переполнен - сбрасываем немедленно
if d.bufferSize >= 64*1024 {
d.flush()
}
}
func (d *DataLogger) flush() {
if d.bufferSize == 0 {
return
}
_, err := d.file.Write(d.buffer[:d.bufferSize])
if err != nil {
// тут должен быть event
return
}
d.file.Sync() // важно для durability
d.buffer = d.buffer[:0]
d.bufferSize = 0
}
func (d *DataLogger) flushLoop() {
for {
select {
case <-d.flushTick.C:
d.mu.Lock()
d.flush()
d.mu.Unlock()
case <-d.closeCh:
d.mu.Lock()
d.flush()
d.mu.Unlock()
return
}
}
}
func (d *DataLogger) Close() error {
close(d.closeCh)
d.flushTick.Stop()
return d.file.Close()
}

View File

@@ -0,0 +1,78 @@
package logger
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
type EventLogger struct {
file *os.File
humanFile *os.File
mu sync.Mutex
}
func NewEventLogger(path string) (*EventLogger, error) {
// Создаём директорию для логов
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
// JSON файл для событий
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
// Human-readable файл для событий
humanPath := filepath.Join(dir, "events_human.log")
humanF, err := os.OpenFile(humanPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
f.Close()
return nil, err
}
return &EventLogger{
file: f,
humanFile: humanF,
}, nil
}
func (l *EventLogger) Event(name string) {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
// JSON формат (для машин)
ev := struct {
Timestamp int64 `json:"ts"`
Time string `json:"time"`
Event string `json:"event"`
}{
Timestamp: now.Unix(),
Time: now.Format("2006-01-02 15:04:05"),
Event: name,
}
data, _ := json.Marshal(ev)
data = append(data, '\n')
l.file.Write(data)
// Human-readable формат
humanLine := fmt.Sprintf("[%s] EVENT: %s\n", now.Format("2006-01-02 15:04:05.000"), name)
l.humanFile.WriteString(humanLine)
l.file.Sync()
l.humanFile.Sync()
}
func (l *EventLogger) Close() error {
l.file.Close()
l.humanFile.Close()
return nil
}

View File

@@ -0,0 +1,36 @@
package logger
import (
"os"
"sync"
)
type HumanLogger struct {
file *os.File
mu sync.Mutex
}
func NewHumanLogger(path string) (*HumanLogger, error) {
if err := os.MkdirAll("logs", 0755); err != nil {
return nil, err
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
return &HumanLogger{file: f}, nil
}
func (l *HumanLogger) Write(data string) {
l.mu.Lock()
defer l.mu.Unlock()
l.file.WriteString(data)
l.file.Sync()
}
func (l *HumanLogger) Close() error {
return l.file.Close()
}

View File

@@ -0,0 +1,42 @@
package logger
import (
"sync"
"time"
)
type Monitor struct {
eventLogger *EventLogger
lastWrite time.Time
mu sync.RWMutex
}
func NewMonitor(eventLogger *EventLogger) *Monitor {
m := &Monitor{
eventLogger: eventLogger,
lastWrite: time.Now(),
}
go m.watchdogLoop()
return m
}
func (m *Monitor) RecordWrite() {
m.mu.Lock()
m.lastWrite = time.Now()
m.mu.Unlock()
}
func (m *Monitor) watchdogLoop() {
ticker := time.NewTicker(1 * time.Minute)
for range ticker.C {
m.mu.RLock()
silence := time.Since(m.lastWrite)
m.mu.RUnlock()
if silence > 10*time.Minute {
m.eventLogger.Event("ТИШИНА_10МИН")
} else if silence > 5*time.Minute {
m.eventLogger.Event("ТИШИНА_5МИН")
}
}
}

View File

@@ -0,0 +1,98 @@
package logger
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
type RotatingLogger struct {
dataLogger *DataLogger
currentHour int
baseDir string
mu sync.Mutex
eventLogger *EventLogger
}
func NewRotatingLogger(baseDir string, eventLogger *EventLogger) (*RotatingLogger, error) {
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, err
}
r := &RotatingLogger{
baseDir: baseDir,
eventLogger: eventLogger,
}
if err := r.rotate(); err != nil {
return nil, err
}
go r.rotationLoop()
return r, nil
}
func (r *RotatingLogger) getFilename(hour int) string {
now := time.Now()
return filepath.Join(r.baseDir, fmt.Sprintf("gpio-%04d-%02d-%02d-%02d.bin",
now.Year(), now.Month(), now.Day(), hour))
}
func (r *RotatingLogger) rotate() error {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
newHour := now.Hour()
// Если уже правильный час и логгер существует - ок
if r.dataLogger != nil && r.currentHour == newHour {
return nil
}
// Закрываем старый
if r.dataLogger != nil {
r.dataLogger.Close()
}
// Открываем новый
filename := r.getFilename(newHour)
dataLogger, err := NewDataLogger(filename)
if err != nil {
r.eventLogger.Event("ROTATION_FAILED")
return err
}
r.dataLogger = dataLogger
r.currentHour = newHour
r.eventLogger.Event("ROTATION_COMPLETE")
return nil
}
func (r *RotatingLogger) Write(s Sample) {
r.mu.Lock()
logger := r.dataLogger
r.mu.Unlock()
if logger != nil {
logger.Write(s)
}
}
func (r *RotatingLogger) rotationLoop() {
ticker := time.NewTicker(1 * time.Minute)
for range ticker.C {
r.rotate()
}
}
func (r *RotatingLogger) Close() error {
r.mu.Lock()
defer r.mu.Unlock()
if r.dataLogger != nil {
return r.dataLogger.Close()
}
return nil
}