add better logs

This commit is contained in:
Fran Jurmanović
2025-05-29 01:27:59 +02:00
parent 1f41f6003b
commit 26c6bc5496
9 changed files with 228 additions and 46 deletions

View File

@@ -4,6 +4,7 @@ import (
"acc-server-manager/local/controller" "acc-server-manager/local/controller"
"acc-server-manager/local/utl/common" "acc-server-manager/local/utl/common"
"acc-server-manager/local/utl/configs" "acc-server-manager/local/utl/configs"
"acc-server-manager/local/utl/logging"
"os" "os"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -40,13 +41,13 @@ func Init(di *dig.Container, app *fiber.App) {
return routeGroups return routeGroups
}) })
if err != nil { if err != nil {
panic("unable to bind routes") logging.Panic("unable to bind routes")
} }
err = di.Provide(func() *dig.Container { err = di.Provide(func() *dig.Container {
return di return di
}) })
if err != nil { if err != nil {
panic("unable to bind dig") logging.Panic("unable to bind dig")
} }
controller.InitializeControllers(di) controller.InitializeControllers(di)

View File

@@ -1,6 +1,8 @@
package model package model
import "time" import (
"time"
)
// BaseFilter contains common filter fields that can be embedded in other filters // BaseFilter contains common filter fields that can be embedded in other filters
type BaseFilter struct { type BaseFilter struct {
@@ -21,15 +23,6 @@ type ServerBasedFilter struct {
ServerID int `param:"id"` ServerID int `param:"id"`
} }
// ServerFilter defines filtering options for Server queries
type ServerFilter struct {
BaseFilter
ServerBasedFilter
Name string `query:"name"`
ServiceName string `query:"service_name"`
Status string `query:"status"`
}
// ConfigFilter defines filtering options for Config queries // ConfigFilter defines filtering options for Config queries
type ConfigFilter struct { type ConfigFilter struct {
BaseFilter BaseFilter

View File

@@ -3,6 +3,8 @@ package model
import ( import (
"sync" "sync"
"time" "time"
"gorm.io/gorm"
) )
// Server represents an ACC server instance // Server represents an ACC server instance
@@ -49,4 +51,23 @@ type ServerState struct {
SessionDurationMinutes int `json:"sessionDurationMinutes"` SessionDurationMinutes int `json:"sessionDurationMinutes"`
// Players map[int]*PlayerState // Players map[int]*PlayerState
// etc. // etc.
}
// ServerFilter defines filtering options for Server queries
type ServerFilter struct {
BaseFilter
ServerBasedFilter
Name string `query:"name"`
ServiceName string `query:"service_name"`
Status string `query:"status"`
}
// ApplyFilter implements the Filterable interface
func (f *ServerFilter) ApplyFilter(query *gorm.DB) *gorm.DB {
// Apply server filter
if f.ServerID != 0 {
query = query.Where("id = ?", f.ServerID)
}
return query
} }

View File

@@ -8,7 +8,6 @@ import (
// StateHistoryFilter combines common filter capabilities // StateHistoryFilter combines common filter capabilities
type StateHistoryFilter struct { type StateHistoryFilter struct {
BaseFilter // Adds pagination and sorting
ServerBasedFilter // Adds server ID from path parameter ServerBasedFilter // Adds server ID from path parameter
DateRangeFilter // Adds date range filtering DateRangeFilter // Adds date range filtering

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"
"acc-server-manager/local/repository" "acc-server-manager/local/repository"
"acc-server-manager/local/utl/logging"
"acc-server-manager/local/utl/tracking" "acc-server-manager/local/utl/tracking"
"context" "context"
"log" "log"
@@ -58,7 +59,7 @@ func NewServerService(repository *repository.ServerRepository, stateHistoryRepo
// Initialize instances for all servers // Initialize instances for all servers
servers, err := repository.GetAll(context.Background(), &model.ServerFilter{}) servers, err := repository.GetAll(context.Background(), &model.ServerFilter{})
if err != nil { if err != nil {
log.Print(err.Error()) logging.Error("Failed to get servers: %v", err)
return service return service
} }
@@ -106,7 +107,7 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
// Try to load sessions from config // Try to load sessions from config
event, err := DecodeFileName(EventJson)(server.ConfigPath) event, err := DecodeFileName(EventJson)(server.ConfigPath)
if err != nil { if err != nil {
log.Printf("Failed to load event config for server %d: %v", server.ID, err) logging.Error("Failed to load event config for server %d: %v", server.ID, err)
return return
} }
evt := event.(model.EventConfig) evt := event.(model.EventConfig)
@@ -115,7 +116,7 @@ func (s *ServerService) updateSessionDuration(server *model.Server, sessionType
} }
sessions := sessionsInterface.([]model.Session) sessions := sessionsInterface.([]model.Session)
if (sessionType == "" && len(sessions) > 0) { if sessionType == "" && len(sessions) > 0 {
sessionType = sessions[0].SessionType sessionType = sessions[0].SessionType
} }
for _, session := range sessions { for _, session := range sessions {
@@ -200,24 +201,25 @@ func (s *ServerService) StartAccServerRuntime(server *model.Server) {
// context.Context: Application context // context.Context: Application context
// Returns: // Returns:
// string: Application version // string: Application version
func (as ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]model.Server, error) { func (s ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]model.Server, error) {
servers, err := as.repository.GetAll(ctx.UserContext(), filter) servers, err := s.repository.GetAll(ctx.UserContext(), filter)
if err != nil { if err != nil {
logging.Error("Failed to get servers: %v", err)
return nil, err return nil, err
} }
for i, server := range *servers { for i, server := range *servers {
status, err := as.apiService.StatusServer(server.ServiceName) status, err := s.apiService.StatusServer(server.ServiceName)
if err != nil { if err != nil {
log.Print(err.Error()) logging.Error("Failed to get status for server %s: %v", server.ServiceName, err)
} }
(*servers)[i].Status = model.ParseServiceStatus(status) (*servers)[i].Status = model.ParseServiceStatus(status)
instance, ok := as.instances.Load(server.ID) instance, ok := s.instances.Load(server.ID)
if !ok { if !ok {
log.Print("Unable to retrieve instance for server of ID: ", server.ID) logging.Warn("No instance found for server ID: %d", server.ID)
} else { } else {
serverInstance := instance.(*tracking.AccServerInstance) serverInstance := instance.(*tracking.AccServerInstance)
if (serverInstance.State != nil) { if serverInstance.State != nil {
(*servers)[i].State = *serverInstance.State (*servers)[i].State = *serverInstance.State
} }
} }

View File

@@ -155,12 +155,18 @@ func parsePathParam(c *fiber.Ctx, field reflect.Value, paramName string) error {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := c.ParamsInt(paramName) val, err := c.ParamsInt(paramName)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "strconv.Atoi: parsing \"\": invalid syntax") {
return nil
}
return err return err
} }
field.SetInt(int64(val)) field.SetInt(int64(val))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := c.ParamsInt(paramName) val, err := c.ParamsInt(paramName)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "strconv.Atoi: parsing \"\": invalid syntax") {
return nil
}
return err return err
} }
field.SetUint(uint64(val)) field.SetUint(uint64(val))

View File

@@ -2,6 +2,7 @@ package db
import ( import (
"acc-server-manager/local/model" "acc-server-manager/local/model"
"acc-server-manager/local/utl/logging"
"go.uber.org/dig" "go.uber.org/dig"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
@@ -11,13 +12,13 @@ import (
func Start(di *dig.Container) { func Start(di *dig.Container) {
db, err := gorm.Open(sqlite.Open("acc.db"), &gorm.Config{}) db, err := gorm.Open(sqlite.Open("acc.db"), &gorm.Config{})
if err != nil { if err != nil {
panic("failed to connect database") logging.Panic("failed to connect database")
} }
err = di.Provide(func() *gorm.DB { err = di.Provide(func() *gorm.DB {
return db return db
}) })
if err != nil { if err != nil {
panic("failed to bind database") logging.Panic("failed to bind database")
} }
Migrate(db) Migrate(db)
} }
@@ -25,39 +26,39 @@ func Start(di *dig.Container) {
func Migrate(db *gorm.DB) { func Migrate(db *gorm.DB) {
err := db.AutoMigrate(&model.ApiModel{}) err := db.AutoMigrate(&model.ApiModel{})
if err != nil { if err != nil {
panic("failed to migrate model.ApiModel") logging.Panic("failed to migrate model.ApiModel")
} }
err = db.AutoMigrate(&model.Server{}) err = db.AutoMigrate(&model.Server{})
if err != nil { if err != nil {
panic("failed to migrate model.Server") logging.Panic("failed to migrate model.Server")
} }
err = db.AutoMigrate(&model.Config{}) err = db.AutoMigrate(&model.Config{})
if err != nil { if err != nil {
panic("failed to migrate model.Config") logging.Panic("failed to migrate model.Config")
} }
err = db.AutoMigrate(&model.Track{}) err = db.AutoMigrate(&model.Track{})
if err != nil { if err != nil {
panic("failed to migrate model.Track") logging.Panic("failed to migrate model.Track")
} }
err = db.AutoMigrate(&model.CarModel{}) err = db.AutoMigrate(&model.CarModel{})
if err != nil { if err != nil {
panic("failed to migrate model.CarModel") logging.Panic("failed to migrate model.CarModel")
} }
err = db.AutoMigrate(&model.CupCategory{}) err = db.AutoMigrate(&model.CupCategory{})
if err != nil { if err != nil {
panic("failed to migrate model.CupCategory") logging.Panic("failed to migrate model.CupCategory")
} }
err = db.AutoMigrate(&model.DriverCategory{}) err = db.AutoMigrate(&model.DriverCategory{})
if err != nil { if err != nil {
panic("failed to migrate model.DriverCategory") logging.Panic("failed to migrate model.DriverCategory")
} }
err = db.AutoMigrate(&model.SessionType{}) err = db.AutoMigrate(&model.SessionType{})
if err != nil { if err != nil {
panic("failed to migrate model.SessionType") logging.Panic("failed to migrate model.SessionType")
} }
err = db.AutoMigrate(&model.StateHistory{}) err = db.AutoMigrate(&model.StateHistory{})
if err != nil { if err != nil {
panic("failed to migrate model.StateHistory") logging.Panic("failed to migrate model.StateHistory")
} }
db.FirstOrCreate(&model.ApiModel{Api: "Works"}) db.FirstOrCreate(&model.ApiModel{Api: "Works"})

149
local/utl/logging/logger.go Normal file
View File

@@ -0,0 +1,149 @@
package logging
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"sync"
"time"
)
var (
logger *Logger
once sync.Once
timeFormat = "2006-01-02 15:04:05.000"
)
type Logger struct {
file *os.File
logger *log.Logger
}
// Initialize creates or gets the singleton logger instance
func Initialize() (*Logger, error) {
var err error
once.Do(func() {
logger, err = newLogger()
})
return logger, err
}
func newLogger() (*Logger, error) {
// Ensure logs directory exists
if err := os.MkdirAll("logs", 0755); err != nil {
return nil, fmt.Errorf("failed to create logs directory: %v", err)
}
// Open log file with date in name
logPath := filepath.Join("logs", fmt.Sprintf("acc-server-%s.log", time.Now().Format("2006-01-02")))
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %v", err)
}
// Create multi-writer for both file and console
multiWriter := io.MultiWriter(file, os.Stdout)
// Create logger with custom prefix
logger := &Logger{
file: file,
logger: log.New(multiWriter, "", 0),
}
return logger, nil
}
func (l *Logger) Close() error {
if l.file != nil {
return l.file.Close()
}
return nil
}
func (l *Logger) log(level, format string, v ...interface{}) {
// Get caller info
_, file, line, _ := runtime.Caller(2)
file = filepath.Base(file)
// Format message
msg := fmt.Sprintf(format, v...)
// Format final log line
logLine := fmt.Sprintf("[%s] [%s] [%s:%d] %s",
time.Now().Format(timeFormat),
level,
file,
line,
msg,
)
l.logger.Println(logLine)
}
func (l *Logger) Info(format string, v ...interface{}) {
l.log("INFO", format, v...)
}
func (l *Logger) Error(format string, v ...interface{}) {
l.log("ERROR", format, v...)
}
func (l *Logger) Warn(format string, v ...interface{}) {
l.log("WARN", format, v...)
}
func (l *Logger) Debug(format string, v ...interface{}) {
l.log("DEBUG", format, v...)
}
func (l *Logger) Panic(format string) {
l.Panic("PANIC " + format)
}
// Global convenience functions
func Info(format string, v ...interface{}) {
if logger != nil {
logger.Info(format, v...)
}
}
func Error(format string, v ...interface{}) {
if logger != nil {
logger.Error(format, v...)
}
}
func Warn(format string, v ...interface{}) {
if logger != nil {
logger.Warn(format, v...)
}
}
func Debug(format string, v ...interface{}) {
if logger != nil {
logger.Debug(format, v...)
}
}
func Panic(format string) {
if logger != nil {
logger.Panic(format)
}
}
// RecoverAndLog recovers from panics and logs them
func RecoverAndLog() {
if r := recover(); r != nil {
if logger != nil {
// Get stack trace
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
stackTrace := string(buf[:n])
logger.log("PANIC", "Recovered from panic: %v\nStack Trace:\n%s", r, stackTrace)
}
}
}

View File

@@ -2,9 +2,8 @@ package server
import ( import (
"acc-server-manager/local/api" "acc-server-manager/local/api"
"acc-server-manager/local/utl/common" "acc-server-manager/local/utl/logging"
"fmt" "fmt"
"log"
"os" "os"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -14,6 +13,17 @@ import (
) )
func Start(di *dig.Container) *fiber.App { func Start(di *dig.Container) *fiber.App {
// Initialize logger
logger, err := logging.Initialize()
if err != nil {
fmt.Printf("Failed to initialize logger: %v\n", err)
os.Exit(1)
}
defer logger.Close()
// Set up panic recovery
defer logging.RecoverAndLog()
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
EnablePrintRoutes: true, EnablePrintRoutes: true,
}) })
@@ -22,22 +32,22 @@ func Start(di *dig.Container) *fiber.App {
app.Get("/swagger/*", swagger.HandlerDefault) app.Get("/swagger/*", swagger.HandlerDefault)
file, err := os.OpenFile("logs.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Print("Cannot open file logs.log")
}
log.SetOutput(file)
api.Init(di, app) api.Init(di, app)
app.Get("/ping", func(c *fiber.Ctx) error { app.Get("/ping", func(c *fiber.Ctx) error {
return c.SendString("pong") return c.SendString("pong")
}) })
port := os.Getenv("PORT") port := os.Getenv("PORT")
err = app.Listen(":" + port) if port == "" {
if err != nil { port = "3000" // Default port
msg := fmt.Sprintf("Running on %s:%s", common.GetIP(), port)
println(msg)
} }
logging.Info("Starting server on port %s", port)
if err := app.Listen(":" + port); err != nil {
logging.Error("Failed to start server: %v", err)
os.Exit(1)
}
return app return app
} }