Files

167 lines
3.8 KiB
Go

package broker
import (
"context"
"sync"
"time"
"github.com/rs/zerolog/log"
"github.com/seu-usuario/go-react-web-tail/internal/models"
)
// Broker gerencia clientes SSE e broadcasting de logs
type Broker struct {
clients map[chan models.LogEntry]struct{}
mu sync.RWMutex
register chan chan models.LogEntry
unregister chan chan models.LogEntry
broadcast chan models.LogEntry
ctx context.Context
cancel context.CancelFunc
// Métricas
totalBroadcasts uint64
activeClients int
}
// New cria uma nova instância do Broker
func New(ctx context.Context) *Broker {
ctx, cancel := context.WithCancel(ctx)
b := &Broker{
clients: make(map[chan models.LogEntry]struct{}),
register: make(chan chan models.LogEntry),
unregister: make(chan chan models.LogEntry),
broadcast: make(chan models.LogEntry, 1000),
ctx: ctx,
cancel: cancel,
}
go b.run()
return b
}
// run é o loop principal do broker
func (b *Broker) run() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
log.Info().Msg("Broker started")
for {
select {
case <-b.ctx.Done():
log.Info().Msg("Broker shutting down")
b.closeAllClients()
return
case client := <-b.register:
b.addClient(client)
case client := <-b.unregister:
b.removeClient(client)
case entry := <-b.broadcast:
b.broadcastToClients(entry)
case <-ticker.C:
b.logMetrics()
}
}
}
// Subscribe registra um novo cliente para receber logs
func (b *Broker) Subscribe() chan models.LogEntry {
ch := make(chan models.LogEntry, 100)
b.register <- ch
return ch
}
// Unsubscribe remove um cliente
func (b *Broker) Unsubscribe(ch chan models.LogEntry) {
b.unregister <- ch
}
// Broadcast envia uma entrada de log para todos os clientes
func (b *Broker) Broadcast(entry models.LogEntry) {
select {
case b.broadcast <- entry:
case <-b.ctx.Done():
default:
log.Warn().Msg("Broadcast channel full, dropping message")
}
}
// Shutdown encerra o broker graciosamente
func (b *Broker) Shutdown() {
log.Info().Msg("Broker shutdown requested")
b.cancel()
}
// addClient adiciona um novo cliente
func (b *Broker) addClient(client chan models.LogEntry) {
b.mu.Lock()
defer b.mu.Unlock()
b.clients[client] = struct{}{}
b.activeClients = len(b.clients)
log.Info().Int("active_clients", b.activeClients).Msg("Client subscribed")
}
// removeClient remove um cliente
func (b *Broker) removeClient(client chan models.LogEntry) {
b.mu.Lock()
defer b.mu.Unlock()
if _, ok := b.clients[client]; ok {
delete(b.clients, client)
close(client)
b.activeClients = len(b.clients)
log.Info().Int("active_clients", b.activeClients).Msg("Client unsubscribed")
}
}
// broadcastToClients envia uma entrada para todos os clientes conectados
func (b *Broker) broadcastToClients(entry models.LogEntry) {
b.mu.RLock()
defer b.mu.RUnlock()
b.totalBroadcasts++
for clientCh := range b.clients {
select {
case clientCh <- entry:
// Enviado com sucesso
default:
// Cliente está lento, remover
go b.Unsubscribe(clientCh)
log.Warn().Msg("Removed slow client")
}
}
}
// closeAllClients fecha todos os canais de clientes
func (b *Broker) closeAllClients() {
b.mu.Lock()
defer b.mu.Unlock()
for client := range b.clients {
close(client)
}
b.clients = make(map[chan models.LogEntry]struct{})
log.Info().Msg("All clients disconnected")
}
// logMetrics registra métricas do broker
func (b *Broker) logMetrics() {
b.mu.RLock()
defer b.mu.RUnlock()
log.Debug().
Int("active_clients", b.activeClients).
Uint64("total_broadcasts", b.totalBroadcasts).
Int("broadcast_buffer", len(b.broadcast)).
Msg("Broker metrics")
}
// GetMetrics retorna métricas atuais do broker
func (b *Broker) GetMetrics() (activeClients int, totalBroadcasts uint64) {
b.mu.RLock()
defer b.mu.RUnlock()
return b.activeClients, b.totalBroadcasts
}