116 lines
2.5 KiB
Go
116 lines
2.5 KiB
Go
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
|
|
}
|