package main import ( "bufio" "os" "path/filepath" "regexp" "sort" "strconv" "strings" ) // LogLine is one marked line of the X server's log. type LogLine struct { At float64 `json:"at_seconds"` Level string `json:"level"` Text string `json:"text"` } // XLog is what x-log answers. type XLog struct { File string `json:"file"` Server string `json:"server,omitempty"` Started string `json:"started,omitempty"` Errors int `json:"errors"` Warnings int `json:"warnings"` Lines []LogLine `json:"lines"` Cut bool `json:"cut,omitempty"` } var ( logMark = regexp.MustCompile(`^\[\s*([0-9.]+)\] \((EE|WW)\) (.*)$`) logStart = regexp.MustCompile(`\(==\) Log file: "[^"]*", Time: (.+)$`) ) // ParseXLog reads an Xorg log: the server's version, when it started, and its errors and (when // asked) warnings, the last `limit` of them. The legend line that explains the markers is not one. func ParseXLog(file, text string, warnings bool, limit int) XLog { out := XLog{File: file, Lines: []LogLine{}} sc := bufio.NewScanner(strings.NewReader(text)) sc.Buffer(make([]byte, 1<<20), 1<<22) for sc.Scan() { line := sc.Text() if out.Server == "" && strings.HasPrefix(strings.TrimSpace(line), "X.Org X Server ") { out.Server = strings.TrimSpace(line) } if m := logStart.FindStringSubmatch(line); m != nil && out.Started == "" { out.Started = m[1] } m := logMark.FindStringSubmatch(line) if m == nil { continue } at, _ := strconv.ParseFloat(m[1], 64) level := map[string]string{"EE": "error", "WW": "warning"}[m[2]] if level == "error" { out.Errors++ } else { out.Warnings++ if !warnings { continue } } out.Lines = append(out.Lines, LogLine{At: at, Level: level, Text: m[3]}) } if limit > 0 && len(out.Lines) > limit { out.Lines = out.Lines[len(out.Lines)-limit:] out.Cut = true } return out } // XLogFiles are where an X server on display n logs, best first: a root server's log, then a // rootless one's under the account's data directory. With no display, every log found, newest first. func XLogFiles(display int, home string) []string { dirs := []string{"/var/log", filepath.Join(home, ".local/share/xorg")} if display >= 0 { var out []string for _, d := range dirs { out = append(out, filepath.Join(d, "Xorg."+strconv.Itoa(display)+".log")) } return out } type found struct { path string mod int64 } var all []found for _, d := range dirs { matches, _ := filepath.Glob(filepath.Join(d, "Xorg.*.log")) for _, p := range matches { if info, err := os.Stat(p); err == nil { all = append(all, found{p, info.ModTime().UnixNano()}) } } } sort.Slice(all, func(i, j int) bool { return all[i].mod > all[j].mod }) var out []string for _, f := range all { out = append(out, f.path) } return out }