package controller import ( "bufio" "net/http" "os" "path/filepath" "strings" "time" "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/gin-gonic/gin" "github.com/samber/do" "gorm.io/gorm" ) type ( LogsController interface { GetLogs(ctx *gin.Context) GetLogsByMonth(ctx *gin.Context) ServeLogsPage(ctx *gin.Context) } logsController struct { db *gorm.DB } ) func NewLogsController(injector *do.Injector) LogsController { db := do.MustInvokeNamed[*gorm.DB](injector, constants.DB) return &logsController{ db: db, } } func (c *logsController) ServeLogsPage(ctx *gin.Context) { acceptHeader := ctx.GetHeader("Accept") if strings.Contains(acceptHeader, "text/html") { month := strings.ToLower(time.Now().Format("January")) ctx.HTML(http.StatusOK, "logs.html", gin.H{ "Month": month, "QueryLogs": []string{}, "AppLogs": []string{}, }) return } ctx.Next() } func (c *logsController) GetLogs(ctx *gin.Context) { month := strings.ToLower(time.Now().Format("January")) c.getLogsForMonth(ctx, month) } func (c *logsController) GetLogsByMonth(ctx *gin.Context) { month := ctx.Param("month") c.getLogsForMonth(ctx, month) } func (c *logsController) getLogsForMonth(ctx *gin.Context, month string) { // Multiple possible log locations logLocations := []string{ "/app/config/logs/query_log", "/app/config/logs/app_log", "./config/logs/query_log", "./config/logs/app_log", "/tmp/logs", } var queryLogs []string var appLogs []string for _, logDir := range logLocations { // Read query logs queryLogFile := filepath.Join(logDir, month+"_query.log") // fmt.Println(queryLogFile) if queryEntries, err := readLogFile(queryLogFile); err == nil { queryLogs = queryEntries } // Read app logs - read as JSON lines appLogFile := filepath.Join(logDir, month+"_app.log") if appEntries, err := readLogFileAsJSONLines(appLogFile); err == nil { appLogs = appEntries } // If both found, break early if len(queryLogs) > 0 && len(appLogs) > 0 { break } } // Reverse slices untuk menampilkan log terbaru di atas reverseSlice(queryLogs) reverseSlice(appLogs) // Public access - maybe limit the logs or show recent only if len(queryLogs) > 100 { queryLogs = queryLogs[:100] // Limit to 100 latest } if len(appLogs) > 100 { appLogs = appLogs[:100] // Limit to 100 latest } // Jika request Accept header adalah application/json, kembalikan JSON if ctx.GetHeader("Accept") == "application/json" { res := utils.BuildResponseSuccess("Success get logs", gin.H{ "month": month, "query_logs": queryLogs, "app_logs": appLogs, "total_query": len(queryLogs), "total_app": len(appLogs), }) ctx.JSON(http.StatusOK, res) return } // Default kembalikan HTML page ctx.HTML(http.StatusOK, "logs.html", gin.H{ "Month": month, "QueryLogs": queryLogs, "AppLogs": appLogs, }) } // readLogFile membaca file log biasa (query log) func readLogFile(filename string) ([]string, error) { content, err := os.ReadFile(filename) if err != nil { return nil, err } // Normalisasi newline (biar Windows/Linux aman) text := strings.ReplaceAll(string(content), "\r\n", "\n") // Pisahkan antar entry pakai double newline rawEntries := strings.Split(text, "\n\n") var logs []string for _, entry := range rawEntries { entry = strings.TrimSpace(entry) if entry != "" { logs = append(logs, entry) } } return logs, nil } // readLogFileAsJSONLines membaca file log yang berisi JSON lines (app log) func readLogFileAsJSONLines(filename string) ([]string, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() var logs []string scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line != "" { logs = append(logs, line) } } return logs, scanner.Err() } // Helper function untuk reverse slice func reverseSlice(s []string) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } }