package KFLog import ( "io" "log" "os" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) var Directory = "" var LogList *widget.List var LogDialog *dialog.CustomDialog type entry struct { Date string Time string File string Line string Message string } var entries []entry type writer chan<- string func (c writer) Write(p []byte) (n int, err error) { c <- string(p) return len(p), nil } // ShowDialog shows the log dialog func ShowDialog(window fyne.Window) { // if the log dialog is nil, create it if LogDialog == nil { err := Setup(window) if err != nil { return } } LogDialog.Show() } // GetDirectory returns the directory that the log files are stored in func GetDirectory() string { return Directory } // SetDirectory sets the directory that the log files are stored in and creates it if it doesn't exist func SetDirectory(dir string) { // make sure it ends in a slash and only one slash if !strings.HasSuffix(dir, "/") { dir += "/" } // check if the directory exists if _, err := os.Stat(dir); os.IsNotExist(err) { // create the directory err = os.MkdirAll(dir, os.ModePerm) if err != nil { panic(err) } } Directory = dir } func Setup(window fyne.Window, dir ...string) error { if len(dir) > 0 { SetDirectory(dir[0]) } else { SetDirectory("logs/") } // check if a log.txt file already exists fi, err := os.Stat(Directory + "log.txt") if err != nil { // create a new log.txt file _, err = os.Create(Directory + "log.txt") if err != nil { return err } } // copy the new log file to one named with its creation date if fi != nil { err = os.Rename(Directory+"log.txt", Directory+fi.ModTime().Format("2006-01-02-1504")+".txt") if err != nil { panic(err) } // create a new log.txt file _, err = os.Create(Directory + "log.txt") if err != nil { panic(err) } } // open the log file for writing logFile, err := os.OpenFile(Directory+"log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { return err } logChannel := make(chan string) channelWriter := writer(logChannel) multi := io.MultiWriter(os.Stdout, logFile, channelWriter) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.SetOutput(multi) LogList = widget.NewList( func() int { return len(entries) }, func() fyne.CanvasObject { return container.NewHBox( widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {}), widget.NewLabel("Template List Item"), ) }, func(i widget.ListItemID, o fyne.CanvasObject) { // create a message with everything but the date and time message := entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message dateTime := entries[i].Date + "-" + entries[i].Time // set the button to copy the message to the clipboard o.(*fyne.Container).Objects[0].(*widget.Button).OnTapped = func() { window.Clipboard().SetContent(dateTime + " | " + entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message) } // set the label to the message o.(*fyne.Container).Objects[1].(*widget.Label).SetText(message) // set the button text to the date and time o.(*fyne.Container).Objects[0].(*widget.Button).SetText(dateTime) }, ) LogDialog = dialog.NewCustom("Log", "Close", LogList, window) // listen for a Escape keypress, hide dialog on fired event window.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) { if key.Name == fyne.KeyEscape { LogDialog.Hide() } }) go func() { for { if LogDialog != nil { // get the size of the screen size := window.Canvas().Size() // set the size of the dialog to 80% of the screen LogDialog.Resize(fyne.NewSize(size.Width*0.8, size.Height*0.8)) } time.Sleep(time.Millisecond) } }() go func() { for logEntry := range logChannel { e := parseLogEntry(logEntry) entries = append(entries, e) if LogDialog != nil { LogDialog.Refresh() } } }() return nil } func parseLogEntry(logEntry string) entry { // split the log entry into its parts based on spaces parts := strings.SplitN(logEntry, " ", 4) // create a new entry e := entry{} // set the date and time e.Date = parts[0] e.Time = parts[1] // further split the "file:line" into file and line fileAndLine := strings.Split(parts[2], ":") if len(fileAndLine) >= 2 { e.File = fileAndLine[0] e.Line = fileAndLine[1] } else { e.File = parts[2] // if it doesn't split, use it as is e.Line = "unknown" // we couldn't determine the line number } // set the message e.Message = parts[3] return e }