add zerolog, colorized stdout and log files for app and http
- commit
- 8744b7e0fab64970f30f58d2ff36e352daad9efc
- author
- bnema · · 2023-09-02 07:17 UTC
- parent
- cd54f11
- signature
- unsigned
10 files · +315 -44
this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.
M cmd/main.go +65 -34
| @@ -83,62 +83,93 @@ func bindStaticAdminUI(e *echo.Echo) error { | |||
| 83 | 83 | return echo.StaticDirectoryHandler(ui.PublicFS, false)(c) | |
| 84 | 84 | }) | |
| 85 | 85 | ||
| 86 | - | // // 404 handler | |
| 87 | - | // e.HTTPErrorHandler = func(err error, c echo.Context) { | |
| 88 | - | // if he, ok := err.(*echo.HTTPError); ok { | |
| 89 | - | // if he.Code == http.StatusNotFound { | |
| 90 | - | // // 404.html | |
| 91 | - | // renderer, err := getRenderer("404.html", ui.TemplateFS) | |
| 92 | - | // if err != nil { | |
| 93 | - | // c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |
| 94 | - | // } | |
| 95 | - | ||
| 96 | - | // data := map[string]interface{}{ | |
| 97 | - | // "website": map[string]interface{}{ | |
| 98 | - | // "title": "Page Not Found", | |
| 99 | - | // }, | |
| 100 | - | // } | |
| 101 | - | ||
| 102 | - | // html, err := renderer.Render(data) | |
| 103 | - | // if err != nil { | |
| 104 | - | // c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |
| 105 | - | // } | |
| 106 | - | ||
| 107 | - | // c.HTML(http.StatusNotFound, html) | |
| 108 | - | // return | |
| 109 | - | // } | |
| 110 | - | // } | |
| 111 | - | ||
| 112 | - | // e.DefaultHTTPErrorHandler(err, c) | |
| 113 | - | // } | |
| 86 | + | // 404 handler | |
| 87 | + | e.HTTPErrorHandler = func(err error, c echo.Context) { | |
| 88 | + | if he, ok := err.(*echo.HTTPError); ok { | |
| 89 | + | if he.Code == http.StatusNotFound { | |
| 90 | + | // 404.html | |
| 91 | + | renderer, err := getRenderer("404.html", ui.PublicFS) | |
| 92 | + | if err != nil { | |
| 93 | + | c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | data := map[string]interface{}{ | |
| 97 | + | "website": map[string]interface{}{ | |
| 98 | + | "title": "Page Not Found", | |
| 99 | + | }, | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | html, err := renderer.Render(data) | |
| 103 | + | if err != nil { | |
| 104 | + | c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | c.HTML(http.StatusNotFound, html) | |
| 108 | + | return | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | e.DefaultHTTPErrorHandler(err, c) | |
| 113 | + | } | |
| 114 | 114 | ||
| 115 | 115 | return nil | |
| 116 | 116 | ||
| 117 | 117 | } | |
| 118 | 118 | ||
| 119 | 119 | func main() { | |
| 120 | - | logger := zerolog.New(os.Stdout) | |
| 120 | + | err := utils.CreateLogsDir() | |
| 121 | + | if err != nil { | |
| 122 | + | panic(err) | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | // Setup App Logger | |
| 126 | + | appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 127 | + | if err != nil { | |
| 128 | + | panic(err) | |
| 129 | + | } | |
| 130 | + | defer appLogFile.Close() | |
| 131 | + | ||
| 132 | + | appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |
| 133 | + | appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) | |
| 134 | + | appLogger := zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger() | |
| 135 | + | ||
| 136 | + | // Setup Echo Logger | |
| 137 | + | httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 138 | + | if err != nil { | |
| 139 | + | panic(err) | |
| 140 | + | } | |
| 141 | + | defer httpLogFile.Close() | |
| 142 | + | ||
| 143 | + | httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |
| 144 | + | httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) | |
| 145 | + | echoLogger := zerolog.New(httpMulti).With().Timestamp().Logger() | |
| 146 | + | ||
| 121 | 147 | e := echo.New() | |
| 122 | 148 | ||
| 149 | + | // Use zerolog for Echo's requests | |
| 123 | 150 | e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ | |
| 124 | 151 | LogURI: true, | |
| 125 | 152 | LogStatus: true, | |
| 126 | 153 | LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { | |
| 127 | - | logger.Info(). | |
| 154 | + | echoLogger.Info(). | |
| 155 | + | Str("type", "http"). | |
| 156 | + | Str("remote_ip", c.RealIP()). | |
| 157 | + | Str("method", c.Request().Method). | |
| 128 | 158 | Str("URI", v.URI). | |
| 129 | 159 | Int("status", v.Status). | |
| 130 | 160 | Msg("request") | |
| 131 | - | ||
| 132 | 161 | return nil | |
| 133 | 162 | }, | |
| 134 | 163 | })) | |
| 135 | - | ||
| 136 | - | // use func bindStaticAdminUI to serve the static admin UI | |
| 137 | 164 | bindStaticAdminUI(e) | |
| 165 | + | ||
| 166 | + | // Will crash in case of error | |
| 138 | 167 | if err := bindStaticAdminUI(e); err != nil { | |
| 139 | 168 | e.Logger.Fatal(err) | |
| 140 | 169 | } | |
| 141 | 170 | ||
| 142 | - | e.Logger.Fatal(e.Start(":1323")) | |
| 171 | + | if err := e.Start(":1323"); err != nil { | |
| 172 | + | appLogger.Error().Err(err).Msg("Failed to start the server") | |
| 173 | + | } | |
| 143 | 174 | ||
| 144 | 175 | } | |
| @@ -83,62 +83,93 @@ func bindStaticAdminUI(e *echo.Echo) error { | |||||
| 83 | return echo.StaticDirectoryHandler(ui.PublicFS, false)(c) | 83 | return echo.StaticDirectoryHandler(ui.PublicFS, false)(c) | ||
| 84 | }) | 84 | }) | ||
| 85 | 85 | ||||
| 86 | - | // // 404 handler | 86 | + | // 404 handler |
| 87 | - | // e.HTTPErrorHandler = func(err error, c echo.Context) { | 87 | + | e.HTTPErrorHandler = func(err error, c echo.Context) { |
| 88 | - | // if he, ok := err.(*echo.HTTPError); ok { | 88 | + | if he, ok := err.(*echo.HTTPError); ok { |
| 89 | - | // if he.Code == http.StatusNotFound { | 89 | + | if he.Code == http.StatusNotFound { |
| 90 | - | // // 404.html | 90 | + | // 404.html |
| 91 | - | // renderer, err := getRenderer("404.html", ui.TemplateFS) | 91 | + | renderer, err := getRenderer("404.html", ui.PublicFS) |
| 92 | - | // if err != nil { | 92 | + | if err != nil { |
| 93 | - | // c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | 93 | + | c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) |
| 94 | - | // } | 94 | + | } |
| 95 | - | 95 | + | ||
| 96 | - | // data := map[string]interface{}{ | 96 | + | data := map[string]interface{}{ |
| 97 | - | // "website": map[string]interface{}{ | 97 | + | "website": map[string]interface{}{ |
| 98 | - | // "title": "Page Not Found", | 98 | + | "title": "Page Not Found", |
| 99 | - | // }, | 99 | + | }, |
| 100 | - | // } | 100 | + | } |
| 101 | - | 101 | + | ||
| 102 | - | // html, err := renderer.Render(data) | 102 | + | html, err := renderer.Render(data) |
| 103 | - | // if err != nil { | 103 | + | if err != nil { |
| 104 | - | // c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | 104 | + | c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) |
| 105 | - | // } | 105 | + | } |
| 106 | - | 106 | + | ||
| 107 | - | // c.HTML(http.StatusNotFound, html) | 107 | + | c.HTML(http.StatusNotFound, html) |
| 108 | - | // return | 108 | + | return |
| 109 | - | // } | 109 | + | } |
| 110 | - | // } | 110 | + | } |
| 111 | - | 111 | + | ||
| 112 | - | // e.DefaultHTTPErrorHandler(err, c) | 112 | + | e.DefaultHTTPErrorHandler(err, c) |
| 113 | - | // } | 113 | + | } |
| 114 | 114 | ||||
| 115 | return nil | 115 | return nil | ||
| 116 | 116 | ||||
| 117 | } | 117 | } | ||
| 118 | 118 | ||||
| 119 | func main() { | 119 | func main() { | ||
| 120 | - | logger := zerolog.New(os.Stdout) | 120 | + | err := utils.CreateLogsDir() |
| 121 | + | if err != nil { | |||
| 122 | + | panic(err) | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | // Setup App Logger | |||
| 126 | + | appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |||
| 127 | + | if err != nil { | |||
| 128 | + | panic(err) | |||
| 129 | + | } | |||
| 130 | + | defer appLogFile.Close() | |||
| 131 | + | ||||
| 132 | + | appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |||
| 133 | + | appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter) | |||
| 134 | + | appLogger := zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger() | |||
| 135 | + | ||||
| 136 | + | // Setup Echo Logger | |||
| 137 | + | httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |||
| 138 | + | if err != nil { | |||
| 139 | + | panic(err) | |||
| 140 | + | } | |||
| 141 | + | defer httpLogFile.Close() | |||
| 142 | + | ||||
| 143 | + | httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout} | |||
| 144 | + | httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter) | |||
| 145 | + | echoLogger := zerolog.New(httpMulti).With().Timestamp().Logger() | |||
| 146 | + | ||||
| 121 | e := echo.New() | 147 | e := echo.New() | ||
| 122 | 148 | ||||
| 149 | + | // Use zerolog for Echo's requests | |||
| 123 | e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ | 150 | e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ | ||
| 124 | LogURI: true, | 151 | LogURI: true, | ||
| 125 | LogStatus: true, | 152 | LogStatus: true, | ||
| 126 | LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { | 153 | LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { | ||
| 127 | - | logger.Info(). | 154 | + | echoLogger.Info(). |
| 155 | + | Str("type", "http"). | |||
| 156 | + | Str("remote_ip", c.RealIP()). | |||
| 157 | + | Str("method", c.Request().Method). | |||
| 128 | Str("URI", v.URI). | 158 | Str("URI", v.URI). | ||
| 129 | Int("status", v.Status). | 159 | Int("status", v.Status). | ||
| 130 | Msg("request") | 160 | Msg("request") | ||
| 131 | - | ||||
| 132 | return nil | 161 | return nil | ||
| 133 | }, | 162 | }, | ||
| 134 | })) | 163 | })) | ||
| 135 | - | ||||
| 136 | - | // use func bindStaticAdminUI to serve the static admin UI | |||
| 137 | bindStaticAdminUI(e) | 164 | bindStaticAdminUI(e) | ||
| 165 | + | ||||
| 166 | + | // Will crash in case of error | |||
| 138 | if err := bindStaticAdminUI(e); err != nil { | 167 | if err := bindStaticAdminUI(e); err != nil { | ||
| 139 | e.Logger.Fatal(err) | 168 | e.Logger.Fatal(err) | ||
| 140 | } | 169 | } | ||
| 141 | 170 | ||||
| 142 | - | e.Logger.Fatal(e.Start(":1323")) | 171 | + | if err := e.Start(":1323"); err != nil { |
| 172 | + | appLogger.Error().Err(err).Msg("Failed to start the server") | |||
| 173 | + | } | |||
| 143 | 174 | ||||
| 144 | } | 175 | } | ||
A internal/ui/public/404.html +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | <!DOCTYPE html> | |
| 2 | + | <html lang="en"> | |
| 3 | + | <head> | |
| 4 | + | <meta charset="UTF-8" /> | |
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| 6 | + | <title>Error 404</title> | |
| 7 | + | </head> | |
| 8 | + | <body> | |
| 9 | + | <h1>Error 404</h1> | |
| 10 | + | <p>Page not found</p> | |
| 11 | + | </body> | |
| 12 | + | </html> | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | <!DOCTYPE html> | |||
| 2 | + | <html lang="en"> | |||
| 3 | + | <head> | |||
| 4 | + | <meta charset="UTF-8" /> | |||
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
| 6 | + | <title>Error 404</title> | |||
| 7 | + | </head> | |||
| 8 | + | <body> | |||
| 9 | + | <h1>Error 404</h1> | |||
| 10 | + | <p>Page not found</p> | |||
| 11 | + | </body> | |||
| 12 | + | </html> | |||
A internal/ui/public/assets/css/custom.css +37 -0
| @@ -0,0 +1,37 @@ | |||
| 1 | + | /* Path: internal/ui/public/assets/css/custom.css */ | |
| 2 | + | /* Embeded path : assets/css/cutsom.css */ | |
| 3 | + | .fade-me-out.htmx-swapping { | |
| 4 | + | opacity: 0; | |
| 5 | + | transition: opacity 1s ease-out; | |
| 6 | + | } | |
| 7 | + | ||
| 8 | + | ||
| 9 | + | /* View transition */ | |
| 10 | + | @keyframes fade-in { | |
| 11 | + | from { opacity: 0; } | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | @keyframes fade-out { | |
| 15 | + | to { opacity: 0; } | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | @keyframes slide-from-right { | |
| 19 | + | from { transform: translateX(90px); } | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | @keyframes slide-to-left { | |
| 23 | + | to { transform: translateX(-90px); } | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | .slide-it { | |
| 27 | + | view-transition-name: slide-it; | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | ::view-transition-old(slide-it) { | |
| 31 | + | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |
| 32 | + | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | |
| 33 | + | } | |
| 34 | + | ::view-transition-new(slide-it) { | |
| 35 | + | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |
| 36 | + | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | |
| 37 | + | } | |
| @@ -0,0 +1,37 @@ | |||||
| 1 | + | /* Path: internal/ui/public/assets/css/custom.css */ | |||
| 2 | + | /* Embeded path : assets/css/cutsom.css */ | |||
| 3 | + | .fade-me-out.htmx-swapping { | |||
| 4 | + | opacity: 0; | |||
| 5 | + | transition: opacity 1s ease-out; | |||
| 6 | + | } | |||
| 7 | + | ||||
| 8 | + | ||||
| 9 | + | /* View transition */ | |||
| 10 | + | @keyframes fade-in { | |||
| 11 | + | from { opacity: 0; } | |||
| 12 | + | } | |||
| 13 | + | ||||
| 14 | + | @keyframes fade-out { | |||
| 15 | + | to { opacity: 0; } | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | @keyframes slide-from-right { | |||
| 19 | + | from { transform: translateX(90px); } | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | @keyframes slide-to-left { | |||
| 23 | + | to { transform: translateX(-90px); } | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | .slide-it { | |||
| 27 | + | view-transition-name: slide-it; | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | ::view-transition-old(slide-it) { | |||
| 31 | + | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |||
| 32 | + | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | |||
| 33 | + | } | |||
| 34 | + | ::view-transition-new(slide-it) { | |||
| 35 | + | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |||
| 36 | + | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | |||
| 37 | + | } | |||
M internal/ui/public/index.html +26 -10
| @@ -1,18 +1,34 @@ | |||
| 1 | 1 | <!DOCTYPE html> | |
| 2 | 2 | <html lang="en"> | |
| 3 | - | ||
| 4 | - | <head> | |
| 5 | - | <meta charset="UTF-8"> | |
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 3 | + | <head> | |
| 4 | + | <meta charset="UTF-8" /> | |
| 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| 7 | 6 | <title>{{.website.title}}</title> | |
| 7 | + | <!-- Custom CSS from assets/css --> | |
| 8 | + | <link rel="stylesheet" href="assets/css/custom.css" /> | |
| 9 | + | <!-- Alpine --> | |
| 10 | + | <script src="libs/alpine.min.js"></script> | |
| 8 | 11 | <!-- HTMX min js --> | |
| 9 | 12 | <script src="libs/htmx.min.js"></script> | |
| 10 | - | <!-- UnoCSS --> | |
| 13 | + | <!-- UnoCSS (need to embed locally) --> | |
| 11 | 14 | <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script> | |
| 12 | - | ||
| 13 | - | </head> | |
| 14 | - | ||
| 15 | - | <body id="app" hx-get="/htmx" hx-headers='{"X-Fragment": "hello"}' hx-trigger="load"></body> | |
| 15 | + | <!-- Custom JS from assets/js --> | |
| 16 | + | <script src="assets/js/custom.js"></script> | |
| 17 | + | </head> | |
| 16 | 18 | ||
| 19 | + | <header | |
| 20 | + | id="header" | |
| 21 | + | hx-get="/htmx" | |
| 22 | + | hx-headers='{"X-Fragment": "header"}' | |
| 23 | + | hx-trigger="load" | |
| 24 | + | hx-swap="innerHTML" | |
| 25 | + | ></header> | |
| 17 | 26 | ||
| 18 | - | </html>⏎̸ | |
| 27 | + | <body | |
| 28 | + | id="app" | |
| 29 | + | hx-get="/htmx" | |
| 30 | + | hx-headers='{"X-Fragment": "hello"}' | |
| 31 | + | hx-trigger="load" | |
| 32 | + | hx-swap="innerHTML" | |
| 33 | + | ></body> | |
| 34 | + | </html> | |
| @@ -1,18 +1,34 @@ | |||||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> | ||
| 2 | <html lang="en"> | 2 | <html lang="en"> | ||
| 3 | - | 3 | + | <head> | |
| 4 | - | <head> | 4 | + | <meta charset="UTF-8" /> |
| 5 | - | <meta charset="UTF-8"> | 5 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
| 7 | <title>{{.website.title}}</title> | 6 | <title>{{.website.title}}</title> | ||
| 7 | + | <!-- Custom CSS from assets/css --> | |||
| 8 | + | <link rel="stylesheet" href="assets/css/custom.css" /> | |||
| 9 | + | <!-- Alpine --> | |||
| 10 | + | <script src="libs/alpine.min.js"></script> | |||
| 8 | <!-- HTMX min js --> | 11 | <!-- HTMX min js --> | ||
| 9 | <script src="libs/htmx.min.js"></script> | 12 | <script src="libs/htmx.min.js"></script> | ||
| 10 | - | <!-- UnoCSS --> | 13 | + | <!-- UnoCSS (need to embed locally) --> |
| 11 | <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script> | 14 | <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script> | ||
| 12 | - | 15 | + | <!-- Custom JS from assets/js --> | |
| 13 | - | </head> | 16 | + | <script src="assets/js/custom.js"></script> |
| 14 | - | 17 | + | </head> | |
| 15 | - | <body id="app" hx-get="/htmx" hx-headers='{"X-Fragment": "hello"}' hx-trigger="load"></body> | |||
| 16 | 18 | ||||
| 19 | + | <header | |||
| 20 | + | id="header" | |||
| 21 | + | hx-get="/htmx" | |||
| 22 | + | hx-headers='{"X-Fragment": "header"}' | |||
| 23 | + | hx-trigger="load" | |||
| 24 | + | hx-swap="innerHTML" | |||
| 25 | + | ></header> | |||
| 17 | 26 | ||||
| 18 | - | </html>⏎̸ | 27 | + | <body |
| 28 | + | id="app" | |||
| 29 | + | hx-get="/htmx" | |||
| 30 | + | hx-headers='{"X-Fragment": "hello"}' | |||
| 31 | + | hx-trigger="load" | |||
| 32 | + | hx-swap="innerHTML" | |||
| 33 | + | ></body> | |||
| 34 | + | </html> | |||
A internal/ui/public/libs/alpine.min.js +5 -0 generated file: collapsed
A internal/ui/public/libs/chart.js +20 -0 too large to show in the browser
too large to show in the browser view raw
A internal/ui/templates/dashboard.html +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | <div id="dashboard"> | |
| 2 | + | <h1 | |
| 3 | + | class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s" | |
| 4 | + | > | |
| 5 | + | Hello World from dashboard.html | |
| 6 | + | </h1> | |
| 7 | + | <!-- We load Chart.js here to avoid loading it in every page end when all the rest is loaded --> | |
| 8 | + | <script src="libs/chart.js"></script> | |
| 9 | + | </div> | |
| @@ -0,0 +1,9 @@ | |||||
| 1 | + | <div id="dashboard"> | |||
| 2 | + | <h1 | |||
| 3 | + | class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s" | |||
| 4 | + | > | |||
| 5 | + | Hello World from dashboard.html | |||
| 6 | + | </h1> | |||
| 7 | + | <!-- We load Chart.js here to avoid loading it in every page end when all the rest is loaded --> | |||
| 8 | + | <script src="libs/chart.js"></script> | |||
| 9 | + | </div> | |||
A internal/ui/templates/manager.html +0 -0
no textual change.
A internal/ui/templates/user.html +0 -0
no textual change.
A pkg/utils/logger.go +141 -0
| @@ -0,0 +1,141 @@ | |||
| 1 | + | package utils | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "os" | |
| 5 | + | "sync" | |
| 6 | + | ||
| 7 | + | "github.com/rs/zerolog" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | const ( | |
| 11 | + | App LoggerType = iota | |
| 12 | + | APP | |
| 13 | + | HTTP | |
| 14 | + | Database | |
| 15 | + | Admin | |
| 16 | + | User | |
| 17 | + | Auth | |
| 18 | + | Utils | |
| 19 | + | Render | |
| 20 | + | ) | |
| 21 | + | ||
| 22 | + | type LoggerType int | |
| 23 | + | type Logger struct { | |
| 24 | + | zerolog.Logger | |
| 25 | + | } | |
| 26 | + | type LogFileManager struct { | |
| 27 | + | files map[LoggerType]*os.File | |
| 28 | + | mu sync.Mutex | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func NewLogger() *Logger { | |
| 32 | + | writer := zerolog.ConsoleWriter{Out: os.Stdout} // changed to os.Stdout | |
| 33 | + | ||
| 34 | + | // Create a new logger | |
| 35 | + | zl := zerolog.New(writer).With().Timestamp().Logger() | |
| 36 | + | return &Logger{zl} | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | func NewLogFileManager() *LogFileManager { | |
| 40 | + | return &LogFileManager{ | |
| 41 | + | files: make(map[LoggerType]*os.File), | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | // GetTypeLogger returns a logger with the specified type | |
| 46 | + | func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | |
| 47 | + | switch loggerType { | |
| 48 | + | case App: | |
| 49 | + | return &Logger{l.With().Str("type", "app").Logger()} | |
| 50 | + | case HTTP: | |
| 51 | + | return &Logger{l.With().Str("type", "http").Logger()} | |
| 52 | + | case Database: | |
| 53 | + | return &Logger{l.With().Str("type", "database").Logger()} | |
| 54 | + | case Admin: | |
| 55 | + | return &Logger{l.With().Str("type", "admin").Logger()} | |
| 56 | + | case User: | |
| 57 | + | return &Logger{l.With().Str("type", "user").Logger()} | |
| 58 | + | case Auth: | |
| 59 | + | return &Logger{l.With().Str("type", "auth").Logger()} | |
| 60 | + | default: | |
| 61 | + | return &Logger{l.With().Str("type", "undefined").Logger()} | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // GetTypeLogger returns a logger with the specified type | |
| 66 | + | func (m *LogFileManager) OpenLogFile(loggerType LoggerType) (*os.File, error) { | |
| 67 | + | m.mu.Lock() | |
| 68 | + | defer m.mu.Unlock() | |
| 69 | + | ||
| 70 | + | if file, exists := m.files[loggerType]; exists { | |
| 71 | + | return file, nil | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | logFilePath := GetLogFileForType(loggerType) | |
| 75 | + | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 76 | + | if err != nil { | |
| 77 | + | return nil, err | |
| 78 | + | } | |
| 79 | + | m.files[loggerType] = logFile | |
| 80 | + | return logFile, nil | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // CreateLogsDir creates the logs directory if it doesn't exist | |
| 84 | + | func CreateLogsDir() error { | |
| 85 | + | if _, err := os.Stat("logs"); os.IsNotExist(err) { | |
| 86 | + | err := os.Mkdir("logs", 0755) | |
| 87 | + | if err != nil { | |
| 88 | + | return err | |
| 89 | + | } | |
| 90 | + | } | |
| 91 | + | return nil | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | // GetLogFileForType returns the log file for the specified logger type | |
| 95 | + | func GetLogFileForType(loggerType LoggerType) string { | |
| 96 | + | switch loggerType { | |
| 97 | + | case App, Database, Utils, Render: | |
| 98 | + | return "logs/app.log" | |
| 99 | + | case HTTP, Admin, User, Auth: | |
| 100 | + | return "logs/http.log" | |
| 101 | + | default: | |
| 102 | + | return "logs/undefined.log" | |
| 103 | + | } | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | // CreateLogFile creates a log file for each logger type | |
| 107 | + | func CreateLogFile(loggerType LoggerType) (*os.File, error) { | |
| 108 | + | logFilePath := GetLogFileForType(loggerType) | |
| 109 | + | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
| 110 | + | if err != nil { | |
| 111 | + | return nil, err | |
| 112 | + | } | |
| 113 | + | return logFile, nil | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // GetLogLevel returns the log level for the specified logger type | |
| 117 | + | func GetLogLevel(loggerType LoggerType) zerolog.Level { | |
| 118 | + | switch loggerType { | |
| 119 | + | case App, Database, Utils, Render: | |
| 120 | + | return zerolog.InfoLevel | |
| 121 | + | case HTTP, Admin, User, Auth: | |
| 122 | + | return zerolog.DebugLevel | |
| 123 | + | default: | |
| 124 | + | return zerolog.DebugLevel | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // CloseLogFile closes the log file for the specified logger type | |
| 129 | + | func (m *LogFileManager) CloseLogFile(loggerType LoggerType) error { | |
| 130 | + | m.mu.Lock() | |
| 131 | + | defer m.mu.Unlock() | |
| 132 | + | ||
| 133 | + | if file, exists := m.files[loggerType]; exists { | |
| 134 | + | err := file.Close() | |
| 135 | + | if err != nil { | |
| 136 | + | return err | |
| 137 | + | } | |
| 138 | + | delete(m.files, loggerType) | |
| 139 | + | } | |
| 140 | + | return nil | |
| 141 | + | } | |
| @@ -0,0 +1,141 @@ | |||||
| 1 | + | package utils | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "os" | |||
| 5 | + | "sync" | |||
| 6 | + | ||||
| 7 | + | "github.com/rs/zerolog" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | const ( | |||
| 11 | + | App LoggerType = iota | |||
| 12 | + | APP | |||
| 13 | + | HTTP | |||
| 14 | + | Database | |||
| 15 | + | Admin | |||
| 16 | + | User | |||
| 17 | + | Auth | |||
| 18 | + | Utils | |||
| 19 | + | Render | |||
| 20 | + | ) | |||
| 21 | + | ||||
| 22 | + | type LoggerType int | |||
| 23 | + | type Logger struct { | |||
| 24 | + | zerolog.Logger | |||
| 25 | + | } | |||
| 26 | + | type LogFileManager struct { | |||
| 27 | + | files map[LoggerType]*os.File | |||
| 28 | + | mu sync.Mutex | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func NewLogger() *Logger { | |||
| 32 | + | writer := zerolog.ConsoleWriter{Out: os.Stdout} // changed to os.Stdout | |||
| 33 | + | ||||
| 34 | + | // Create a new logger | |||
| 35 | + | zl := zerolog.New(writer).With().Timestamp().Logger() | |||
| 36 | + | return &Logger{zl} | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | func NewLogFileManager() *LogFileManager { | |||
| 40 | + | return &LogFileManager{ | |||
| 41 | + | files: make(map[LoggerType]*os.File), | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | // GetTypeLogger returns a logger with the specified type | |||
| 46 | + | func (l *Logger) GetTypeLogger(loggerType LoggerType) *Logger { | |||
| 47 | + | switch loggerType { | |||
| 48 | + | case App: | |||
| 49 | + | return &Logger{l.With().Str("type", "app").Logger()} | |||
| 50 | + | case HTTP: | |||
| 51 | + | return &Logger{l.With().Str("type", "http").Logger()} | |||
| 52 | + | case Database: | |||
| 53 | + | return &Logger{l.With().Str("type", "database").Logger()} | |||
| 54 | + | case Admin: | |||
| 55 | + | return &Logger{l.With().Str("type", "admin").Logger()} | |||
| 56 | + | case User: | |||
| 57 | + | return &Logger{l.With().Str("type", "user").Logger()} | |||
| 58 | + | case Auth: | |||
| 59 | + | return &Logger{l.With().Str("type", "auth").Logger()} | |||
| 60 | + | default: | |||
| 61 | + | return &Logger{l.With().Str("type", "undefined").Logger()} | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | // GetTypeLogger returns a logger with the specified type | |||
| 66 | + | func (m *LogFileManager) OpenLogFile(loggerType LoggerType) (*os.File, error) { | |||
| 67 | + | m.mu.Lock() | |||
| 68 | + | defer m.mu.Unlock() | |||
| 69 | + | ||||
| 70 | + | if file, exists := m.files[loggerType]; exists { | |||
| 71 | + | return file, nil | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | logFilePath := GetLogFileForType(loggerType) | |||
| 75 | + | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |||
| 76 | + | if err != nil { | |||
| 77 | + | return nil, err | |||
| 78 | + | } | |||
| 79 | + | m.files[loggerType] = logFile | |||
| 80 | + | return logFile, nil | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // CreateLogsDir creates the logs directory if it doesn't exist | |||
| 84 | + | func CreateLogsDir() error { | |||
| 85 | + | if _, err := os.Stat("logs"); os.IsNotExist(err) { | |||
| 86 | + | err := os.Mkdir("logs", 0755) | |||
| 87 | + | if err != nil { | |||
| 88 | + | return err | |||
| 89 | + | } | |||
| 90 | + | } | |||
| 91 | + | return nil | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | // GetLogFileForType returns the log file for the specified logger type | |||
| 95 | + | func GetLogFileForType(loggerType LoggerType) string { | |||
| 96 | + | switch loggerType { | |||
| 97 | + | case App, Database, Utils, Render: | |||
| 98 | + | return "logs/app.log" | |||
| 99 | + | case HTTP, Admin, User, Auth: | |||
| 100 | + | return "logs/http.log" | |||
| 101 | + | default: | |||
| 102 | + | return "logs/undefined.log" | |||
| 103 | + | } | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | // CreateLogFile creates a log file for each logger type | |||
| 107 | + | func CreateLogFile(loggerType LoggerType) (*os.File, error) { | |||
| 108 | + | logFilePath := GetLogFileForType(loggerType) | |||
| 109 | + | logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |||
| 110 | + | if err != nil { | |||
| 111 | + | return nil, err | |||
| 112 | + | } | |||
| 113 | + | return logFile, nil | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // GetLogLevel returns the log level for the specified logger type | |||
| 117 | + | func GetLogLevel(loggerType LoggerType) zerolog.Level { | |||
| 118 | + | switch loggerType { | |||
| 119 | + | case App, Database, Utils, Render: | |||
| 120 | + | return zerolog.InfoLevel | |||
| 121 | + | case HTTP, Admin, User, Auth: | |||
| 122 | + | return zerolog.DebugLevel | |||
| 123 | + | default: | |||
| 124 | + | return zerolog.DebugLevel | |||
| 125 | + | } | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | // CloseLogFile closes the log file for the specified logger type | |||
| 129 | + | func (m *LogFileManager) CloseLogFile(loggerType LoggerType) error { | |||
| 130 | + | m.mu.Lock() | |||
| 131 | + | defer m.mu.Unlock() | |||
| 132 | + | ||||
| 133 | + | if file, exists := m.files[loggerType]; exists { | |||
| 134 | + | err := file.Close() | |||
| 135 | + | if err != nil { | |||
| 136 | + | return err | |||
| 137 | + | } | |||
| 138 | + | delete(m.files, loggerType) | |||
| 139 | + | } | |||
| 140 | + | return nil | |||
| 141 | + | } | |||