Refactor error handling and logging system
- Replace custom errors package with xerrors for structured error handling - Remove local logging wrapper and use git.antanst.com/antanst/logging - Add proper error codes and user messages in server responses - Improve connection handling with better error categorization - Update certificate path to use local certs/ directory - Add request size validation (1024 byte limit) - Remove panic-on-error configuration option - Enhance error logging with connection IDs and remote addresses
This commit is contained in:
115
main.go
115
main.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -13,46 +14,40 @@ import (
|
||||
"time"
|
||||
|
||||
"gemserve/config"
|
||||
"gemserve/errors"
|
||||
"gemserve/logging"
|
||||
"gemserve/server"
|
||||
"gemserve/uid"
|
||||
"github.com/rs/zerolog"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
logging "git.antanst.com/antanst/logging"
|
||||
"git.antanst.com/antanst/xerrors"
|
||||
)
|
||||
|
||||
var fatalErrors chan error
|
||||
|
||||
func main() {
|
||||
config.CONFIG = *config.GetConfig()
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
zerolog.SetGlobalLevel(config.CONFIG.LogLevel)
|
||||
zlog.Logger = zlog.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "[2006-01-02 15:04:05]"})
|
||||
|
||||
logging.InitSlogger(config.CONFIG.LogLevel)
|
||||
|
||||
err := runApp()
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
logging.LogError("%v", err)
|
||||
os.Exit(1)
|
||||
panic(fmt.Sprintf("Fatal Error: %v", err))
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func runApp() error {
|
||||
logging.LogInfo("Starting up. Press Ctrl+C to exit")
|
||||
|
||||
var listenHost string
|
||||
if len(os.Args) != 2 {
|
||||
listenHost = "0.0.0.0:1965"
|
||||
} else {
|
||||
listenHost = os.Args[1]
|
||||
}
|
||||
listenHost := config.CONFIG.Listen
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
serverErrors := make(chan error)
|
||||
fatalErrors = make(chan error)
|
||||
|
||||
go func() {
|
||||
err := startServer(listenHost)
|
||||
if err != nil {
|
||||
serverErrors <- errors.NewFatalError(err)
|
||||
fatalErrors <- xerrors.NewError(err, 0, "Server startup failed", true)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -61,16 +56,16 @@ func runApp() error {
|
||||
case <-signals:
|
||||
logging.LogWarn("Received SIGINT or SIGTERM signal, exiting")
|
||||
return nil
|
||||
case serverError := <-serverErrors:
|
||||
return errors.NewFatalError(serverError)
|
||||
case fatalError := <-fatalErrors:
|
||||
return xerrors.NewError(fatalError, 0, "Server error", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startServer(listenHost string) (err error) {
|
||||
cert, err := tls.LoadX509KeyPair("/certs/cert", "/certs/key")
|
||||
cert, err := tls.LoadX509KeyPair("certs/server.crt", "certs/server.key")
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("failed to load certificate: %w", err))
|
||||
return xerrors.NewError(fmt.Errorf("failed to load certificate: %w", err), 0, "Certificate loading failed", true)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
@@ -80,7 +75,7 @@ func startServer(listenHost string) (err error) {
|
||||
|
||||
listener, err := tls.Listen("tcp", listenHost, tlsConfig)
|
||||
if err != nil {
|
||||
return errors.NewFatalError(fmt.Errorf("failed to create listener: %w", err))
|
||||
return xerrors.NewError(fmt.Errorf("failed to create listener: %w", err), 0, "Listener creation failed", true)
|
||||
}
|
||||
defer func(listener net.Listener) {
|
||||
// If we've got an error closing the
|
||||
@@ -88,7 +83,7 @@ func startServer(listenHost string) (err error) {
|
||||
// the original error (if not nil)
|
||||
errClose := listener.Close()
|
||||
if errClose != nil && err == nil {
|
||||
err = errors.NewFatalError(err)
|
||||
err = xerrors.NewError(err, 0, "Listener close failed", true)
|
||||
}
|
||||
}(listener)
|
||||
|
||||
@@ -102,16 +97,16 @@ func startServer(listenHost string) (err error) {
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := handleConnection(conn.(*tls.Conn))
|
||||
remoteAddr := conn.RemoteAddr().String()
|
||||
connId := uid.UID()
|
||||
err := handleConnection(conn.(*tls.Conn), connId, remoteAddr)
|
||||
if err != nil {
|
||||
var asErr *errors.Error
|
||||
if errors.As(err, &asErr) {
|
||||
logging.LogError("Unexpected error: %v", err.(*errors.Error).ErrorWithStack())
|
||||
var asErr *xerrors.XError
|
||||
if errors.As(err, &asErr) && asErr.IsFatal {
|
||||
fatalErrors <- asErr
|
||||
return
|
||||
} else {
|
||||
logging.LogError("Unexpected error: %v", err)
|
||||
}
|
||||
if config.CONFIG.PanicOnUnexpectedError {
|
||||
panic("Encountered unexpected error")
|
||||
logging.LogWarn("%s %s Connection failed: %d %s (%v)", connId, remoteAddr, asErr.Code, asErr.UserMsg, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -121,56 +116,68 @@ func startServer(listenHost string) (err error) {
|
||||
func closeConnection(conn *tls.Conn) error {
|
||||
err := conn.CloseWrite()
|
||||
if err != nil {
|
||||
return errors.NewConnectionError(fmt.Errorf("failed to close TLS connection: %w", err))
|
||||
return xerrors.NewError(fmt.Errorf("failed to close TLS connection: %w", err), 50, "Connection close failed", false)
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return errors.NewConnectionError(fmt.Errorf("failed to close connection: %w", err))
|
||||
return xerrors.NewError(fmt.Errorf("failed to close connection: %w", err), 50, "Connection close failed", false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConnection(conn *tls.Conn) (err error) {
|
||||
remoteAddr := conn.RemoteAddr().String()
|
||||
connId := uid.UID()
|
||||
func handleConnection(conn *tls.Conn, connId string, remoteAddr string) (err error) {
|
||||
start := time.Now()
|
||||
var outputBytes []byte
|
||||
|
||||
defer func(conn *tls.Conn) {
|
||||
// Three possible cases here:
|
||||
// - We don't have an error
|
||||
// - We have a ConnectionError, which we don't propagate up
|
||||
// - We have an unexpected error.
|
||||
end := time.Now()
|
||||
tookMs := end.Sub(start).Milliseconds()
|
||||
var responseHeader string
|
||||
if err != nil {
|
||||
_, _ = conn.Write([]byte("50 server error"))
|
||||
responseHeader = "50 server error"
|
||||
// We don't propagate connection errors up.
|
||||
if errors.Is(err, errors.ConnectionError) {
|
||||
logging.LogInfo("%s %s %v", connId, remoteAddr, err)
|
||||
err = nil
|
||||
}
|
||||
} else {
|
||||
|
||||
// On non-errors, just log response and close connection.
|
||||
if err == nil {
|
||||
// Log non-erroneous responses
|
||||
if i := bytes.Index(outputBytes, []byte{'\r'}); i >= 0 {
|
||||
responseHeader = string(outputBytes[:i])
|
||||
}
|
||||
logging.LogInfo("%s %s response %s (%dms)", connId, remoteAddr, responseHeader, tookMs)
|
||||
_ = closeConnection(conn)
|
||||
return
|
||||
}
|
||||
logging.LogInfo("%s %s response %s (%dms)", connId, remoteAddr, responseHeader, tookMs)
|
||||
|
||||
var code int
|
||||
var responseMsg string
|
||||
var xErr *xerrors.XError
|
||||
if errors.As(err, &xErr) {
|
||||
// On fatal errors, immediatelly return the error.
|
||||
if xErr.IsFatal {
|
||||
_ = closeConnection(conn)
|
||||
return
|
||||
}
|
||||
code = xErr.Code
|
||||
responseMsg = xErr.UserMsg
|
||||
} else {
|
||||
code = 50
|
||||
responseMsg = "server error"
|
||||
}
|
||||
responseHeader = fmt.Sprintf("%d %s", code, responseMsg)
|
||||
_, _ = conn.Write([]byte(responseHeader))
|
||||
_ = closeConnection(conn)
|
||||
}(conn)
|
||||
|
||||
// Gemini is supposed to have a 1kb limit
|
||||
// on input requests.
|
||||
buffer := make([]byte, 1024)
|
||||
buffer := make([]byte, 1025)
|
||||
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
return errors.NewConnectionError(fmt.Errorf("failed to read connection data: %w", err))
|
||||
return xerrors.NewError(fmt.Errorf("failed to read connection data: %w", err), 59, "Connection read failed", false)
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.NewConnectionError(fmt.Errorf("client did not send data"))
|
||||
return xerrors.NewError(fmt.Errorf("client did not send data"), 59, "No data received", false)
|
||||
}
|
||||
if n > 1024 {
|
||||
return xerrors.NewError(fmt.Errorf("client request size %d > 1024 bytes", n), 59, "Request too large", false)
|
||||
}
|
||||
|
||||
dataBytes := buffer[:n]
|
||||
|
||||
Reference in New Issue
Block a user