package tail import ( "context" "os" "sync" "github.com/nxadm/tail" "github.com/rs/zerolog/log" "github.com/seu-usuario/go-react-web-tail/internal/models" ) // Tailer gerencia o monitoramento de múltiplos arquivos de log type Tailer struct { files []string tails []*tail.Tail mu sync.Mutex ctx context.Context cancel context.CancelFunc onEntry func(models.LogEntry) } // New cria uma nova instância do Tailer func New(ctx context.Context, files []string, onEntry func(models.LogEntry)) *Tailer { ctx, cancel := context.WithCancel(ctx) return &Tailer{ files: files, tails: make([]*tail.Tail, 0, len(files)), ctx: ctx, cancel: cancel, onEntry: onEntry, } } // Start inicia o monitoramento de todos os arquivos func (t *Tailer) Start() error { log.Info().Int("file_count", len(t.files)).Msg("Starting tailer") for _, filename := range t.files { if err := t.startTailing(filename); err != nil { log.Error().Err(err).Str("file", filename).Msg("Failed to start tailing file") continue } } return nil } // startTailing inicia o monitoramento de um arquivo específico func (t *Tailer) startTailing(filename string) error { config := tail.Config{ Follow: true, ReOpen: true, Location: &tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}, Logger: tail.DiscardingLogger, } tailFile, err := tail.TailFile(filename, config) if err != nil { return err } t.mu.Lock() t.tails = append(t.tails, tailFile) t.mu.Unlock() log.Info().Str("file", filename).Msg("Started tailing file") go t.processTail(tailFile, filename) return nil } // processTail processa as linhas de um arquivo func (t *Tailer) processTail(tailFile *tail.Tail, filename string) { for { select { case <-t.ctx.Done(): log.Debug().Str("file", filename).Msg("Stopping tail processing") return case line, ok := <-tailFile.Lines: if !ok { log.Warn().Str("file", filename).Msg("Tail channel closed") return } if line.Err != nil { log.Error().Err(line.Err).Str("file", filename).Msg("Error reading line") continue } entry := models.NewLogEntry(filename, line.Text) t.onEntry(entry) } } } // Shutdown para o monitoramento de todos os arquivos func (t *Tailer) Shutdown() error { log.Info().Msg("Shutting down tailer") t.cancel() t.mu.Lock() defer t.mu.Unlock() for _, tailFile := range t.tails { if err := tailFile.Stop(); err != nil { log.Error().Err(err).Msg("Error stopping tail") } } log.Info().Msg("All tail operations stopped") return nil }