detached 8aa346e

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
8aa346e73e185d50693f815d385f14226e642c4e
author
bnema · · 2023-09-04 10:42 UTC
parent
b2b2266
signature
unsigned
9 files · +373 -135
M cmd/main.go +71 -6
@@ -1,11 +1,39 @@
11 package main
22
33 import (
4+ "net/http"
5+ "os"
6+ "os/exec"
7+ "os/signal"
8+ "syscall"
9+ "time"
10+
11+ "github.com/labstack/echo/v4"
412 "gogs.bnema.dev/gordon-echo/internal/app"
513 "gogs.bnema.dev/gordon-echo/internal/http/routes"
14+ "gogs.bnema.dev/gordon-echo/pkg/docker"
15+ "gogs.bnema.dev/gordon-echo/pkg/utils"
616 )
717
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+
830 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+
937 // Initialize Gordon
1038 gordon, err := app.NewApp()
1139 if err != nil {
@@ -14,13 +42,50 @@ func main() {
1442 return
1543 }
1644
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+ }()
1982
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")
2289 }
2390
24- // Graceful shutdown
25- gordon.APPLogger.Info().Msg("Server is shutting down...")
2691 }
@@ -1,11 +1,39 @@
1 package main1 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 Gordon37 // 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 return42 return
15 }43 }
16 44
17- // Initialize echo server with routes and middlewares45+ docker.ListDocker() // List docker containers
18- e := routes.NewRouter(gordon) // Pass the entire 'App' instance46+
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 (
99 )
1010
1111 require (
12+ github.com/Microsoft/go-winio v0.6.1 // indirect
1213 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
1319 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
1424 golang.org/x/time v0.3.0 // indirect
25+ golang.org/x/tools v0.6.0 // indirect
1526 )
1627
1728 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 // indirect13 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 // indirect19 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 // indirect24 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
22
33 import (
44 "html/template"
5+ "io"
56 "os"
67
78 "github.com/rs/zerolog"
89 "gogs.bnema.dev/gordon-echo/pkg/utils"
910 )
1011
11-// Define the main app struct
1212 type App struct {
13- // Define the app logger
14- APPLogger *utils.Logger
15- // Define the HTTP logger
13+ APPLogger *utils.Logger
1614 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
2217 }
2318
2419 func NewApp() (*App, error) {
2520 app := &App{}
2621
2722 // 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())
3424 // 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())
4026
4127 return app, nil
4228 }
43-func initializeAppLogger() (zerolog.Logger, error) {
29+
30+func initializeAppLoggerOutput() io.Writer {
4431 appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
4532 if err != nil {
46- return zerolog.Logger{}, err
33+ panic(err)
4734 }
48-
4935 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)
5237 }
5338
54-func initializeHTTPLogger() (zerolog.Logger, error) {
39+func initializeHTTPLoggerOutput() io.Writer {
5540 httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
5641 if err != nil {
57- return zerolog.Logger{}, err
42+ panic(err)
5843 }
59-
6044 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)
6346 }
6447
6548 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 logger13+ APPLogger *utils.Logger
14- APPLogger *utils.Logger
15- // Define the HTTP logger
16 HTTPLogger *utils.Logger14 HTTPLogger *utils.Logger
17- // Define the app config15+ Template *template.Template
18- Template *template.Template16+ 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 logger22 // 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 logger24 // 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, nil27 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{}, err33+ 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{}, err42+ 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 (
1111
1212 func HTMXHandler(c echo.Context) error {
1313 // Get the logger instance
14- logger := utils.NewLogger().GetTypeLogger(utils.App)
14+ logger := utils.NewLogger()
1515 // Detect and extract HTMX data from the request
1616 htmxRequest, err := htmx.GetRequest(c)
1717 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 instance13 // Get the logger instance
14- logger := utils.NewLogger().GetTypeLogger(utils.App)14+ logger := utils.NewLogger()
15 // Detect and extract HTMX data from the request15 // 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 (
77 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
88 )
99
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.
1212
1313 // Register middlewares
1414 e.Use(middlewares.NewRequestLoggerMiddleware(app.HTTPLogger.Logger))
@@ -19,7 +19,6 @@ func NewRouter(app *app.App) *echo.Echo {
1919
2020 return e
2121 }
22-
2322 func bindStaticAdminUI(e *echo.Echo) *echo.Echo {
2423 e.GET("/admin", AdminRoute)
2524 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 middlewares13 // 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 e20 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 (
1111 // GetHTMLFragmentByID returns the HTML fragment with the specified id
1212 func GetHTMLFragmentByID(id string, data interface{}) (string, error) {
1313 // 1. Render the template
14- renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger().GetTypeLogger(App))
14+ renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger())
1515 if err != nil {
1616 return "", fmt.Errorf("failed to get renderer: %w", err)
1717 }
@@ -11,7 +11,7 @@ import (
11 // GetHTMLFragmentByID returns the HTML fragment with the specified id11 // 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 template13 // 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 @@
11 package utils
22
33 import (
4+ "fmt"
5+ "io"
46 "os"
5- "sync"
67
8+ "github.com/labstack/gommon/log"
79 "github.com/rs/zerolog"
810 )
911
12+type LogLevel string
13+
1014 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"
1921 )
2022
21-type LoggerType int
2223 type Logger struct {
2324 zerolog.Logger
2425 }
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...))
2858 }
2959
30-// NewLogger returns a new logger instance
3160 func NewLogger() *Logger {
3261 writer := zerolog.ConsoleWriter{Out: os.Stdout}
33-
34- // Create a new logger
3562 zl := zerolog.New(writer).With().Timestamp().Logger()
3663 return &Logger{zl}
3764 }
3865
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}
4468 }
4569
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
6473 }
6574
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+}
7479
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
7996 }
80- m.files[loggerType] = logFile
81- return logFile, nil
8297 }
8398
84-// CreateLogsDir creates the logs directory if it doesn't exist
8599 func CreateLogsDir() error {
86100 if _, err := os.Stat("logs"); os.IsNotExist(err) {
87101 err := os.Mkdir("logs", 0755)
@@ -92,51 +106,150 @@ func CreateLogsDir() error {
92106 return nil
93107 }
94108
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
105111 }
106112
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
115115 }
116116
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 ""
127119 }
128120
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+}
133124
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+// Print
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)
142255 }
@@ -1,87 +1,101 @@
1 package utils1 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 = iota15+ DEBUG LogLevel = "debug"
12- Access16+ INFO LogLevel = "info"
13- Database17+ WARN LogLevel = "warn"
14- Admin18+ ERROR LogLevel = "error"
15- User19+ FATAL LogLevel = "fatal"
16- Auth20+ 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.Logger24 zerolog.Logger
24 }25 }
25-type LogFileManager struct {26+
26- files map[LoggerType]*os.File27+type EchoLoggerWrapper struct {
27- mu sync.Mutex28+ *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 manager66+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 type70+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 type75+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, err83+ 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 nil106 return nil
93 }107 }
94 108
95-// GetLogFileForType returns the log file for the specified logger type109+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 type113+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 type117+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 type121+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 err128+
138- }129+// SetHeader
139- delete(m.files, loggerType)130+func (l *EchoLoggerWrapper) SetHeader(h string) {
140- }131+ // do nothing
141- return nil132+}
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+// Print
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 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close