(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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
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
|
// 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) {
|
return func(ctx *gin.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
// Log the panic with stack trace
|
// Log the panic with stack trace
|
||||||
err := fmt.Errorf("panic recovered: %v\n%s", r, debug.Stack())
|
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{
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
"status": http.StatusOK,
|
"status": http.StatusOK,
|
||||||
|
|||||||
@ -114,12 +114,12 @@ func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
|||||||
path string = ctx.Request.URL.Path
|
path string = ctx.Request.URL.Path
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Add color to status
|
// TODO: Add color to status
|
||||||
|
|
||||||
format := "%d | %-14s | %15s | %-9s \"%s\""
|
format := "%d | %-14s | %15s | %-9s \"%s\""
|
||||||
logging.LogAll(
|
logging.LogAll(
|
||||||
logs,
|
logs,
|
||||||
logging.LogLevelInformation,
|
logging.LogLevelInfo,
|
||||||
format,
|
format,
|
||||||
status,
|
status,
|
||||||
latency,
|
latency,
|
||||||
|
|||||||
@ -38,8 +38,8 @@ func Init(port int) *Server {
|
|||||||
logs: []logging.Logger{},
|
logs: []logging.Logger{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default loggers
|
// Default logger which logs everything
|
||||||
server.logs = append(server.logs, loggers.NewConsoleLogger())
|
server.logs = append(server.logs, loggers.NewConsoleLogger(logging.LogLevelTrace))
|
||||||
|
|
||||||
// Some stuff for templ rendering
|
// Some stuff for templ rendering
|
||||||
// TODO: Remove this
|
// TODO: Remove this
|
||||||
@ -66,7 +66,8 @@ func (s *Server) Start() {
|
|||||||
s.Router.Run(fmt.Sprintf(":%d", s.port))
|
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 {
|
func (s *Server) Setup() *Server {
|
||||||
// SETUP THE ENVIRONMENT CONFIGURATION
|
// SETUP THE ENVIRONMENT CONFIGURATION
|
||||||
cfg, err := domain.LoadEnvironment(s.logs)
|
cfg, err := domain.LoadEnvironment(s.logs)
|
||||||
@ -88,17 +89,6 @@ func (s *Server) Setup() *Server {
|
|||||||
gin.SetMode(gin.TestMode)
|
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
|
// SETUP GOOGLE AUTH
|
||||||
var (
|
var (
|
||||||
// NOTE: USING V2 NOW
|
// NOTE: USING V2 NOW
|
||||||
@ -126,6 +116,24 @@ func (s *Server) Setup() *Server {
|
|||||||
|
|
||||||
s.DB = db
|
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
|
// SETUP JWT
|
||||||
jwtSecret := []byte(cfg.JwtSecret)
|
jwtSecret := []byte(cfg.JwtSecret)
|
||||||
|
|
||||||
@ -148,7 +156,7 @@ func (s *Server) Setup() *Server {
|
|||||||
|
|
||||||
// Apply middleware
|
// Apply middleware
|
||||||
// TODO: Review the recovery 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
|
// Redirect index to home page: Update this as needed
|
||||||
s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
|
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/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/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
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LogLevelTrace LogLevel = "TRACE"
|
LogLevelTrace LogLevel = "TRACE"
|
||||||
LogLevelDebug LogLevel = "DEBUG"
|
LogLevelDebug LogLevel = "DEBUG"
|
||||||
LogLevelInformation LogLevel = "INFO"
|
LogLevelInfo LogLevel = "INFO"
|
||||||
LogLevelWarning LogLevel = "WARNING"
|
LogLevelWarning LogLevel = "WARN"
|
||||||
LogLevelError LogLevel = "ERROR"
|
LogLevelError LogLevel = "ERROR"
|
||||||
LogLevelFatal LogLevel = "FATAL"
|
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 (
|
const (
|
||||||
// Background colors
|
// Background colors
|
||||||
BgBlack = "\033[40m"
|
BgBlack = "\033[40m"
|
||||||
|
|||||||
@ -11,23 +11,28 @@ import (
|
|||||||
|
|
||||||
type ConsoleLogger struct {
|
type ConsoleLogger struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
|
filter logging.LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ logging.Logger = (*ConsoleLogger)(nil)
|
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{
|
return &ConsoleLogger{
|
||||||
writer: os.Stdout,
|
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 {
|
func formatLevelString(level logging.LogLevel) string {
|
||||||
switch level {
|
switch level {
|
||||||
case logging.LogLevelTrace:
|
case logging.LogLevelTrace:
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgMagenta, level, logging.Reset)
|
return fmt.Sprintf("%s[%s]%s", logging.BgMagenta, level, logging.Reset)
|
||||||
case logging.LogLevelDebug:
|
case logging.LogLevelDebug:
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgBlue, level, logging.Reset)
|
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)
|
return fmt.Sprintf("%s[%s]%s", logging.BgGreen, level, logging.Reset)
|
||||||
case logging.LogLevelWarning:
|
case logging.LogLevelWarning:
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgYellow, level, logging.Reset)
|
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)
|
return fmt.Sprintf("[%s]", level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log implements the interface.
|
||||||
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
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")
|
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
||||||
levelStr := formatLevelString(level)
|
levelStr := formatLevelString(level)
|
||||||
fullFormat := fmt.Sprintf("%-18s %s | %s\n", levelStr, timestamp, format)
|
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"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Implement the Logger interface
|
|
||||||
|
|
||||||
type FileLogger struct {
|
type FileLogger struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
|
filter logging.LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ logging.Logger = (*FileLogger)(nil)
|
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
|
// This function does not close the file, cleanup function that is returned should be called
|
||||||
// to close the file opened in this function.
|
// 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)
|
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &FileLogger{}, nil, err
|
return &FileLogger{}, nil, err
|
||||||
@ -35,6 +34,7 @@ func NewFileLogger(filepath string) (logging.Logger, func() error, error) {
|
|||||||
|
|
||||||
logger := &FileLogger{
|
logger := &FileLogger{
|
||||||
writer: f,
|
writer: f,
|
||||||
|
filter: filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup := func() error {
|
cleanup := func() error {
|
||||||
@ -44,7 +44,13 @@ func NewFileLogger(filepath string) (logging.Logger, func() error, error) {
|
|||||||
return logger, cleanup, nil
|
return logger, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log implements the interface.
|
||||||
func (l *FileLogger) Log(level logging.LogLevel, format string, v ...any) {
|
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")
|
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
||||||
fullFormat := fmt.Sprintf("%-13s %s | %s\n", "["+level+"]", timestamp, format)
|
fullFormat := fmt.Sprintf("%-13s %s | %s\n", "["+level+"]", timestamp, format)
|
||||||
bytes := fmt.Appendf(nil, fullFormat, v...)
|
bytes := fmt.Appendf(nil, fullFormat, v...)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user