detached 8744b7e

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 {
8383 return echo.StaticDirectoryHandler(ui.PublicFS, false)(c)
8484 })
8585
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+ }
114114
115115 return nil
116116
117117 }
118118
119119 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+
121147 e := echo.New()
122148
149+ // Use zerolog for Echo's requests
123150 e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
124151 LogURI: true,
125152 LogStatus: true,
126153 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).
128158 Str("URI", v.URI).
129159 Int("status", v.Status).
130160 Msg("request")
131-
132161 return nil
133162 },
134163 }))
135-
136- // use func bindStaticAdminUI to serve the static admin UI
137164 bindStaticAdminUI(e)
165+
166+ // Will crash in case of error
138167 if err := bindStaticAdminUI(e); err != nil {
139168 e.Logger.Fatal(err)
140169 }
141170
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+ }
143174
144175 }
@@ -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 handler86+ // 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.html90+ // 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- // return108+ return
109- // }109+ }
110- // }110+ }
111-111+
112- // e.DefaultHTTPErrorHandler(err, c)112+ e.DefaultHTTPErrorHandler(err, c)
113- // }113+ }
114 114
115 return nil115 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 nil161 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 @@
11 <!DOCTYPE html>
22 <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" />
76 <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>
811 <!-- HTMX min js -->
912 <script src="libs/htmx.min.js"></script>
10- <!-- UnoCSS -->
13+ <!-- UnoCSS (need to embed locally) -->
1114 <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>
1618
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>
1726
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+}

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