209 lines
5.8 KiB
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
|
|
}
|