Files
gotail/backend/cmd/server/main.go

209 lines
5.8 KiB
Go

package main
import (
"context"
"embed"
"flag"
"fmt"
"io/fs"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/seu-usuario/go-react-web-tail/internal/broker"
"github.com/seu-usuario/go-react-web-tail/internal/config"
"github.com/seu-usuario/go-react-web-tail/internal/handlers"
"github.com/seu-usuario/go-react-web-tail/internal/middleware"
"github.com/seu-usuario/go-react-web-tail/internal/tail"
)
//go:embed dist
var staticFiles embed.FS
var version = "2.0.0"
func main() {
if err := run(); err != nil {
log.Fatal().Err(err).Msg("Application failed")
}
}
func run() error {
// Carregar configuração
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// Configurar logging
setupLogging(cfg.Logging)
log.Info().
Str("version", version).
Str("port", cfg.Server.Port).
Bool("auth_enabled", cfg.Auth.Enabled).
Bool("tls_enabled", cfg.Server.TLSEnabled).
Bool("rate_limit_enabled", cfg.Security.RateLimitEnabled).
Msg("Starting Web Tail Pro")
// Obter arquivos de log dos argumentos
logFiles := flag.Args()
if len(logFiles) < 1 {
fmt.Println("Uso: web-tail-pro [opções] <arquivo1.log> <arquivo2.log> ...")
fmt.Println("\nOpções:")
flag.PrintDefaults()
fmt.Println("\nVariáveis de Ambiente:")
fmt.Println(" PORT - Porta do servidor (padrão: :8080)")
fmt.Println(" USERNAME - Nome de usuário (padrão: admin)")
fmt.Println(" PASSWORD - Senha de autenticação")
fmt.Println(" LOG_LEVEL - Nível de log: debug, info, warn, error (padrão: info)")
fmt.Println(" LOG_FORMAT - Formato: json, console (padrão: console)")
fmt.Println(" RATE_LIMIT_ENABLED - Habilitar rate limiting (padrão: true)")
fmt.Println(" RATE_LIMIT_RPS - Requisições por segundo (padrão: 100)")
fmt.Println(" CORS_ORIGINS - Origens CORS permitidas, separadas por vírgula")
fmt.Println(" TLS_ENABLED - Habilitar TLS (padrão: false)")
fmt.Println(" TLS_CERT_FILE - Arquivo de certificado TLS")
fmt.Println(" TLS_KEY_FILE - Arquivo de chave TLS")
os.Exit(1)
}
// Criar context para graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Inicializar broker
brk := broker.New(ctx)
// Inicializar tailer
tailer := tail.New(ctx, logFiles, brk.Broadcast)
if err := tailer.Start(); err != nil {
return fmt.Errorf("failed to start tailer: %w", err)
}
// Configurar servidor HTTP
server, err := setupServer(cfg, brk, logFiles)
if err != nil {
return fmt.Errorf("failed to setup server: %w", err)
}
// Iniciar servidor em goroutine
serverErrors := make(chan error, 1)
go func() {
log.Info().Str("address", cfg.Server.Port).Msg("HTTP server listening")
if cfg.Server.TLSEnabled {
serverErrors <- server.ListenAndServeTLS(cfg.Server.TLSCertFile, cfg.Server.TLSKeyFile)
} else {
serverErrors <- server.ListenAndServe()
}
}()
// Aguardar sinal de shutdown
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-serverErrors:
return fmt.Errorf("server error: %w", err)
case sig := <-shutdown:
log.Info().Str("signal", sig.String()).Msg("Shutdown signal received")
// Graceful shutdown
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout)
defer shutdownCancel()
log.Info().Msg("Shutting down HTTP server...")
if err := server.Shutdown(shutdownCtx); err != nil {
log.Error().Err(err).Msg("HTTP server shutdown error")
}
log.Info().Msg("Stopping tailer...")
if err := tailer.Shutdown(); err != nil {
log.Error().Err(err).Msg("Tailer shutdown error")
}
log.Info().Msg("Stopping broker...")
brk.Shutdown()
log.Info().Msg("Shutdown complete")
}
return nil
}
func setupLogging(cfg config.LoggingConfig) {
// Configurar nível de log
level, err := zerolog.ParseLevel(cfg.Level)
if err != nil {
level = zerolog.InfoLevel
}
zerolog.SetGlobalLevel(level)
// Configurar formato
if cfg.Format == "console" {
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: time.RFC3339,
})
}
}
func setupServer(cfg *config.Config, brk *broker.Broker, logFiles []string) (*http.Server, error) {
// Configurar handlers
h := handlers.New(brk, logFiles)
// Configurar arquivos estáticos
distFS, err := fs.Sub(staticFiles, "dist")
if err != nil {
return nil, fmt.Errorf("failed to create sub-filesystem: %w", err)
}
// Configurar rotas
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.FS(distFS)))
mux.HandleFunc("/logs", h.HandleSSE)
mux.HandleFunc("/api/files", h.HandleFiles)
mux.HandleFunc("/api/last", h.HandleLastLines)
mux.HandleFunc("/health", h.HandleHealth)
mux.HandleFunc("/metrics", h.HandleMetrics)
// Aplicar cadeia de middleware
var handler http.Handler = mux
// Security headers
handler = middleware.SecurityHeadersMiddleware(handler)
// CORS
corsMiddleware := middleware.NewCORSMiddleware(cfg.Security.CORSOrigins)
handler = corsMiddleware.Handler(handler)
// Rate limiting
if cfg.Security.RateLimitEnabled {
rateLimitMiddleware := middleware.NewRateLimitMiddleware(cfg.Security.RateLimitRPS)
handler = rateLimitMiddleware.Handler(handler)
log.Info().Int("rps", cfg.Security.RateLimitRPS).Msg("Rate limiting enabled")
}
// Authentication
authMiddleware := middleware.NewAuthMiddleware(cfg.Auth.Enabled, cfg.Auth.Username, cfg.Auth.Password)
handler = authMiddleware.Handler(handler)
// Logging
handler = middleware.LoggingMiddleware(handler)
// Criar servidor
server := &http.Server{
Addr: cfg.Server.Port,
Handler: handler,
ReadTimeout: cfg.Server.ReadTimeout,
WriteTimeout: cfg.Server.WriteTimeout,
}
return server, nil
}