(FEAT): Logger is implemented!
However, its not used everywhere and the ENV needs work. Lots of work...
This commit is contained in:
parent
aca3c8b4ee
commit
dd43845138
@ -2,13 +2,13 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||
)
|
||||
|
||||
// DepedencyInjectionMiddleware injects the dependencies into the context set. This is a middleware
|
||||
@ -77,14 +77,13 @@ func JwtAuthMiddleWare(jwtSecretKey []byte) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryMiddleware() gin.HandlerFunc {
|
||||
|
||||
func RecoveryMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Log the panic with stack trace
|
||||
err := fmt.Errorf("panic recovered: %v\n%s", r, debug.Stack())
|
||||
log.Printf("[PANIC RECOVERY] %s", err)
|
||||
logging.LogAll(logs, logging.LogLevelFatal, "[PANIC RECOVERY] %s\n", err)
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusOK,
|
||||
|
||||
@ -114,12 +114,12 @@ func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
||||
path string = ctx.Request.URL.Path
|
||||
)
|
||||
|
||||
// TODO: Add color to status
|
||||
// TODO: Add color to status
|
||||
|
||||
format := "%d | %-14s | %15s | %-9s \"%s\""
|
||||
logging.LogAll(
|
||||
logs,
|
||||
logging.LogLevelInformation,
|
||||
logging.LogLevelInfo,
|
||||
format,
|
||||
status,
|
||||
latency,
|
||||
|
||||
@ -38,8 +38,8 @@ func Init(port int) *Server {
|
||||
logs: []logging.Logger{},
|
||||
}
|
||||
|
||||
// Default loggers
|
||||
server.logs = append(server.logs, loggers.NewConsoleLogger())
|
||||
// Default logger which logs everything
|
||||
server.logs = append(server.logs, loggers.NewConsoleLogger(logging.LogLevelTrace))
|
||||
|
||||
// Some stuff for templ rendering
|
||||
// TODO: Remove this
|
||||
@ -66,7 +66,8 @@ func (s *Server) Start() {
|
||||
s.Router.Run(fmt.Sprintf(":%d", s.port))
|
||||
}
|
||||
|
||||
// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy...
|
||||
// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy...
|
||||
// TODO: (1/26/2026) Abstract these functions and cleanup. This is fucking messy... still
|
||||
func (s *Server) Setup() *Server {
|
||||
// SETUP THE ENVIRONMENT CONFIGURATION
|
||||
cfg, err := domain.LoadEnvironment(s.logs)
|
||||
@ -88,17 +89,6 @@ func (s *Server) Setup() *Server {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
// TODO: Implement environment here for logging file
|
||||
path := "./logs.log"
|
||||
|
||||
fileLogger, cleanup, err := loggers.NewFileLogger(path)
|
||||
if err != nil {
|
||||
logging.LogAll(s.logs, logging.LogLevelWarning, "Failed to create file logger. %s\n", err.Error())
|
||||
} else {
|
||||
s.logs = append(s.logs, fileLogger)
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
// SETUP GOOGLE AUTH
|
||||
var (
|
||||
// NOTE: USING V2 NOW
|
||||
@ -126,6 +116,24 @@ func (s *Server) Setup() *Server {
|
||||
|
||||
s.DB = db
|
||||
|
||||
// TODO: Implement environment here for logging file
|
||||
path := "./logs.log"
|
||||
|
||||
fileLogger, cleanup, err := loggers.NewFileLogger(path, logging.LogLevelDebug)
|
||||
if err != nil {
|
||||
logging.LogAll(s.logs, logging.LogLevelWarning, "Failed to create file logger. %s\n", err.Error())
|
||||
} else {
|
||||
s.logs = append(s.logs, fileLogger)
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
databaseLogger, err := loggers.NewDatabaseLogger(s.DB, "logs", logging.LogLevelInfo)
|
||||
if err != nil {
|
||||
logging.LogAll(s.logs, logging.LogLevelWarning, "Failed to create database logger. %s\n", err.Error())
|
||||
} else {
|
||||
s.logs = append(s.logs, databaseLogger)
|
||||
}
|
||||
|
||||
// SETUP JWT
|
||||
jwtSecret := []byte(cfg.JwtSecret)
|
||||
|
||||
@ -148,7 +156,7 @@ func (s *Server) Setup() *Server {
|
||||
|
||||
// Apply middleware
|
||||
// TODO: Review the recovery middleware
|
||||
s.Router.Use(gin.Recovery(), RecoveryMiddleware(), LoggingMiddleware(s.logs))
|
||||
s.Router.Use(gin.Recovery(), RecoveryMiddleware(s.logs), LoggingMiddleware(s.logs))
|
||||
|
||||
// Redirect index to home page: Update this as needed
|
||||
s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
|
||||
-- Desc: Create the logs table.
|
||||
-- Date: 01/23/2026
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Logs (
|
||||
Id SERIAL PRIMARY KEY NOT NULL,
|
||||
Level TEXT NOT NULL CHECK (level IN ('TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL')),
|
||||
Message TEXT NOT NULL,
|
||||
Created TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@ -13,4 +13,5 @@ psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infra
|
||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/011_update_engagement_enum.sql
|
||||
|
||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/013_update_recipes_allow_large_servings.sql
|
||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/014_create_logs_table.sql
|
||||
|
||||
|
||||
@ -3,14 +3,38 @@ package logging
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
LogLevelTrace LogLevel = "TRACE"
|
||||
LogLevelDebug LogLevel = "DEBUG"
|
||||
LogLevelInformation LogLevel = "INFO"
|
||||
LogLevelWarning LogLevel = "WARNING"
|
||||
LogLevelError LogLevel = "ERROR"
|
||||
LogLevelFatal LogLevel = "FATAL"
|
||||
LogLevelTrace LogLevel = "TRACE"
|
||||
LogLevelDebug LogLevel = "DEBUG"
|
||||
LogLevelInfo LogLevel = "INFO"
|
||||
LogLevelWarning LogLevel = "WARN"
|
||||
LogLevelError LogLevel = "ERROR"
|
||||
LogLevelFatal LogLevel = "FATAL"
|
||||
)
|
||||
|
||||
// MatchFilter is called on a filter (l) with a target level to match on the filter. Match
|
||||
// means returning true of the target is greater than OR EQUAL TO the filter level. They order
|
||||
// by scale of magnitude.
|
||||
func (filter LogLevel) MatchFilter(target LogLevel) bool {
|
||||
// Define severity levels (higher number = more severe)
|
||||
severity := map[LogLevel]int{
|
||||
LogLevelTrace: 0,
|
||||
LogLevelDebug: 1,
|
||||
LogLevelInfo: 2,
|
||||
LogLevelWarning: 3,
|
||||
LogLevelError: 4,
|
||||
LogLevelFatal: 5,
|
||||
}
|
||||
|
||||
filterSeverity, filterOk := severity[filter]
|
||||
targetSeverity, targetOk := severity[target]
|
||||
|
||||
if !filterOk || !targetOk {
|
||||
return false
|
||||
}
|
||||
|
||||
return targetSeverity >= filterSeverity
|
||||
}
|
||||
|
||||
const (
|
||||
// Background colors
|
||||
BgBlack = "\033[40m"
|
||||
|
||||
@ -11,23 +11,28 @@ import (
|
||||
|
||||
type ConsoleLogger struct {
|
||||
writer io.Writer
|
||||
filter logging.LogLevel
|
||||
}
|
||||
|
||||
var _ logging.Logger = (*ConsoleLogger)(nil)
|
||||
|
||||
func NewConsoleLogger() logging.Logger {
|
||||
// NewConsoleLogger creates a new logger which writes directly to standard out (stdout).
|
||||
func NewConsoleLogger(filter logging.LogLevel) logging.Logger {
|
||||
return &ConsoleLogger{
|
||||
writer: os.Stdout,
|
||||
filter: filter,
|
||||
}
|
||||
}
|
||||
|
||||
// formatLevelString converts a log level string (level) into a new, formatted output string.
|
||||
// This also includes color, if the shell supports it. Otherwise, the rendering may appear odd.
|
||||
func formatLevelString(level logging.LogLevel) string {
|
||||
switch level {
|
||||
case logging.LogLevelTrace:
|
||||
return fmt.Sprintf("%s[%s]%s", logging.BgMagenta, level, logging.Reset)
|
||||
case logging.LogLevelDebug:
|
||||
return fmt.Sprintf("%s[%s]%s", logging.BgBlue, level, logging.Reset)
|
||||
case logging.LogLevelInformation:
|
||||
case logging.LogLevelInfo:
|
||||
return fmt.Sprintf("%s[%s]%s", logging.BgGreen, level, logging.Reset)
|
||||
case logging.LogLevelWarning:
|
||||
return fmt.Sprintf("%s[%s]%s", logging.BgYellow, level, logging.Reset)
|
||||
@ -39,7 +44,13 @@ func formatLevelString(level logging.LogLevel) string {
|
||||
return fmt.Sprintf("[%s]", level)
|
||||
}
|
||||
|
||||
// Log implements the interface.
|
||||
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||
// level is too low, do not log
|
||||
if !l.filter.MatchFilter(level) {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
||||
levelStr := formatLevelString(level)
|
||||
fullFormat := fmt.Sprintf("%-18s %s | %s\n", levelStr, timestamp, format)
|
||||
|
||||
77
internal/infrastructure/logging/loggers/database_logger.go
Normal file
77
internal/infrastructure/logging/loggers/database_logger.go
Normal file
@ -0,0 +1,77 @@
|
||||
package loggers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||
)
|
||||
|
||||
type DatabaseLogger struct {
|
||||
db *sql.DB
|
||||
table string
|
||||
filter logging.LogLevel
|
||||
}
|
||||
|
||||
var _ logging.Logger = (*DatabaseLogger)(nil)
|
||||
|
||||
func NewDatabaseLogger(conn *sql.DB, table string, filter logging.LogLevel) (logging.Logger, error) {
|
||||
if conn == nil {
|
||||
return &DatabaseLogger{}, fmt.Errorf("Connection is nil, something is very wrong.")
|
||||
}
|
||||
// Ensure the DB is open
|
||||
if err := conn.Ping(); err != nil {
|
||||
return &DatabaseLogger{}, err
|
||||
}
|
||||
|
||||
// Ensure the table exists
|
||||
exists, err := tableExists(conn, table)
|
||||
if err != nil {
|
||||
return &DatabaseLogger{}, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return &DatabaseLogger{}, fmt.Errorf("Database table '%s' does not exist on provided connection.", table)
|
||||
}
|
||||
|
||||
logger := &DatabaseLogger{
|
||||
db: conn,
|
||||
table: table,
|
||||
filter: filter,
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// tableExists queries a database connection and returns whether the table name provided
|
||||
// exists on the table.
|
||||
func tableExists(conn *sql.DB, tableName string) (bool, error) {
|
||||
var exists bool
|
||||
err := conn.QueryRow(`
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = $1
|
||||
)`,
|
||||
tableName).Scan(&exists)
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// Log implements the interface.
|
||||
func (l *DatabaseLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||
// level is too low, do not log
|
||||
if !l.filter.MatchFilter(level) {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(format, v...)
|
||||
query := "INSERT INTO logs (level, message) VALUES ($1, $2);"
|
||||
|
||||
// Ignoring result and error, cuz what the hell would we do with them lol
|
||||
_, err := l.db.Exec(query, level, message)
|
||||
|
||||
// TODO: Remove
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,9 @@ import (
|
||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||
)
|
||||
|
||||
// TODO: Implement the Logger interface
|
||||
|
||||
type FileLogger struct {
|
||||
writer io.Writer
|
||||
filter logging.LogLevel
|
||||
}
|
||||
|
||||
var _ logging.Logger = (*FileLogger)(nil)
|
||||
@ -23,7 +22,7 @@ var _ logging.Logger = (*FileLogger)(nil)
|
||||
//
|
||||
// This function does not close the file, cleanup function that is returned should be called
|
||||
// to close the file opened in this function.
|
||||
func NewFileLogger(filepath string) (logging.Logger, func() error, error) {
|
||||
func NewFileLogger(filepath string, filter logging.LogLevel) (logging.Logger, func() error, error) {
|
||||
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
return &FileLogger{}, nil, err
|
||||
@ -35,6 +34,7 @@ func NewFileLogger(filepath string) (logging.Logger, func() error, error) {
|
||||
|
||||
logger := &FileLogger{
|
||||
writer: f,
|
||||
filter: filter,
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
@ -44,7 +44,13 @@ func NewFileLogger(filepath string) (logging.Logger, func() error, error) {
|
||||
return logger, cleanup, nil
|
||||
}
|
||||
|
||||
// Log implements the interface.
|
||||
func (l *FileLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||
// level is too low, do not log
|
||||
if !l.filter.MatchFilter(level) {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
||||
fullFormat := fmt.Sprintf("%-13s %s | %s\n", "["+level+"]", timestamp, format)
|
||||
bytes := fmt.Appendf(nil, fullFormat, v...)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user