Доработка. Журналирование
This commit is contained in:
146
internal/pipe/reader.go
Normal file
146
internal/pipe/reader.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gpio-monitor/internal/adapter"
|
||||
"gpio-monitor/internal/logger"
|
||||
)
|
||||
|
||||
type PipeReader struct {
|
||||
buf *adapter.RingBuffer
|
||||
dataLogger *logger.RotatingLogger
|
||||
humanLogger *logger.HumanLogger
|
||||
monitor *logger.Monitor
|
||||
eventLog *logger.EventLogger
|
||||
lastAlert map[byte]time.Time // для предотвращения спама алертов
|
||||
}
|
||||
|
||||
func NewPipeReader(
|
||||
buf *adapter.RingBuffer,
|
||||
dataLogger *logger.RotatingLogger,
|
||||
humanLogger *logger.HumanLogger,
|
||||
monitor *logger.Monitor,
|
||||
eventLog *logger.EventLogger,
|
||||
) *PipeReader {
|
||||
return &PipeReader{
|
||||
buf: buf,
|
||||
dataLogger: dataLogger,
|
||||
humanLogger: humanLogger,
|
||||
monitor: monitor,
|
||||
eventLog: eventLog,
|
||||
lastAlert: make(map[byte]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PipeReader) Start(ctx context.Context, pipePath string) {
|
||||
go func() {
|
||||
buffer := make([]byte, 4096)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// Проверяем существует ли pipe
|
||||
if _, err := os.Stat(pipePath); os.IsNotExist(err) {
|
||||
if pr.eventLog != nil {
|
||||
pr.eventLog.Event("PIPE_НЕ_НАЙДЕН")
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(pipePath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
if pr.eventLog != nil {
|
||||
pr.eventLog.Event("ОШИБКА_ОТКРЫТИЯ_PIPE")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if pr.eventLog != nil {
|
||||
pr.eventLog.Event("PIPE_ПОДКЛЮЧЕН")
|
||||
}
|
||||
|
||||
for {
|
||||
n, err := f.Read(buffer)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
if pr.eventLog != nil {
|
||||
pr.eventLog.Event("PIPE_ОТКЛЮЧЕН")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Обрабатываем каждый байт
|
||||
for i := 0; i < n; i++ {
|
||||
b := buffer[i]
|
||||
now := time.Now()
|
||||
|
||||
// 1. В RAM для UI
|
||||
pr.buf.Write(b)
|
||||
|
||||
// 2. На диск для архива (бинарный)
|
||||
if pr.dataLogger != nil {
|
||||
pr.dataLogger.Write(logger.Sample{
|
||||
Timestamp: now.UnixMicro(),
|
||||
Value: b,
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Human-readable лог (только когда есть данные)
|
||||
if pr.humanLogger != nil {
|
||||
humanLine := fmt.Sprintf("[%s] Значение GPIO: %d (0x%02X)\n",
|
||||
now.Format("2006-01-02 15:04:05.000"),
|
||||
b, b)
|
||||
pr.humanLogger.Write(humanLine)
|
||||
}
|
||||
|
||||
// 4. Алерт если значение > 10
|
||||
if b > 10 {
|
||||
pr.handleAlert(b, now)
|
||||
}
|
||||
|
||||
// 5. Обновляем watchdog
|
||||
if pr.monitor != nil {
|
||||
pr.monitor.RecordWrite()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Пауза перед переподключением
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (pr *PipeReader) handleAlert(value byte, timestamp time.Time) {
|
||||
// Anti-spam: не чаще 1 алерта в секунду для одного значения
|
||||
if last, exists := pr.lastAlert[value]; exists {
|
||||
if timestamp.Sub(last) < 1*time.Second {
|
||||
return
|
||||
}
|
||||
}
|
||||
pr.lastAlert[value] = timestamp
|
||||
|
||||
// 1. В event log (JSON)
|
||||
if pr.eventLog != nil {
|
||||
pr.eventLog.Event(fmt.Sprintf("ВЫСОКОЕ_ЗНАЧЕНИЕ_%d", value))
|
||||
}
|
||||
|
||||
// 2. В human-readable лог с предупреждением (только на русском)
|
||||
if pr.humanLogger != nil {
|
||||
alertLine := fmt.Sprintf("[%s] ВНИМАНИЕ: Обнаружено высокое значение! GPIO = %d (>10)\n",
|
||||
timestamp.Format("2006-01-02 15:04:05.000"), value)
|
||||
pr.humanLogger.Write(alertLine)
|
||||
}
|
||||
|
||||
// 3. В консоль больше НЕ пишем (всё уже в логах)
|
||||
}
|
||||
Reference in New Issue
Block a user