Enhance logging by refactoring the utility package to reduce repetition. Integrate Echo logging with zerolog. Improve server shutdown procedure to handle Ctrl+C gracefully with a 5-second delay and cancellation option via keypress.
- commit
- ce225b6ab724dab480ebdc4b6512560e10b90978
- author
- bnema · · 2023-09-04 10:42 UTC
- parent
- 4849ddd
- signature
- unsigned
9 files · +373 -135
M cmd/main.go +71 -6
| @@ -1,11 +1,39 @@ | |||
| 1 | 1 | package main | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "net/http" | |
| 5 | + | "os" | |
| 6 | + | "os/exec" | |
| 7 | + | "os/signal" | |
| 8 | + | "syscall" | |
| 9 | + | "time" | |
| 10 | + | ||
| 11 | + | "github.com/labstack/echo/v4" | |
| 4 | 12 | "gogs.bnema.dev/gordon-echo/internal/app" | |
| 5 | 13 | "gogs.bnema.dev/gordon-echo/internal/http/routes" | |
| 14 | + | "gogs.bnema.dev/gordon-echo/pkg/docker" | |
| 15 | + | "gogs.bnema.dev/gordon-echo/pkg/utils" | |
| 6 | 16 | ) | |
| 7 | 17 | ||
| 18 | + | func setRawMode() { | |
| 19 | + | cmd := exec.Command("stty", "raw", "-echo") | |
| 20 | + | cmd.Stdin = os.Stdin | |
| 21 | + | _ = cmd.Run() | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func restoreTerminal() { | |
| 25 | + | cmd := exec.Command("stty", "-raw", "echo") | |
| 26 | + | cmd.Stdin = os.Stdin | |
| 27 | + | _ = cmd.Run() | |
| 28 | + | } | |
| 29 | + | ||
| 8 | 30 | func main() { | |
| 31 | + | // Port = os.Getenv("PORT") or fallback to default port (1323) | |
| 32 | + | port := os.Getenv("PORT") | |
| 33 | + | if port == "" { | |
| 34 | + | port = "1323" | |
| 35 | + | } | |
| 36 | + | ||
| 9 | 37 | // Initialize Gordon | |
| 10 | 38 | gordon, err := app.NewApp() | |
| 11 | 39 | if err != nil { | |
| @@ -14,13 +42,50 @@ func main() { | |||
| 14 | 42 | return | |
| 15 | 43 | } | |
| 16 | 44 | ||
| 17 | - | // Initialize echo server with routes and middlewares | |
| 18 | - | e := routes.NewRouter(gordon) // Pass the entire 'App' instance | |
| 45 | + | docker.ListDocker() // List docker containers | |
| 46 | + | ||
| 47 | + | e := echo.New() | |
| 48 | + | e.HideBanner = true | |
| 49 | + | e.HidePort = true | |
| 50 | + | e.Logger = utils.NewEchoLoggerWrapper(gordon.APPLogger) | |
| 51 | + | e = routes.ConfigureRouter(e, gordon) | |
| 52 | + | ||
| 53 | + | // Start the server in a separate goroutine | |
| 54 | + | go func() { | |
| 55 | + | gordon.APPLogger.Info().Msgf("Server is listening on port %s", port) | |
| 56 | + | if err := e.Start(":" + port); err != nil && err != http.ErrServerClosed { | |
| 57 | + | gordon.APPLogger.Fatal().Err(err).Msg("Failed to start the server") | |
| 58 | + | } | |
| 59 | + | }() | |
| 60 | + | ||
| 61 | + | // Catch SIGINT and SIGTERM signals without raw mode initially | |
| 62 | + | c := make(chan os.Signal, 1) | |
| 63 | + | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |
| 64 | + | <-c | |
| 65 | + | ||
| 66 | + | gordon.APPLogger.Warn().Msg("Graceful shutdown initiated") | |
| 67 | + | gordon.APPLogger.Warn().Msg("Server will shutdown in 5 seconds press ENTER to cancel") | |
| 68 | + | ||
| 69 | + | // Now, set raw mode | |
| 70 | + | setRawMode() | |
| 71 | + | defer restoreTerminal() // Ensure terminal is restored even if program exits unexpectedly | |
| 72 | + | ||
| 73 | + | // Channel to signal a key press | |
| 74 | + | keypress := make(chan struct{}) | |
| 75 | + | ||
| 76 | + | // Goroutine to listen for a key press | |
| 77 | + | go func() { | |
| 78 | + | var b []byte = make([]byte, 1) | |
| 79 | + | os.Stdin.Read(b) | |
| 80 | + | keypress <- struct{}{} | |
| 81 | + | }() | |
| 19 | 82 | ||
| 20 | - | if err := e.Start(":1323"); err != nil { | |
| 21 | - | gordon.APPLogger.Error().Err(err).Msg("Failed to start the server") | |
| 83 | + | // Wait for either a key press or the 5-second timer | |
| 84 | + | select { | |
| 85 | + | case <-time.After(5 * time.Second): | |
| 86 | + | gordon.APPLogger.Info().Msg("Server shutdown") | |
| 87 | + | case <-keypress: | |
| 88 | + | gordon.APPLogger.Info().Msg("Shutdown cancelled by user") | |
| 22 | 89 | } | |
| 23 | 90 | ||
| 24 | - | // Graceful shutdown | |
| 25 | - | gordon.APPLogger.Info().Msg("Server is shutting down...") | |
| 26 | 91 | } | |
| @@ -1,11 +1,39 @@ | |||||
| 1 | package main | 1 | package main | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "net/http" | |||
| 5 | + | "os" | |||
| 6 | + | "os/exec" | |||
| 7 | + | "os/signal" | |||
| 8 | + | "syscall" | |||
| 9 | + | "time" | |||
| 10 | + | ||||
| 11 | + | "github.com/labstack/echo/v4" | |||
| 4 | "gogs.bnema.dev/gordon-echo/internal/app" | 12 | "gogs.bnema.dev/gordon-echo/internal/app" | ||
| 5 | "gogs.bnema.dev/gordon-echo/internal/http/routes" | 13 | "gogs.bnema.dev/gordon-echo/internal/http/routes" | ||
| 14 | + | "gogs.bnema.dev/gordon-echo/pkg/docker" | |||
| 15 | + | "gogs.bnema.dev/gordon-echo/pkg/utils" | |||
| 6 | ) | 16 | ) | ||
| 7 | 17 | ||||
| 18 | + | func setRawMode() { | |||
| 19 | + | cmd := exec.Command("stty", "raw", "-echo") | |||
| 20 | + | cmd.Stdin = os.Stdin | |||
| 21 | + | _ = cmd.Run() | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func restoreTerminal() { | |||
| 25 | + | cmd := exec.Command("stty", "-raw", "echo") | |||
| 26 | + | cmd.Stdin = os.Stdin | |||
| 27 | + | _ = cmd.Run() | |||
| 28 | + | } | |||
| 29 | + | ||||
| 8 | func main() { | 30 | func main() { | ||
| 31 | + | // Port = os.Getenv("PORT") or fallback to default port (1323) | |||
| 32 | + | port := os.Getenv("PORT") | |||
| 33 | + | if port == "" { | |||
| 34 | + | port = "1323" | |||
| 35 | + | } | |||
| 36 | + | ||||
| 9 | // Initialize Gordon | 37 | // Initialize Gordon | ||
| 10 | gordon, err := app.NewApp() | 38 | gordon, err := app.NewApp() | ||
| 11 | if err != nil { | 39 | if err != nil { | ||
| @@ -14,13 +42,50 @@ func main() { | |||||
| 14 | return | 42 | return | ||
| 15 | } | 43 | } | ||
| 16 | 44 | ||||
| 17 | - | // Initialize echo server with routes and middlewares | 45 | + | docker.ListDocker() // List docker containers |
| 18 | - | e := routes.NewRouter(gordon) // Pass the entire 'App' instance | 46 | + | |
| 47 | + | e := echo.New() | |||
| 48 | + | e.HideBanner = true | |||
| 49 | + | e.HidePort = true | |||
| 50 | + | e.Logger = utils.NewEchoLoggerWrapper(gordon.APPLogger) | |||
| 51 | + | e = routes.ConfigureRouter(e, gordon) | |||
| 52 | + | ||||
| 53 | + | // Start the server in a separate goroutine | |||
| 54 | + | go func() { | |||
| 55 | + | gordon.APPLogger.Info().Msgf("Server is listening on port %s", port) | |||
| 56 | + | if err := e.Start(":" + port); err != nil && err != http.ErrServerClosed { | |||
| 57 | + | gordon.APPLogger.Fatal().Err(err).Msg("Failed to start the server") | |||
| 58 | + | } | |||
| 59 | + | }() | |||
| 60 | + | ||||
| 61 | + | // Catch SIGINT and SIGTERM signals without raw mode initially | |||
| 62 | + | c := make(chan os.Signal, 1) | |||
| 63 | + | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |||
| 64 | + | <-c | |||
| 65 | + | ||||
| 66 | + | gordon.APPLogger.Warn().Msg("Graceful shutdown initiated") | |||
| 67 | + | gordon.APPLogger.Warn().Msg("Server will shutdown in 5 seconds press ENTER to cancel") | |||
| 68 | + | ||||
| 69 | + | // Now, set raw mode | |||
| 70 | + | setRawMode() | |||
| 71 | + | defer restoreTerminal() // Ensure terminal is restored even if program exits unexpectedly | |||
| 72 | + | ||||
| 73 | + | // Channel to signal a key press | |||
| 74 | + | keypress := make(chan struct{}) | |||
| 75 | + | ||||
| 76 | + | // Goroutine to listen for a key press | |||
| 77 | + | go func() { | |||
| 78 | + | var b []byte = make([]byte, 1) | |||
| 79 | + | os.Stdin.Read(b) | |||
| 80 | + | keypress <- struct{}{} | |||
| 81 | + | }() | |||
| 19 | 82 | ||||
| 20 | - | if err := e.Start(":1323"); err != nil { | 83 | + | // Wait for either a key press or the 5-second timer |
| 21 | - | gordon.APPLogger.Error().Err(err).Msg("Failed to start the server") | 84 | + | select { |
| 85 | + | case <-time.After(5 * time.Second): | |||
| 86 | + | gordon.APPLogger.Info().Msg("Server shutdown") | |||
| 87 | + | case <-keypress: | |||
| 88 | + | gordon.APPLogger.Info().Msg("Shutdown cancelled by user") | |||
| 22 | } | 89 | } | ||
| 23 | 90 | ||||
| 24 | - | // Graceful shutdown | |||
| 25 | - | gordon.APPLogger.Info().Msg("Server is shutting down...") | |||
| 26 | } | 91 | } | ||
M go.mod +11 -0
| @@ -9,9 +9,20 @@ require ( | |||
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | 11 | require ( | |
| 12 | + | github.com/Microsoft/go-winio v0.6.1 // indirect | |
| 12 | 13 | github.com/andybalholm/cascadia v1.3.1 // indirect | |
| 14 | + | github.com/docker/distribution v2.8.2+incompatible // indirect | |
| 15 | + | github.com/docker/docker v24.0.5+incompatible // indirect | |
| 16 | + | github.com/docker/go-connections v0.4.0 // indirect | |
| 17 | + | github.com/docker/go-units v0.5.0 // indirect | |
| 18 | + | github.com/gogo/protobuf v1.3.2 // indirect | |
| 13 | 19 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | |
| 20 | + | github.com/opencontainers/go-digest v1.0.0 // indirect | |
| 21 | + | github.com/opencontainers/image-spec v1.0.2 // indirect | |
| 22 | + | github.com/pkg/errors v0.9.1 // indirect | |
| 23 | + | golang.org/x/mod v0.8.0 // indirect | |
| 14 | 24 | golang.org/x/time v0.3.0 // indirect | |
| 25 | + | golang.org/x/tools v0.6.0 // indirect | |
| 15 | 26 | ) | |
| 16 | 27 | ||
| 17 | 28 | require ( | |
| @@ -9,9 +9,20 @@ require ( | |||||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | require ( | 11 | require ( | ||
| 12 | + | github.com/Microsoft/go-winio v0.6.1 // indirect | |||
| 12 | github.com/andybalholm/cascadia v1.3.1 // indirect | 13 | github.com/andybalholm/cascadia v1.3.1 // indirect | ||
| 14 | + | github.com/docker/distribution v2.8.2+incompatible // indirect | |||
| 15 | + | github.com/docker/docker v24.0.5+incompatible // indirect | |||
| 16 | + | github.com/docker/go-connections v0.4.0 // indirect | |||
| 17 | + | github.com/docker/go-units v0.5.0 // indirect | |||
| 18 | + | github.com/gogo/protobuf v1.3.2 // indirect | |||
| 13 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | 19 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||
| 20 | + | github.com/opencontainers/go-digest v1.0.0 // indirect | |||
| 21 | + | github.com/opencontainers/image-spec v1.0.2 // indirect | |||
| 22 | + | github.com/pkg/errors v0.9.1 // indirect | |||
| 23 | + | golang.org/x/mod v0.8.0 // indirect | |||
| 14 | golang.org/x/time v0.3.0 // indirect | 24 | golang.org/x/time v0.3.0 // indirect | ||
| 25 | + | golang.org/x/tools v0.6.0 // indirect | |||
| 15 | ) | 26 | ) | ||
| 16 | 27 | ||||
| 17 | require ( | 28 | require ( | ||
M go.sum +41 -0 generated file: collapsed
M internal/app/app.go +13 -30
| @@ -2,64 +2,47 @@ package app | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "html/template" | |
| 5 | + | "io" | |
| 5 | 6 | "os" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/rs/zerolog" | |
| 8 | 9 | "gogs.bnema.dev/gordon-echo/pkg/utils" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | - | // Define the main app struct | |
| 12 | 12 | type App struct { | |
| 13 | - | // Define the app logger | |
| 14 | - | APPLogger *utils.Logger | |
| 15 | - | // Define the HTTP logger | |
| 13 | + | APPLogger *utils.Logger | |
| 16 | 14 | HTTPLogger *utils.Logger | |
| 17 | - | // Define the app config | |
| 18 | - | Template *template.Template | |
| 19 | - | // Define the app renderer | |
| 20 | - | Renderer *utils.Renderer | |
| 21 | - | // Define the app router | |
| 15 | + | Template *template.Template | |
| 16 | + | Renderer *utils.Renderer | |
| 22 | 17 | } | |
| 23 | 18 | ||
| 24 | 19 | func NewApp() (*App, error) { | |
| 25 | 20 | app := &App{} | |
| 26 | 21 | ||
| 27 | 22 | // Initialize the general application logger | |
| 28 | - | AppLogger, err := initializeAppLogger() | |
| 29 | - | if err != nil { | |
| 30 | - | return nil, err | |
| 31 | - | } | |
| 32 | - | app.APPLogger = &utils.Logger{Logger: AppLogger} | |
| 33 | - | ||
| 23 | + | app.APPLogger = utils.NewLogger().SetOutput(initializeAppLoggerOutput()) | |
| 34 | 24 | // Initialize the HTTP logger | |
| 35 | - | HttpLogger, err := initializeHTTPLogger() | |
| 36 | - | if err != nil { | |
| 37 | - | return nil, err | |
| 38 | - | } | |
| 39 | - | app.HTTPLogger = &utils.Logger{Logger: HttpLogger} | |
| 25 | + | app.HTTPLogger = utils.NewLogger().SetOutput(initializeHTTPLoggerOutput()) | |
| 40 | 26 | ||
| 41 | 27 | return app, nil | |
| 42 | 28 | } | |
| 43 | - | func initializeAppLogger() (zerolog.Logger, error) { | |
| 29 | + | ||
| 30 | + | func initializeAppLoggerOutput() io.Writer { | |
| 44 | 31 | appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 45 | 32 | if err != nil { | |
| 46 | - | return zerolog.Logger{}, err | |
| 33 | + | panic(err) | |
| 47 | 34 | } | |
| 48 | - | ||
| 49 | 35 | appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |
| 50 | - | appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) | |
| 51 | - | return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger(), nil | |
| 36 | + | return zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) | |
| 52 | 37 | } | |
| 53 | 38 | ||
| 54 | - | func initializeHTTPLogger() (zerolog.Logger, error) { | |
| 39 | + | func initializeHTTPLoggerOutput() io.Writer { | |
| 55 | 40 | httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 56 | 41 | if err != nil { | |
| 57 | - | return zerolog.Logger{}, err | |
| 42 | + | panic(err) | |
| 58 | 43 | } | |
| 59 | - | ||
| 60 | 44 | httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |
| 61 | - | httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) | |
| 62 | - | return zerolog.New(httpMulti).With().Timestamp().Logger(), nil | |
| 45 | + | return zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) | |
| 63 | 46 | } | |
| 64 | 47 | ||
| 65 | 48 | func SetupLogging() { | |
| @@ -2,64 +2,47 @@ package app | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "html/template" | 4 | "html/template" | ||
| 5 | + | "io" | |||
| 5 | "os" | 6 | "os" | ||
| 6 | 7 | ||||
| 7 | "github.com/rs/zerolog" | 8 | "github.com/rs/zerolog" | ||
| 8 | "gogs.bnema.dev/gordon-echo/pkg/utils" | 9 | "gogs.bnema.dev/gordon-echo/pkg/utils" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | - | // Define the main app struct | |||
| 12 | type App struct { | 12 | type App struct { | ||
| 13 | - | // Define the app logger | 13 | + | APPLogger *utils.Logger |
| 14 | - | APPLogger *utils.Logger | |||
| 15 | - | // Define the HTTP logger | |||
| 16 | HTTPLogger *utils.Logger | 14 | HTTPLogger *utils.Logger | ||
| 17 | - | // Define the app config | 15 | + | Template *template.Template |
| 18 | - | Template *template.Template | 16 | + | Renderer *utils.Renderer |
| 19 | - | // Define the app renderer | |||
| 20 | - | Renderer *utils.Renderer | |||
| 21 | - | // Define the app router | |||
| 22 | } | 17 | } | ||
| 23 | 18 | ||||
| 24 | func NewApp() (*App, error) { | 19 | func NewApp() (*App, error) { | ||
| 25 | app := &App{} | 20 | app := &App{} | ||
| 26 | 21 | ||||
| 27 | // Initialize the general application logger | 22 | // Initialize the general application logger | ||
| 28 | - | AppLogger, err := initializeAppLogger() | 23 | + | app.APPLogger = utils.NewLogger().SetOutput(initializeAppLoggerOutput()) |
| 29 | - | if err != nil { | |||
| 30 | - | return nil, err | |||
| 31 | - | } | |||
| 32 | - | app.APPLogger = &utils.Logger{Logger: AppLogger} | |||
| 33 | - | ||||
| 34 | // Initialize the HTTP logger | 24 | // Initialize the HTTP logger | ||
| 35 | - | HttpLogger, err := initializeHTTPLogger() | 25 | + | app.HTTPLogger = utils.NewLogger().SetOutput(initializeHTTPLoggerOutput()) |
| 36 | - | if err != nil { | |||
| 37 | - | return nil, err | |||
| 38 | - | } | |||
| 39 | - | app.HTTPLogger = &utils.Logger{Logger: HttpLogger} | |||
| 40 | 26 | ||||
| 41 | return app, nil | 27 | return app, nil | ||
| 42 | } | 28 | } | ||
| 43 | - | func initializeAppLogger() (zerolog.Logger, error) { | 29 | + | |
| 30 | + | func initializeAppLoggerOutput() io.Writer { | |||
| 44 | appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | 31 | appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | ||
| 45 | if err != nil { | 32 | if err != nil { | ||
| 46 | - | return zerolog.Logger{}, err | 33 | + | panic(err) |
| 47 | } | 34 | } | ||
| 48 | - | ||||
| 49 | appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | 35 | appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | ||
| 50 | - | appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) | 36 | + | return zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) |
| 51 | - | return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger(), nil | |||
| 52 | } | 37 | } | ||
| 53 | 38 | ||||
| 54 | - | func initializeHTTPLogger() (zerolog.Logger, error) { | 39 | + | func initializeHTTPLoggerOutput() io.Writer { |
| 55 | httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | 40 | httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | ||
| 56 | if err != nil { | 41 | if err != nil { | ||
| 57 | - | return zerolog.Logger{}, err | 42 | + | panic(err) |
| 58 | } | 43 | } | ||
| 59 | - | ||||
| 60 | httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | 44 | httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | ||
| 61 | - | httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) | 45 | + | return zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) |
| 62 | - | return zerolog.New(httpMulti).With().Timestamp().Logger(), nil | |||
| 63 | } | 46 | } | ||
| 64 | 47 | ||||
| 65 | func SetupLogging() { | 48 | func SetupLogging() { | ||
M internal/http/handlers/htmx.go +1 -1
| @@ -11,7 +11,7 @@ import ( | |||
| 11 | 11 | ||
| 12 | 12 | func HTMXHandler(c echo.Context) error { | |
| 13 | 13 | // Get the logger instance | |
| 14 | - | logger := utils.NewLogger().GetTypeLogger(utils.App) | |
| 14 | + | logger := utils.NewLogger() | |
| 15 | 15 | // Detect and extract HTMX data from the request | |
| 16 | 16 | htmxRequest, err := htmx.GetRequest(c) | |
| 17 | 17 | if err != nil { | |
| @@ -11,7 +11,7 @@ import ( | |||||
| 11 | 11 | ||||
| 12 | func HTMXHandler(c echo.Context) error { | 12 | func HTMXHandler(c echo.Context) error { | ||
| 13 | // Get the logger instance | 13 | // Get the logger instance | ||
| 14 | - | logger := utils.NewLogger().GetTypeLogger(utils.App) | 14 | + | logger := utils.NewLogger() |
| 15 | // Detect and extract HTMX data from the request | 15 | // Detect and extract HTMX data from the request | ||
| 16 | htmxRequest, err := htmx.GetRequest(c) | 16 | htmxRequest, err := htmx.GetRequest(c) | ||
| 17 | if err != nil { | 17 | if err != nil { | ||
M internal/http/routes/router.go +2 -3
| @@ -7,8 +7,8 @@ import ( | |||
| 7 | 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | - | func NewRouter(app *app.App) *echo.Echo { | |
| 11 | - | e := echo.New() | |
| 10 | + | func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo { | |
| 11 | + | // The logger is already set in main.go, so we don't need to set it again here. | |
| 12 | 12 | ||
| 13 | 13 | // Register middlewares | |
| 14 | 14 | e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger)) | |
| @@ -19,7 +19,6 @@ func NewRouter(app *app.App) *echo.Echo { | |||
| 19 | 19 | ||
| 20 | 20 | return e | |
| 21 | 21 | } | |
| 22 | - | ||
| 23 | 22 | func bindStaticAdminUI(e *echo.Echo) *echo.Echo { | |
| 24 | 23 | e.GET("/admin", AdminRoute) | |
| 25 | 24 | e.GET("/htmx", handlers.HTMXHandler) | |
| @@ -7,8 +7,8 @@ import ( | |||||
| 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | 7 | "gogs.bnema.dev/gordon-echo/internal/http/middlewares" | ||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | - | func NewRouter(app *app.App) *echo.Echo { | 10 | + | func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo { |
| 11 | - | e := echo.New() | 11 | + | // The logger is already set in main.go, so we don't need to set it again here. |
| 12 | 12 | ||||
| 13 | // Register middlewares | 13 | // Register middlewares | ||
| 14 | e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger)) | 14 | e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger)) | ||
| @@ -19,7 +19,6 @@ func NewRouter(app *app.App) *echo.Echo { | |||||
| 19 | 19 | ||||
| 20 | return e | 20 | return e | ||
| 21 | } | 21 | } | ||
| 22 | - | ||||
| 23 | func bindStaticAdminUI(e *echo.Echo) *echo.Echo { | 22 | func bindStaticAdminUI(e *echo.Echo) *echo.Echo { | ||
| 24 | e.GET("/admin", AdminRoute) | 23 | e.GET("/admin", AdminRoute) | ||
| 25 | e.GET("/htmx", handlers.HTMXHandler) | 24 | e.GET("/htmx", handlers.HTMXHandler) | ||
A pkg/docker/registry.go +26 -0
| @@ -0,0 +1,26 @@ | |||
| 1 | + | package docker | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | ||
| 7 | + | "github.com/docker/docker/api/types" | |
| 8 | + | "github.com/docker/docker/client" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // basic list of containers and images | |
| 12 | + | func ListDocker() { | |
| 13 | + | cli, err := client.NewClientWithOpts(client.FromEnv) | |
| 14 | + | if err != nil { | |
| 15 | + | panic(err) | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) | |
| 19 | + | if err != nil { | |
| 20 | + | panic(err) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | for _, container := range containers { | |
| 24 | + | fmt.Printf("%s %s\n", container.ID[:10], container.Image) | |
| 25 | + | } | |
| 26 | + | } | |
| @@ -0,0 +1,26 @@ | |||||
| 1 | + | package docker | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | ||||
| 7 | + | "github.com/docker/docker/api/types" | |||
| 8 | + | "github.com/docker/docker/client" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // basic list of containers and images | |||
| 12 | + | func ListDocker() { | |||
| 13 | + | cli, err := client.NewClientWithOpts(client.FromEnv) | |||
| 14 | + | if err != nil { | |||
| 15 | + | panic(err) | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) | |||
| 19 | + | if err != nil { | |||
| 20 | + | panic(err) | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | for _, container := range containers { | |||
| 24 | + | fmt.Printf("%s %s\n", container.ID[:10], container.Image) | |||
| 25 | + | } | |||
| 26 | + | } | |||
M pkg/utils/fragments.go +1 -1
| @@ -11,7 +11,7 @@ import ( | |||
| 11 | 11 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | |
| 12 | 12 | func GetHTMLFragmentByID(id string, data interface{}) (string, error) { | |
| 13 | 13 | // 1. Render the template | |
| 14 | - | renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger().GetTypeLogger(App)) | |
| 14 | + | renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger()) | |
| 15 | 15 | if err != nil { | |
| 16 | 16 | return "", fmt.Errorf("failed to get renderer: %w", err) | |
| 17 | 17 | } | |
| @@ -11,7 +11,7 @@ import ( | |||||
| 11 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | 11 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | ||
| 12 | func GetHTMLFragmentByID(id string, data interface{}) (string, error) { | 12 | func GetHTMLFragmentByID(id string, data interface{}) (string, error) { | ||
| 13 | // 1. Render the template | 13 | // 1. Render the template | ||
| 14 | - | renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger().GetTypeLogger(App)) | 14 | + | renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger()) |
| 15 | if err != nil { | 15 | if err != nil { | ||
| 16 | return "", fmt.Errorf("failed to get renderer: %w", err) | 16 | return "", fmt.Errorf("failed to get renderer: %w", err) | ||
| 17 | } | 17 | } | ||
M pkg/utils/logger.go +207 -94
| @@ -1,87 +1,101 @@ | |||
| 1 | 1 | package utils | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "io" | |
| 4 | 6 | "os" | |
| 5 | - | "sync" | |
| 6 | 7 | ||
| 8 | + | "github.com/labstack/gommon/log" | |
| 7 | 9 | "github.com/rs/zerolog" | |
| 8 | 10 | ) | |
| 9 | 11 | ||
| 12 | + | type LogLevel string | |
| 13 | + | ||
| 10 | 14 | const ( | |
| 11 | - | App LoggerType = iota | |
| 12 | - | Access | |
| 13 | - | Database | |
| 14 | - | Admin | |
| 15 | - | User | |
| 16 | - | Auth | |
| 17 | - | Utils | |
| 18 | - | Render | |
| 15 | + | DEBUG LogLevel = "debug" | |
| 16 | + | INFO LogLevel = "info" | |
| 17 | + | WARN LogLevel = "warn" | |
| 18 | + | ERROR LogLevel = "error" | |
| 19 | + | FATAL LogLevel = "fatal" | |
| 20 | + | PANIC LogLevel = "panic" | |
| 19 | 21 | ) | |
| 20 | 22 | ||
| 21 | - | type LoggerType int | |
| 22 | 23 | type Logger struct { | |
| 23 | 24 | zerolog.Logger | |
| 24 | 25 | } | |
| 25 | - | type LogFileManager struct { | |
| 26 | - | files map[LoggerType]*os.File | |
| 27 | - | mu sync.Mutex | |
| 26 | + | ||
| 27 | + | type EchoLoggerWrapper struct { | |
| 28 | + | *Logger | |
| 29 | + | output io.Writer | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | // Map log levels to zerolog log functions | |
| 33 | + | func (l *Logger) getLogFunc(level LogLevel) LogFunc { | |
| 34 | + | switch level { | |
| 35 | + | case DEBUG: | |
| 36 | + | return func() *zerolog.Event { return l.Logger.Debug() } | |
| 37 | + | case INFO: | |
| 38 | + | return func() *zerolog.Event { return l.Logger.Info() } | |
| 39 | + | case WARN: | |
| 40 | + | return func() *zerolog.Event { return l.Logger.Warn() } | |
| 41 | + | case ERROR: | |
| 42 | + | return func() *zerolog.Event { return l.Logger.Error() } | |
| 43 | + | case FATAL: | |
| 44 | + | return func() *zerolog.Event { return l.Logger.Fatal() } | |
| 45 | + | case PANIC: | |
| 46 | + | return func() *zerolog.Event { return l.Logger.Panic() } | |
| 47 | + | default: | |
| 48 | + | return func() *zerolog.Event { return l.Logger.Info() } // default to INFO if unknown level | |
| 49 | + | } | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | // LogFunc is a function that logs a message with optional context. | |
| 53 | + | type LogFunc func() *zerolog.Event | |
| 54 | + | ||
| 55 | + | func (l *Logger) Log(level LogLevel, i ...interface{}) { | |
| 56 | + | logFunc := l.getLogFunc(level) | |
| 57 | + | logFunc().Msg(fmt.Sprint(i...)) | |
| 28 | 58 | } | |
| 29 | 59 | ||
| 30 | - | // NewLogger returns a new logger instance | |
| 31 | 60 | func NewLogger() *Logger { | |
| 32 | 61 | writer := zerolog.ConsoleWriter{Out: os.Stdout} | |
| 33 | - | ||
| 34 | - | // Create a new logger | |
| 35 | 62 | zl := zerolog.New(writer).With().Timestamp().Logger() | |
| 36 | 63 | return &Logger{zl} | |
| 37 | 64 | } | |
| 38 | 65 | ||
| 39 | - | // NewLogFileManager returns a new log file manager | |
| 40 | - | func NewLogFileManager() *LogFileManager { | |
| 41 | - | return &LogFileManager{ | |
| 42 | - | files: make(map[LoggerType]*os.File), | |
| 43 | - | } | |
| 66 | + | func NewEchoLoggerWrapper(l *Logger) *EchoLoggerWrapper { | |
| 67 | + | return &EchoLoggerWrapper{Logger: l, output: os.Stdout} | |
| 44 | 68 | } | |
| 45 | 69 | ||
| 46 | - | // GetTypeLogger returns a logger with the specified type | |
| 47 | - | func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | |
| 48 | - | switch loggerType { | |
| 49 | - | case App: | |
| 50 | - | return &Logger{l.With().Str("type", "app").Logger()} | |
| 51 | - | case Access: | |
| 52 | - | return &Logger{l.With().Str("type", "access").Logger()} | |
| 53 | - | case Database: | |
| 54 | - | return &Logger{l.With().Str("type", "database").Logger()} | |
| 55 | - | case Admin: | |
| 56 | - | return &Logger{l.With().Str("type", "admin").Logger()} | |
| 57 | - | case User: | |
| 58 | - | return &Logger{l.With().Str("type", "user").Logger()} | |
| 59 | - | case Auth: | |
| 60 | - | return &Logger{l.With().Str("type", "auth").Logger()} | |
| 61 | - | default: | |
| 62 | - | return &Logger{l.With().Str("type", "undefined").Logger()} | |
| 63 | - | } | |
| 70 | + | func (l *Logger) SetOutput(w io.Writer) *Logger { | |
| 71 | + | l.Logger = l.Logger.Output(w) | |
| 72 | + | return l | |
| 64 | 73 | } | |
| 65 | 74 | ||
| 66 | - | // GetTypeLogger returns a logger with the specified type | |
| 67 | - | func (m *LogFileManager) OpenLogFile(loggerType LoggerType) (*os.File, error) { | |
| 68 | - | m.mu.Lock() | |
| 69 | - | defer m.mu.Unlock() | |
| 70 | - | ||
| 71 | - | if file, exists := m.files[loggerType]; exists { | |
| 72 | - | return file, nil | |
| 73 | - | } | |
| 75 | + | func (l *Logger) SetLevel(level LogLevel) *Logger { | |
| 76 | + | l.Logger = l.Logger.Level(getZerologLevel(level)) | |
| 77 | + | return l | |
| 78 | + | } | |
| 74 | 79 | ||
| 75 | - | logFilePath := GetLogFileForType(loggerType) | |
| 76 | - | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 77 | - | if err != nil { | |
| 78 | - | return nil, err | |
| 80 | + | func getZerologLevel(level LogLevel) zerolog.Level { | |
| 81 | + | switch level { | |
| 82 | + | case DEBUG: | |
| 83 | + | return zerolog.DebugLevel | |
| 84 | + | case INFO: | |
| 85 | + | return zerolog.InfoLevel | |
| 86 | + | case WARN: | |
| 87 | + | return zerolog.WarnLevel | |
| 88 | + | case ERROR: | |
| 89 | + | return zerolog.ErrorLevel | |
| 90 | + | case FATAL: | |
| 91 | + | return zerolog.FatalLevel | |
| 92 | + | case PANIC: | |
| 93 | + | return zerolog.PanicLevel | |
| 94 | + | default: | |
| 95 | + | return zerolog.InfoLevel // default to INFO if unknown level | |
| 79 | 96 | } | |
| 80 | - | m.files[loggerType] = logFile | |
| 81 | - | return logFile, nil | |
| 82 | 97 | } | |
| 83 | 98 | ||
| 84 | - | // CreateLogsDir creates the logs directory if it doesn't exist | |
| 85 | 99 | func CreateLogsDir() error { | |
| 86 | 100 | if _, err := os.Stat("logs"); os.IsNotExist(err) { | |
| 87 | 101 | err := os.Mkdir("logs", 0755) | |
| @@ -92,51 +106,150 @@ func CreateLogsDir() error { | |||
| 92 | 106 | return nil | |
| 93 | 107 | } | |
| 94 | 108 | ||
| 95 | - | // GetLogFileForType returns the log file for the specified logger type | |
| 96 | - | func GetLogFileForType(loggerType LoggerType) string { | |
| 97 | - | switch loggerType { | |
| 98 | - | case App, Database, Utils, Render: | |
| 99 | - | return "logs/app.log" | |
| 100 | - | case Access, Admin, User, Auth: | |
| 101 | - | return "logs/http.log" | |
| 102 | - | default: | |
| 103 | - | return "logs/undefined.log" | |
| 104 | - | } | |
| 109 | + | func (l *EchoLoggerWrapper) Output() io.Writer { | |
| 110 | + | return l.output | |
| 105 | 111 | } | |
| 106 | 112 | ||
| 107 | - | // CreateLogFile creates a log file for each logger type | |
| 108 | - | func CreateLogFile(loggerType LoggerType) (*os.File, error) { | |
| 109 | - | logFilePath := GetLogFileForType(loggerType) | |
| 110 | - | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 111 | - | if err != nil { | |
| 112 | - | return nil, err | |
| 113 | - | } | |
| 114 | - | return logFile, nil | |
| 113 | + | func (l *EchoLoggerWrapper) SetOutput(w io.Writer) { | |
| 114 | + | l.output = w | |
| 115 | 115 | } | |
| 116 | 116 | ||
| 117 | - | // GetLogLevel returns the log level for the specified logger type | |
| 118 | - | func GetLogLevel(loggerType LoggerType) zerolog.Level { | |
| 119 | - | switch loggerType { | |
| 120 | - | case App, Database, Utils, Render: | |
| 121 | - | return zerolog.InfoLevel | |
| 122 | - | case Access, Admin, User, Auth: | |
| 123 | - | return zerolog.DebugLevel | |
| 124 | - | default: | |
| 125 | - | return zerolog.DebugLevel | |
| 126 | - | } | |
| 117 | + | func (l *EchoLoggerWrapper) Prefix() string { | |
| 118 | + | return "" | |
| 127 | 119 | } | |
| 128 | 120 | ||
| 129 | - | // CloseLogFile closes the log file for the specified logger type | |
| 130 | - | func (m *LogFileManager) CloseLogFile(loggerType LoggerType) error { | |
| 131 | - | m.mu.Lock() | |
| 132 | - | defer m.mu.Unlock() | |
| 121 | + | func (l *EchoLoggerWrapper) SetPrefix(p string) { | |
| 122 | + | // do nothing | |
| 123 | + | } | |
| 133 | 124 | ||
| 134 | - | if file, exists := m.files[loggerType]; exists { | |
| 135 | - | err := file.Close() | |
| 136 | - | if err != nil { | |
| 137 | - | return err | |
| 138 | - | } | |
| 139 | - | delete(m.files, loggerType) | |
| 140 | - | } | |
| 141 | - | return nil | |
| 125 | + | func (l *EchoLoggerWrapper) Level() log.Lvl { | |
| 126 | + | return log.Lvl(l.Logger.GetLevel()) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // SetHeader | |
| 130 | + | func (l *EchoLoggerWrapper) SetHeader(h string) { | |
| 131 | + | // do nothing | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | func (l *EchoLoggerWrapper) SetLevel(log.Lvl) { | |
| 135 | + | // do nothing | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | // SetEvent creates a new event with the given level and message. | |
| 139 | + | func (l *EchoLoggerWrapper) SetEvent(level zerolog.Level, msg string) *zerolog.Event { | |
| 140 | + | //cannot use utils.NewEchoLoggerWrapper(app.HTTPLogger) (value of type *utils.EchoLoggerWrapper) as echo.Logger value in assignment: *utils.EchoLoggerWrapper does not implement echo.Logger (wrong type for method Error) | |
| 141 | + | return l.Logger.WithLevel(level).Str("message", msg) | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | func (l *EchoLoggerWrapper) OutputStdout() bool { | |
| 145 | + | return true | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | // Helper function for methods that accept variadic slice of interfaces | |
| 149 | + | func (l *EchoLoggerWrapper) LogWithArgs(level LogLevel, i ...interface{}) { | |
| 150 | + | l.Logger.Log(level, i...) | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | // Helper function for methods that accept a format string and arguments | |
| 154 | + | func (l *EchoLoggerWrapper) LogWithFormat(level LogLevel, format string, args ...interface{}) { | |
| 155 | + | l.Logger.Log(level, fmt.Sprintf(format, args...)) | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | // Helper function for methods that accept a log.JSON object | |
| 159 | + | func (l *EchoLoggerWrapper) LogWithJSON(level LogLevel, j log.JSON) { | |
| 160 | + | l.Logger.Log(level, j) | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | // Debug | |
| 164 | + | func (l *EchoLoggerWrapper) Debug(i ...interface{}) { | |
| 165 | + | l.LogWithArgs(DEBUG, i...) | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | func (l *EchoLoggerWrapper) Debugf(format string, args ...interface{}) { | |
| 169 | + | l.LogWithFormat(DEBUG, format, args...) | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | func (l *EchoLoggerWrapper) Debugj(j log.JSON) { | |
| 173 | + | l.LogWithJSON(DEBUG, j) | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | // Info | |
| 177 | + | func (l *EchoLoggerWrapper) Info(i ...interface{}) { | |
| 178 | + | l.LogWithArgs(INFO, i...) | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | func (l *EchoLoggerWrapper) Infof(format string, args ...interface{}) { | |
| 182 | + | l.LogWithFormat(INFO, format, args...) | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func (l *EchoLoggerWrapper) Infoj(j log.JSON) { | |
| 186 | + | l.LogWithJSON(INFO, j) | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | // Warn | |
| 190 | + | ||
| 191 | + | func (l *EchoLoggerWrapper) Warn(i ...interface{}) { | |
| 192 | + | l.LogWithArgs(WARN, i...) | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | func (l *EchoLoggerWrapper) Warnf(format string, args ...interface{}) { | |
| 196 | + | l.LogWithFormat(WARN, format, args...) | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | func (l *EchoLoggerWrapper) Warnj(j log.JSON) { | |
| 200 | + | l.LogWithJSON(WARN, j) | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | // Error | |
| 204 | + | func (l *EchoLoggerWrapper) Error(i ...interface{}) { | |
| 205 | + | l.LogWithArgs(ERROR, i...) | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | func (l *EchoLoggerWrapper) Errorf(format string, args ...interface{}) { | |
| 209 | + | l.LogWithFormat(ERROR, format, args...) | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | func (l *EchoLoggerWrapper) Errorj(j log.JSON) { | |
| 213 | + | l.LogWithJSON(ERROR, j) | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // Fatal | |
| 217 | + | func (l *EchoLoggerWrapper) Fatal(i ...interface{}) { | |
| 218 | + | l.LogWithArgs(FATAL, i...) | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | func (l *EchoLoggerWrapper) Fatalf(format string, args ...interface{}) { | |
| 222 | + | l.LogWithFormat(FATAL, format, args...) | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | func (l *EchoLoggerWrapper) Fatalj(j log.JSON) { | |
| 226 | + | l.LogWithJSON(FATAL, j) | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | // Panic | |
| 230 | + | ||
| 231 | + | func (l *EchoLoggerWrapper) Panic(i ...interface{}) { | |
| 232 | + | l.LogWithArgs(PANIC, i...) | |
| 233 | + | } | |
| 234 | + | ||
| 235 | + | func (l *EchoLoggerWrapper) Panicf(format string, args ...interface{}) { | |
| 236 | + | l.LogWithFormat(PANIC, format, args...) | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | func (l *EchoLoggerWrapper) Panicj(j log.JSON) { | |
| 240 | + | l.LogWithJSON(PANIC, j) | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | ||
| 244 | + | ||
| 245 | + | func (l *EchoLoggerWrapper) Print(i ...interface{}) { | |
| 246 | + | l.LogWithArgs(INFO, i...) | |
| 247 | + | } | |
| 248 | + | ||
| 249 | + | func (l *EchoLoggerWrapper) Printf(format string, args ...interface{}) { | |
| 250 | + | l.LogWithFormat(INFO, format, args...) | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | func (l *EchoLoggerWrapper) Printj(j log.JSON) { | |
| 254 | + | l.LogWithJSON(INFO, j) | |
| 142 | 255 | } | |
| @@ -1,87 +1,101 @@ | |||||
| 1 | package utils | 1 | package utils | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 5 | + | "io" | |||
| 4 | "os" | 6 | "os" | ||
| 5 | - | "sync" | |||
| 6 | 7 | ||||
| 8 | + | "github.com/labstack/gommon/log" | |||
| 7 | "github.com/rs/zerolog" | 9 | "github.com/rs/zerolog" | ||
| 8 | ) | 10 | ) | ||
| 9 | 11 | ||||
| 12 | + | type LogLevel string | |||
| 13 | + | ||||
| 10 | const ( | 14 | const ( | ||
| 11 | - | App LoggerType = iota | 15 | + | DEBUG LogLevel = "debug" |
| 12 | - | Access | 16 | + | INFO LogLevel = "info" |
| 13 | - | Database | 17 | + | WARN LogLevel = "warn" |
| 14 | - | Admin | 18 | + | ERROR LogLevel = "error" |
| 15 | - | User | 19 | + | FATAL LogLevel = "fatal" |
| 16 | - | Auth | 20 | + | PANIC LogLevel = "panic" |
| 17 | - | Utils | |||
| 18 | - | Render | |||
| 19 | ) | 21 | ) | ||
| 20 | 22 | ||||
| 21 | - | type LoggerType int | |||
| 22 | type Logger struct { | 23 | type Logger struct { | ||
| 23 | zerolog.Logger | 24 | zerolog.Logger | ||
| 24 | } | 25 | } | ||
| 25 | - | type LogFileManager struct { | 26 | + | |
| 26 | - | files map[LoggerType]*os.File | 27 | + | type EchoLoggerWrapper struct { |
| 27 | - | mu sync.Mutex | 28 | + | *Logger |
| 29 | + | output io.Writer | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | // Map log levels to zerolog log functions | |||
| 33 | + | func (l *Logger) getLogFunc(level LogLevel) LogFunc { | |||
| 34 | + | switch level { | |||
| 35 | + | case DEBUG: | |||
| 36 | + | return func() *zerolog.Event { return l.Logger.Debug() } | |||
| 37 | + | case INFO: | |||
| 38 | + | return func() *zerolog.Event { return l.Logger.Info() } | |||
| 39 | + | case WARN: | |||
| 40 | + | return func() *zerolog.Event { return l.Logger.Warn() } | |||
| 41 | + | case ERROR: | |||
| 42 | + | return func() *zerolog.Event { return l.Logger.Error() } | |||
| 43 | + | case FATAL: | |||
| 44 | + | return func() *zerolog.Event { return l.Logger.Fatal() } | |||
| 45 | + | case PANIC: | |||
| 46 | + | return func() *zerolog.Event { return l.Logger.Panic() } | |||
| 47 | + | default: | |||
| 48 | + | return func() *zerolog.Event { return l.Logger.Info() } // default to INFO if unknown level | |||
| 49 | + | } | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | // LogFunc is a function that logs a message with optional context. | |||
| 53 | + | type LogFunc func() *zerolog.Event | |||
| 54 | + | ||||
| 55 | + | func (l *Logger) Log(level LogLevel, i ...interface{}) { | |||
| 56 | + | logFunc := l.getLogFunc(level) | |||
| 57 | + | logFunc().Msg(fmt.Sprint(i...)) | |||
| 28 | } | 58 | } | ||
| 29 | 59 | ||||
| 30 | - | // NewLogger returns a new logger instance | |||
| 31 | func NewLogger() *Logger { | 60 | func NewLogger() *Logger { | ||
| 32 | writer := zerolog.ConsoleWriter{Out: os.Stdout} | 61 | writer := zerolog.ConsoleWriter{Out: os.Stdout} | ||
| 33 | - | ||||
| 34 | - | // Create a new logger | |||
| 35 | zl := zerolog.New(writer).With().Timestamp().Logger() | 62 | zl := zerolog.New(writer).With().Timestamp().Logger() | ||
| 36 | return &Logger{zl} | 63 | return &Logger{zl} | ||
| 37 | } | 64 | } | ||
| 38 | 65 | ||||
| 39 | - | // NewLogFileManager returns a new log file manager | 66 | + | func NewEchoLoggerWrapper(l *Logger) *EchoLoggerWrapper { |
| 40 | - | func NewLogFileManager() *LogFileManager { | 67 | + | return &EchoLoggerWrapper{Logger: l, output: os.Stdout} |
| 41 | - | return &LogFileManager{ | |||
| 42 | - | files: make(map[LoggerType]*os.File), | |||
| 43 | - | } | |||
| 44 | } | 68 | } | ||
| 45 | 69 | ||||
| 46 | - | // GetTypeLogger returns a logger with the specified type | 70 | + | func (l *Logger) SetOutput(w io.Writer) *Logger { |
| 47 | - | func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | 71 | + | l.Logger = l.Logger.Output(w) |
| 48 | - | switch loggerType { | 72 | + | return l |
| 49 | - | case App: | |||
| 50 | - | return &Logger{l.With().Str("type", "app").Logger()} | |||
| 51 | - | case Access: | |||
| 52 | - | return &Logger{l.With().Str("type", "access").Logger()} | |||
| 53 | - | case Database: | |||
| 54 | - | return &Logger{l.With().Str("type", "database").Logger()} | |||
| 55 | - | case Admin: | |||
| 56 | - | return &Logger{l.With().Str("type", "admin").Logger()} | |||
| 57 | - | case User: | |||
| 58 | - | return &Logger{l.With().Str("type", "user").Logger()} | |||
| 59 | - | case Auth: | |||
| 60 | - | return &Logger{l.With().Str("type", "auth").Logger()} | |||
| 61 | - | default: | |||
| 62 | - | return &Logger{l.With().Str("type", "undefined").Logger()} | |||
| 63 | - | } | |||
| 64 | } | 73 | } | ||
| 65 | 74 | ||||
| 66 | - | // GetTypeLogger returns a logger with the specified type | 75 | + | func (l *Logger) SetLevel(level LogLevel) *Logger { |
| 67 | - | func (m *LogFileManager) OpenLogFile(loggerType LoggerType) (*os.File, error) { | 76 | + | l.Logger = l.Logger.Level(getZerologLevel(level)) |
| 68 | - | m.mu.Lock() | 77 | + | return l |
| 69 | - | defer m.mu.Unlock() | 78 | + | } |
| 70 | - | ||||
| 71 | - | if file, exists := m.files[loggerType]; exists { | |||
| 72 | - | return file, nil | |||
| 73 | - | } | |||
| 74 | 79 | ||||
| 75 | - | logFilePath := GetLogFileForType(loggerType) | 80 | + | func getZerologLevel(level LogLevel) zerolog.Level { |
| 76 | - | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | 81 | + | switch level { |
| 77 | - | if err != nil { | 82 | + | case DEBUG: |
| 78 | - | return nil, err | 83 | + | return zerolog.DebugLevel |
| 84 | + | case INFO: | |||
| 85 | + | return zerolog.InfoLevel | |||
| 86 | + | case WARN: | |||
| 87 | + | return zerolog.WarnLevel | |||
| 88 | + | case ERROR: | |||
| 89 | + | return zerolog.ErrorLevel | |||
| 90 | + | case FATAL: | |||
| 91 | + | return zerolog.FatalLevel | |||
| 92 | + | case PANIC: | |||
| 93 | + | return zerolog.PanicLevel | |||
| 94 | + | default: | |||
| 95 | + | return zerolog.InfoLevel // default to INFO if unknown level | |||
| 79 | } | 96 | } | ||
| 80 | - | m.files[loggerType] = logFile | |||
| 81 | - | return logFile, nil | |||
| 82 | } | 97 | } | ||
| 83 | 98 | ||||
| 84 | - | // CreateLogsDir creates the logs directory if it doesn't exist | |||
| 85 | func CreateLogsDir() error { | 99 | func CreateLogsDir() error { | ||
| 86 | if _, err := os.Stat("logs"); os.IsNotExist(err) { | 100 | if _, err := os.Stat("logs"); os.IsNotExist(err) { | ||
| 87 | err := os.Mkdir("logs", 0755) | 101 | err := os.Mkdir("logs", 0755) | ||
| @@ -92,51 +106,150 @@ func CreateLogsDir() error { | |||||
| 92 | return nil | 106 | return nil | ||
| 93 | } | 107 | } | ||
| 94 | 108 | ||||
| 95 | - | // GetLogFileForType returns the log file for the specified logger type | 109 | + | func (l *EchoLoggerWrapper) Output() io.Writer { |
| 96 | - | func GetLogFileForType(loggerType LoggerType) string { | 110 | + | return l.output |
| 97 | - | switch loggerType { | |||
| 98 | - | case App, Database, Utils, Render: | |||
| 99 | - | return "logs/app.log" | |||
| 100 | - | case Access, Admin, User, Auth: | |||
| 101 | - | return "logs/http.log" | |||
| 102 | - | default: | |||
| 103 | - | return "logs/undefined.log" | |||
| 104 | - | } | |||
| 105 | } | 111 | } | ||
| 106 | 112 | ||||
| 107 | - | // CreateLogFile creates a log file for each logger type | 113 | + | func (l *EchoLoggerWrapper) SetOutput(w io.Writer) { |
| 108 | - | func CreateLogFile(loggerType LoggerType) (*os.File, error) { | 114 | + | l.output = w |
| 109 | - | logFilePath := GetLogFileForType(loggerType) | |||
| 110 | - | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |||
| 111 | - | if err != nil { | |||
| 112 | - | return nil, err | |||
| 113 | - | } | |||
| 114 | - | return logFile, nil | |||
| 115 | } | 115 | } | ||
| 116 | 116 | ||||
| 117 | - | // GetLogLevel returns the log level for the specified logger type | 117 | + | func (l *EchoLoggerWrapper) Prefix() string { |
| 118 | - | func GetLogLevel(loggerType LoggerType) zerolog.Level { | 118 | + | return "" |
| 119 | - | switch loggerType { | |||
| 120 | - | case App, Database, Utils, Render: | |||
| 121 | - | return zerolog.InfoLevel | |||
| 122 | - | case Access, Admin, User, Auth: | |||
| 123 | - | return zerolog.DebugLevel | |||
| 124 | - | default: | |||
| 125 | - | return zerolog.DebugLevel | |||
| 126 | - | } | |||
| 127 | } | 119 | } | ||
| 128 | 120 | ||||
| 129 | - | // CloseLogFile closes the log file for the specified logger type | 121 | + | func (l *EchoLoggerWrapper) SetPrefix(p string) { |
| 130 | - | func (m *LogFileManager) CloseLogFile(loggerType LoggerType) error { | 122 | + | // do nothing |
| 131 | - | m.mu.Lock() | 123 | + | } |
| 132 | - | defer m.mu.Unlock() | |||
| 133 | 124 | ||||
| 134 | - | if file, exists := m.files[loggerType]; exists { | 125 | + | func (l *EchoLoggerWrapper) Level() log.Lvl { |
| 135 | - | err := file.Close() | 126 | + | return log.Lvl(l.Logger.GetLevel()) |
| 136 | - | if err != nil { | 127 | + | } |
| 137 | - | return err | 128 | + | |
| 138 | - | } | 129 | + | // SetHeader |
| 139 | - | delete(m.files, loggerType) | 130 | + | func (l *EchoLoggerWrapper) SetHeader(h string) { |
| 140 | - | } | 131 | + | // do nothing |
| 141 | - | return nil | 132 | + | } |
| 133 | + | ||||
| 134 | + | func (l *EchoLoggerWrapper) SetLevel(log.Lvl) { | |||
| 135 | + | // do nothing | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | // SetEvent creates a new event with the given level and message. | |||
| 139 | + | func (l *EchoLoggerWrapper) SetEvent(level zerolog.Level, msg string) *zerolog.Event { | |||
| 140 | + | //cannot use utils.NewEchoLoggerWrapper(app.HTTPLogger) (value of type *utils.EchoLoggerWrapper) as echo.Logger value in assignment: *utils.EchoLoggerWrapper does not implement echo.Logger (wrong type for method Error) | |||
| 141 | + | return l.Logger.WithLevel(level).Str("message", msg) | |||
| 142 | + | } | |||
| 143 | + | ||||
| 144 | + | func (l *EchoLoggerWrapper) OutputStdout() bool { | |||
| 145 | + | return true | |||
| 146 | + | } | |||
| 147 | + | ||||
| 148 | + | // Helper function for methods that accept variadic slice of interfaces | |||
| 149 | + | func (l *EchoLoggerWrapper) LogWithArgs(level LogLevel, i ...interface{}) { | |||
| 150 | + | l.Logger.Log(level, i...) | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | // Helper function for methods that accept a format string and arguments | |||
| 154 | + | func (l *EchoLoggerWrapper) LogWithFormat(level LogLevel, format string, args ...interface{}) { | |||
| 155 | + | l.Logger.Log(level, fmt.Sprintf(format, args...)) | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | + | // Helper function for methods that accept a log.JSON object | |||
| 159 | + | func (l *EchoLoggerWrapper) LogWithJSON(level LogLevel, j log.JSON) { | |||
| 160 | + | l.Logger.Log(level, j) | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | // Debug | |||
| 164 | + | func (l *EchoLoggerWrapper) Debug(i ...interface{}) { | |||
| 165 | + | l.LogWithArgs(DEBUG, i...) | |||
| 166 | + | } | |||
| 167 | + | ||||
| 168 | + | func (l *EchoLoggerWrapper) Debugf(format string, args ...interface{}) { | |||
| 169 | + | l.LogWithFormat(DEBUG, format, args...) | |||
| 170 | + | } | |||
| 171 | + | ||||
| 172 | + | func (l *EchoLoggerWrapper) Debugj(j log.JSON) { | |||
| 173 | + | l.LogWithJSON(DEBUG, j) | |||
| 174 | + | } | |||
| 175 | + | ||||
| 176 | + | // Info | |||
| 177 | + | func (l *EchoLoggerWrapper) Info(i ...interface{}) { | |||
| 178 | + | l.LogWithArgs(INFO, i...) | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | func (l *EchoLoggerWrapper) Infof(format string, args ...interface{}) { | |||
| 182 | + | l.LogWithFormat(INFO, format, args...) | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | func (l *EchoLoggerWrapper) Infoj(j log.JSON) { | |||
| 186 | + | l.LogWithJSON(INFO, j) | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | // Warn | |||
| 190 | + | ||||
| 191 | + | func (l *EchoLoggerWrapper) Warn(i ...interface{}) { | |||
| 192 | + | l.LogWithArgs(WARN, i...) | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | func (l *EchoLoggerWrapper) Warnf(format string, args ...interface{}) { | |||
| 196 | + | l.LogWithFormat(WARN, format, args...) | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | func (l *EchoLoggerWrapper) Warnj(j log.JSON) { | |||
| 200 | + | l.LogWithJSON(WARN, j) | |||
| 201 | + | } | |||
| 202 | + | ||||
| 203 | + | // Error | |||
| 204 | + | func (l *EchoLoggerWrapper) Error(i ...interface{}) { | |||
| 205 | + | l.LogWithArgs(ERROR, i...) | |||
| 206 | + | } | |||
| 207 | + | ||||
| 208 | + | func (l *EchoLoggerWrapper) Errorf(format string, args ...interface{}) { | |||
| 209 | + | l.LogWithFormat(ERROR, format, args...) | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | func (l *EchoLoggerWrapper) Errorj(j log.JSON) { | |||
| 213 | + | l.LogWithJSON(ERROR, j) | |||
| 214 | + | } | |||
| 215 | + | ||||
| 216 | + | // Fatal | |||
| 217 | + | func (l *EchoLoggerWrapper) Fatal(i ...interface{}) { | |||
| 218 | + | l.LogWithArgs(FATAL, i...) | |||
| 219 | + | } | |||
| 220 | + | ||||
| 221 | + | func (l *EchoLoggerWrapper) Fatalf(format string, args ...interface{}) { | |||
| 222 | + | l.LogWithFormat(FATAL, format, args...) | |||
| 223 | + | } | |||
| 224 | + | ||||
| 225 | + | func (l *EchoLoggerWrapper) Fatalj(j log.JSON) { | |||
| 226 | + | l.LogWithJSON(FATAL, j) | |||
| 227 | + | } | |||
| 228 | + | ||||
| 229 | + | // Panic | |||
| 230 | + | ||||
| 231 | + | func (l *EchoLoggerWrapper) Panic(i ...interface{}) { | |||
| 232 | + | l.LogWithArgs(PANIC, i...) | |||
| 233 | + | } | |||
| 234 | + | ||||
| 235 | + | func (l *EchoLoggerWrapper) Panicf(format string, args ...interface{}) { | |||
| 236 | + | l.LogWithFormat(PANIC, format, args...) | |||
| 237 | + | } | |||
| 238 | + | ||||
| 239 | + | func (l *EchoLoggerWrapper) Panicj(j log.JSON) { | |||
| 240 | + | l.LogWithJSON(PANIC, j) | |||
| 241 | + | } | |||
| 242 | + | ||||
| 243 | + | ||||
| 244 | + | ||||
| 245 | + | func (l *EchoLoggerWrapper) Print(i ...interface{}) { | |||
| 246 | + | l.LogWithArgs(INFO, i...) | |||
| 247 | + | } | |||
| 248 | + | ||||
| 249 | + | func (l *EchoLoggerWrapper) Printf(format string, args ...interface{}) { | |||
| 250 | + | l.LogWithFormat(INFO, format, args...) | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | func (l *EchoLoggerWrapper) Printj(j log.JSON) { | |||
| 254 | + | l.LogWithJSON(INFO, j) | |||
| 142 | } | 255 | } | ||