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 }