(FEAT): Logging is so much better now :)
This commit is contained in:
parent
745a59ecaa
commit
72c9cb0f96
@ -1,10 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/haydenhargreaves/Potion/internal/app/server"
|
||||||
"github.com/haydenhargreaves/Potion/internal/app/server"
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging/loggers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const PORT = 3000
|
const PORT = 3000
|
||||||
|
|
||||||
@ -12,8 +8,5 @@ func main() {
|
|||||||
s := server.Init(PORT).Setup()
|
s := server.Init(PORT).Setup()
|
||||||
defer s.DB.Close()
|
defer s.DB.Close()
|
||||||
|
|
||||||
logger := loggers.NewConsoleLogger()
|
|
||||||
logger.Log(logging.LogLevelDebug, "%s", "Hello world")
|
|
||||||
|
|
||||||
s.Start()
|
s.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -24,6 +24,7 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -57,6 +57,8 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
|
|||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
|||||||
@ -3,10 +3,12 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong
|
// JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong
|
||||||
@ -96,3 +98,34 @@ func JwtOptionalAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
||||||
|
// TODO: Need traces using IDs?
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
|
||||||
|
var (
|
||||||
|
status int = ctx.Writer.Status()
|
||||||
|
latency string = time.Since(start).String()
|
||||||
|
client string = ctx.ClientIP()
|
||||||
|
method string = ctx.Request.Method
|
||||||
|
path string = ctx.Request.URL.Path
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Add color to status
|
||||||
|
|
||||||
|
format := "%d | %-14s | %15s | %-9s \"%s\""
|
||||||
|
logging.LogAll(
|
||||||
|
logs,
|
||||||
|
logging.LogLevelInformation,
|
||||||
|
format,
|
||||||
|
status,
|
||||||
|
latency,
|
||||||
|
client,
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import (
|
|||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging/loggers"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
@ -23,18 +25,24 @@ type Server struct {
|
|||||||
config cors.Config
|
config cors.Config
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
deps domain.InjectedDependencies
|
deps domain.InjectedDependencies
|
||||||
|
logs []logging.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the server with the provided port. CORS settings are defined here.
|
// Init initializes the server with the provided port. CORS settings are defined here.
|
||||||
// A pointer to a server object is returned which allows for method chaining.
|
// A pointer to a server object is returned which allows for method chaining.
|
||||||
func Init(port int) *Server {
|
func Init(port int) *Server {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Router: gin.Default(),
|
Router: gin.New(), // Not default anymore, to allow for custom logger
|
||||||
port: port,
|
port: port,
|
||||||
config: cors.DefaultConfig(),
|
config: cors.DefaultConfig(),
|
||||||
|
logs: []logging.Logger{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default loggers
|
||||||
|
server.logs = append(server.logs, loggers.NewConsoleLogger())
|
||||||
|
|
||||||
// Some stuff for templ rendering
|
// Some stuff for templ rendering
|
||||||
|
// TODO: Remove this
|
||||||
htmlRenderer := server.Router.HTMLRender
|
htmlRenderer := server.Router.HTMLRender
|
||||||
server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}
|
server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}
|
||||||
|
|
||||||
@ -54,28 +62,43 @@ func Init(port int) *Server {
|
|||||||
|
|
||||||
// Start starts the server on the port provided when the server was initialized
|
// Start starts the server on the port provided when the server was initialized
|
||||||
func (s *Server) Start() {
|
func (s *Server) Start() {
|
||||||
|
logging.LogAll(s.logs, logging.LogLevelDebug, "Server started on :%d\n", s.port)
|
||||||
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...
|
||||||
func (s *Server) Setup() *Server {
|
func (s *Server) Setup() *Server {
|
||||||
// SETUP THE ENVIRONMENT CONFIGURATION
|
// SETUP THE ENVIRONMENT CONFIGURATION
|
||||||
cfg, err := domain.LoadEnvironment()
|
cfg, err := domain.LoadEnvironment(s.logs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logging.LogAll(s.logs, logging.LogLevelFatal, err.Error())
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
|
logging.LogAll(s.logs, logging.LogLevelFatal, "Environment configuration is nil, crashing.")
|
||||||
panic("Environment configuration is nil, crashing.")
|
panic("Environment configuration is nil, crashing.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Using release on them all? Def need to clean this shitty environment up
|
||||||
if cfg.Environment == "dev" {
|
if cfg.Environment == "dev" {
|
||||||
gin.SetMode(gin.DebugMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
} else if cfg.Environment == "prod" {
|
} else if cfg.Environment == "prod" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
} else {
|
} else {
|
||||||
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
|
||||||
@ -111,7 +134,7 @@ func (s *Server) Setup() *Server {
|
|||||||
recipeRepo := repository.NewRecipeRepository(s.DB)
|
recipeRepo := repository.NewRecipeRepository(s.DB)
|
||||||
engagementRepo := repository.NewEngagementRepository(s.DB)
|
engagementRepo := repository.NewEngagementRepository(s.DB)
|
||||||
userService := service.NewUserService(userRepo)
|
userService := service.NewUserService(userRepo)
|
||||||
authService := service.NewAuthService(userRepo, jwtSecret)
|
authService := service.NewAuthService(userRepo, jwtSecret, s.logs)
|
||||||
recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
|
recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
|
||||||
engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
|
engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
|
||||||
|
|
||||||
@ -124,9 +147,8 @@ func (s *Server) Setup() *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply middleware
|
// Apply middleware
|
||||||
s.Router.Use(RecoveryMiddleware())
|
// TODO: Review the recovery middleware
|
||||||
// NOTE: No longer running on every connection!
|
s.Router.Use(gin.Recovery(), RecoveryMiddleware(), LoggingMiddleware(s.logs))
|
||||||
// s.Router.Use(JwtAuthMiddleWare(jwtSecret))
|
|
||||||
|
|
||||||
// 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) })
|
||||||
@ -219,14 +241,21 @@ func (s *Server) Setup() *Server {
|
|||||||
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
||||||
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
||||||
|
|
||||||
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
|
||||||
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
|
||||||
})
|
|
||||||
|
|
||||||
router_api_v2.POST("/engagement/view/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementViewRecipeHandlerV2)
|
router_api_v2.POST("/engagement/view/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementViewRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/share/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementShareRecipeHandlerV2)
|
router_api_v2.POST("/engagement/share/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementShareRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/favorite/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementFavoriteRecipeHandlerV2)
|
router_api_v2.POST("/engagement/favorite/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementFavoriteRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/make/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementMakeRecipeHandlerV2)
|
router_api_v2.POST("/engagement/make/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementMakeRecipeHandlerV2)
|
||||||
|
|
||||||
|
if cfg.Environment == "dev" {
|
||||||
|
s.debugDisplayRoutes()
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) debugDisplayRoutes() {
|
||||||
|
for _, route := range s.Router.Routes() {
|
||||||
|
format := "%-8s %s"
|
||||||
|
logging.LogAll(s.logs, logging.LogLevelDebug, format, route.Method, route.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
userRepository domain.UserRepository
|
userRepository domain.UserRepository
|
||||||
jwtSecret []byte
|
jwtSecret []byte
|
||||||
|
logs []logging.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile-time check to ensure the AuthService implements domain.AuthService
|
// Compile-time check to ensure the AuthService implements domain.AuthService
|
||||||
@ -38,10 +40,11 @@ var _ domainAuth.AuthService = (*AuthService)(nil)
|
|||||||
|
|
||||||
// NewAuthService creates a user service object which can be passed into the context. The service
|
// NewAuthService creates a user service object which can be passed into the context. The service
|
||||||
// requires a user repository which it will use to hit the database when needed.
|
// requires a user repository which it will use to hit the database when needed.
|
||||||
func NewAuthService(userRepository domain.UserRepository, jwtSecret []byte) domainAuth.AuthService {
|
func NewAuthService(userRepository domain.UserRepository, jwtSecret []byte, logs []logging.Logger) domainAuth.AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
userRepository: userRepository,
|
userRepository: userRepository,
|
||||||
jwtSecret: jwtSecret,
|
jwtSecret: jwtSecret,
|
||||||
|
logs: logs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +58,8 @@ func (s *AuthService) GetGoogleAuthUrl() string {
|
|||||||
oauth2.ApprovalForce,
|
oauth2.ApprovalForce,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logging.LogAll(s.logs, logging.LogLevelDebug, "Generated Google authentication URL: %s", url)
|
||||||
|
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,3 +129,53 @@ func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
|
|||||||
|
|
||||||
return tokenString, nil
|
return tokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "bytes"
|
||||||
|
// "time"
|
||||||
|
//
|
||||||
|
// "github.com/gin-gonic/gin"
|
||||||
|
// "github.com/google/uuid"
|
||||||
|
// "golang.org/x/exp/slog" // or your logging.Logger
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // LoggerMiddleware logs HTTP requests with structured fields
|
||||||
|
// func LoggerMiddleware(logger *slog.Logger) gin.HandlerFunc {
|
||||||
|
// return func(c *gin.Context) {
|
||||||
|
// // Generate request ID for tracing
|
||||||
|
// reqID := uuid.New().String()
|
||||||
|
// start := time.Now()
|
||||||
|
//
|
||||||
|
// // Capture request body (if enabled)
|
||||||
|
// var reqBody []byte
|
||||||
|
// if c.Request.ContentLength > 0 && c.Request.Body != nil {
|
||||||
|
// reqBody, _ = io.ReadAll(c.Request.Body)
|
||||||
|
// c.Request.Body.Close()
|
||||||
|
// c.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Log request start
|
||||||
|
// logger.Info("request started",
|
||||||
|
// slog.String("req_id", reqID),
|
||||||
|
// slog.String("method", c.Request.Method),
|
||||||
|
// slog.String("path", c.Request.URL.Path),
|
||||||
|
// slog.Int("content_length", int(c.Request.ContentLength)),
|
||||||
|
// slog.String("user_agent", c.Request.UserAgent()),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Process request
|
||||||
|
// c.Next()
|
||||||
|
//
|
||||||
|
// // Log request completion
|
||||||
|
// duration := time.Since(start)
|
||||||
|
// logger.Info("request completed",
|
||||||
|
// slog.String("req_id", reqID),
|
||||||
|
// slog.Int("status", c.Writer.Status()),
|
||||||
|
// slog.String("method", c.Request.Method),
|
||||||
|
// slog.String("path", c.Request.URL.Path),
|
||||||
|
// slog.Duration("duration", duration),
|
||||||
|
// slog.Int("size", c.Writer.Size()),
|
||||||
|
// slog.Any("req_body", string(reqBody)), // truncate if too big
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
|
domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
|
||||||
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||||
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
|
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,10 +56,10 @@ func IsLoggedIn(ctx *gin.Context) bool {
|
|||||||
// the event that required fields are not provided, an error will return and the caller should handle
|
// the event that required fields are not provided, an error will return and the caller should handle
|
||||||
// the missing value or panic. Toggles between 'dev', 'prod', etc are also handled by this method,
|
// the missing value or panic. Toggles between 'dev', 'prod', etc are also handled by this method,
|
||||||
// the values can be access assuming they are the proper values based on the provided environment.
|
// the values can be access assuming they are the proper values based on the provided environment.
|
||||||
func LoadEnvironment() (*EnvironmentConfig, error) {
|
func LoadEnvironment(logs []logging.Logger) (*EnvironmentConfig, error) {
|
||||||
err := godotenv.Load(".env")
|
err := godotenv.Load(".env")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
|
logging.LogAll(logs, logging.LogLevelWarning, "No .env file found or error loading .env: %v. Relying on system environment variables.", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := os.Getenv("ENVIRONMENT")
|
env := os.Getenv("ENVIRONMENT")
|
||||||
@ -130,7 +131,7 @@ func LoadEnvironment() (*EnvironmentConfig, error) {
|
|||||||
FrontendDomain: frontendDomain,
|
FrontendDomain: frontendDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Environment Config: %+v\n", cfg)
|
logging.LogAll(logs, logging.LogLevelDebug, "Environment Config: %+v\n", cfg)
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,35 @@ type LogLevel string
|
|||||||
const (
|
const (
|
||||||
LogLevelTrace LogLevel = "TRACE"
|
LogLevelTrace LogLevel = "TRACE"
|
||||||
LogLevelDebug LogLevel = "DEBUG"
|
LogLevelDebug LogLevel = "DEBUG"
|
||||||
LogLevelInformation LogLevel = "INFORMATION"
|
LogLevelInformation LogLevel = "INFO"
|
||||||
LogLevelWarning LogLevel = "WARNING"
|
LogLevelWarning LogLevel = "WARNING"
|
||||||
LogLevelError LogLevel = "ERROR"
|
LogLevelError LogLevel = "ERROR"
|
||||||
LogLevelFatal LogLevel = "FATAL"
|
LogLevelFatal LogLevel = "FATAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Background colors
|
||||||
|
BgBlack = "\033[40m"
|
||||||
|
BgRed = "\033[41m"
|
||||||
|
BgGreen = "\033[42m"
|
||||||
|
BgYellow = "\033[43m"
|
||||||
|
BgBlue = "\033[44m"
|
||||||
|
BgMagenta = "\033[45m"
|
||||||
|
BgCyan = "\033[46m"
|
||||||
|
BgWhite = "\033[47m"
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
Reset = "\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Log(level LogLevel, format string, v ...any)
|
Log(level LogLevel, format string, v ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogAll takes all of the inputs for a single logger and executes the logging operation
|
||||||
|
// on each of the loggers (logs) provided. This is just a convince function.
|
||||||
|
func LogAll(logs []Logger, level LogLevel, format string, v ...any) {
|
||||||
|
for _, log := range logs {
|
||||||
|
log.Log(level, format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,29 +4,45 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Implement the Logger interface
|
|
||||||
|
|
||||||
type ConsoleLogger struct {
|
type ConsoleLogger struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ logging.Logger = (*ConsoleLogger)(nil)
|
var _ logging.Logger = (*ConsoleLogger)(nil)
|
||||||
|
|
||||||
func NewConsoleLogger() ConsoleLogger {
|
func NewConsoleLogger() logging.Logger {
|
||||||
|
return &ConsoleLogger{
|
||||||
return ConsoleLogger{
|
|
||||||
writer: os.Stdout,
|
writer: os.Stdout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
func formatLevelString(level logging.LogLevel) string {
|
||||||
prefix := fmt.Appendf(nil, "[%s] ", level)
|
switch level {
|
||||||
bytes := fmt.Appendf(prefix, format, v...)
|
case logging.LogLevelTrace:
|
||||||
|
return fmt.Sprintf("%s[%s]%s", logging.BgMagenta, level, logging.Reset)
|
||||||
// WARN: Do we need to worry about errors?
|
case logging.LogLevelDebug:
|
||||||
_, _ = l.writer.Write(bytes)
|
return fmt.Sprintf("%s[%s]%s", logging.BgBlue, level, logging.Reset)
|
||||||
|
case logging.LogLevelInformation:
|
||||||
|
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)
|
||||||
|
case logging.LogLevelError:
|
||||||
|
return fmt.Sprintf("%s[%s]%s", logging.BgRed, level, logging.Reset)
|
||||||
|
case logging.LogLevelFatal:
|
||||||
|
return fmt.Sprintf("%s[%s]%s", logging.BgRed, level, logging.Reset)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%s]", level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||||
|
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
||||||
|
levelStr := formatLevelString(level)
|
||||||
|
fullFormat := fmt.Sprintf("%-18s %s | %s\n", levelStr, timestamp, format)
|
||||||
|
bytes := fmt.Appendf(nil, fullFormat, v...)
|
||||||
|
l.writer.Write(bytes)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,52 @@
|
|||||||
package loggers
|
package loggers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: Implement the Logger interface
|
// TODO: Implement the Logger interface
|
||||||
|
|
||||||
|
type FileLogger struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ logging.Logger = (*FileLogger)(nil)
|
||||||
|
|
||||||
|
// NewFileLogger creates a new file logger, opened on the filepath provided. If any errors
|
||||||
|
// occur, an error will be returned, along with an EMPTY logger. This is not a pointer return
|
||||||
|
// so it will never be nil, just empty.
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return &FileLogger{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
return &FileLogger{}, nil, fmt.Errorf("File could not be opened. File is nil.")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := &FileLogger{
|
||||||
|
writer: f,
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() error {
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *FileLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||||
|
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...)
|
||||||
|
l.writer.Write(bytes)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user