105 lines
3.4 KiB
Go
105 lines
3.4 KiB
Go
package events
|
||
|
||
import (
|
||
"strings"
|
||
"sync"
|
||
)
|
||
|
||
// Канонические уровни записей лога (фильтр панели «Логи», см. ui.LogLevels).
|
||
const (
|
||
LevelError = "error"
|
||
LevelWarning = "warning"
|
||
LevelInfo = "info"
|
||
LevelDebug = "debug"
|
||
)
|
||
|
||
// LogLine — строка лога для панели «Логи».
|
||
type LogLine struct {
|
||
Level string
|
||
Text string
|
||
}
|
||
|
||
func (LogLine) _event() {}
|
||
|
||
// markers проверяет наличие любого из маркеров в уже приведённой к нижнему
|
||
// регистру строке.
|
||
func markers(s string, anyOf ...string) bool {
|
||
for _, m := range anyOf {
|
||
if strings.Contains(s, m) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ClassifyLevel определяет уровень строки лога по её тексту.
|
||
//
|
||
// Стандартный log.Printf не несёт уровня, поэтому применяются эвристики по
|
||
// маркерам в тексте (в приоритете debug → warning → error): "debug"/"trace" —
|
||
// отладка; "warn"/"предупреж" — предупреждение; "error"/"ошибк"/"failed"/
|
||
// "fatal"/"panic" — ошибка. Остальные строки — информационные.
|
||
func ClassifyLevel(line string) string {
|
||
s := strings.ToLower(line)
|
||
switch {
|
||
case markers(s, "debug", "trace"):
|
||
return LevelDebug
|
||
case markers(s, "warn", "предупреж"):
|
||
return LevelWarning
|
||
case markers(s, "error", "ошибк", "failed", "failure", "fatal", "panic"):
|
||
return LevelError
|
||
default:
|
||
return LevelInfo
|
||
}
|
||
}
|
||
|
||
// LogBus — тип-обёртка над *Bus для логов.
|
||
//
|
||
// Логи идут отдельной шиной, чтобы большие объёмы текста не блокировали
|
||
// доменные события и наоборот.
|
||
type LogBus struct {
|
||
*Bus
|
||
}
|
||
|
||
// NewLogBus создаёт шину логов с буфером на подписчика.
|
||
func NewLogBus(bufSize int) *LogBus {
|
||
return &LogBus{Bus: New(bufSize)}
|
||
}
|
||
|
||
// LogWriter — io.Writer, который публикует каждую строку лога как LogLine.
|
||
// Предполагается использование через log.SetOutput в связке, чтобы всё
|
||
// логирование приложения попадало и в панель «Логи».
|
||
type LogWriter struct {
|
||
bus *Bus
|
||
mu sync.Mutex // защищает остаток частичной строки
|
||
buf strings.Builder
|
||
}
|
||
|
||
// NewLogWriter создаёт LogWriter, публикующий строки лога как события
|
||
// LogLine; уровень каждой строки определяется эвристикой ClassifyLevel.
|
||
func NewLogWriter(bus *LogBus) *LogWriter {
|
||
return &LogWriter{bus: bus.Bus}
|
||
}
|
||
|
||
// Write реализует io.Writer. Данные разрезаются по переводам строки:
|
||
// каждая законченная строка публикуется отдельным событием.
|
||
func (w *LogWriter) Write(p []byte) (int, error) {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
w.buf.Write(p)
|
||
data := w.buf.String()
|
||
for {
|
||
idx := strings.IndexByte(data, '\n')
|
||
if idx < 0 {
|
||
break
|
||
}
|
||
line := strings.TrimSuffix(data[:idx], "\r")
|
||
data = data[idx+1:]
|
||
if line != "" {
|
||
w.bus.Publish(LogLine{Level: ClassifyLevel(line), Text: line})
|
||
}
|
||
}
|
||
w.buf.Reset()
|
||
w.buf.WriteString(data)
|
||
return len(p), nil
|
||
} |