Repository: syncthing/syncthing
File: cmd/syncthing/gui.go (Go)

Code before:

func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	since, err := time.Parse(time.RFC3339, q.Get("since"))
	if err != nil {
		l.Debugln(err)
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")

	for _, line := range s.systemLog.Since(since) {
		fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
	}
}

type fileByte struct {
	name string
	buf  bytes.Buffer
}

func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
	var arrayFile []fileByte

	jsonConfig, _ := json.MarshalIndent(getRedactedConfig(s), "", "  ")
	arrayFile = addBufferFile(arrayFile, "config.json", jsonConfig)

	jsonLog, _ := json.MarshalIndent(s.systemLog.Since(time.Time{}), "", "  ")
	arrayFile = addBufferFile(arrayFile, "log.json", jsonLog)

	jsonError, _ := json.MarshalIndent(s.guiErrors.Since(time.Time{}), "", "  ")
	if string(jsonError) != "null" {
		arrayFile = addBufferFile(arrayFile, "error.json", jsonError)
	}

	panicFiles, ok := checkPanicFile(baseDirs["config"])
	if ok {
		for _, f := range panicFiles {
			panicF, _ := ioutil.ReadFile(f)
			arrayFile = addBufferFile(arrayFile, filepath.Base(f), panicF)
		}
	}

	versionPlatform, _ := json.MarshalIndent(map[string]string{
		"version":  Version,
		"codename": Codename,
		// "longVersion": LongVersion,
		"os":   runtime.GOOS,
		"arch": runtime.GOARCH,
	}, "", "  ")
	arrayFile = addBufferFile(arrayFile, "versionPlatform.json", versionPlatform)

	usageReportingData, _ := json.MarshalIndent(getusageReportingData(s, r), "", "  ")

Review comment (suggestion):
I don't think we should drop in the log as JSON; a simple text format would be better and more readable. Basically, for each entry in the returned log lines, `fmt.Fprintf(buf, "%s: %s\n", entry.When, entry.Message)` (roughly) to just print it to a buffer and add that as the `log.txt` or something.

Code after:

func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	since, err := time.Parse(time.RFC3339, q.Get("since"))
	if err != nil {
		l.Debugln(err)
	}
	sendJSON(w, map[string][]logger.Line{
		"messages": s.systemLog.Since(since),
	})
}

func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	since, err := time.Parse(time.RFC3339, q.Get("since"))
	if err != nil {
		l.Debugln(err)
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")

	for _, line := range s.systemLog.Since(since) {
		fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
	}
}

type fileEntry struct {
	name string
	data []byte
}

func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
	var files []fileEntry

	// Redacted configuration as a JSON
	if jsonConfig, err := json.MarshalIndent(getRedactedConfig(s), "", "  "); err == nil {
		files = append(files, fileEntry{name: "config.json.txt", data: jsonConfig})
	} else {
		l.Warnln("Support bundle: failed to create config.json:", err)
	}

	// Log as a text
	var buflog bytes.Buffer
	for _, line := range s.systemLog.Since(time.Time{}) {
		fmt.Fprintf(&buflog, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
	}
	files = append(files, fileEntry{name: "log.txt", data: buflog.Bytes()})

	// Errors as a JSON
	if errs := s.guiErrors.Since(time.Time{}); len(errs) > 0 {
		if jsonError, err := json.MarshalIndent(errs, "", "  "); err != nil {
			files = append(files, fileEntry{name: "errors.json.txt", data: jsonError})