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

Code before:
		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)
	}
}

func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {

	jsonConfig, _ := json.MarshalIndent(s.cfg.RawCopy(), "", "	")
	createJSONFile("config.json", jsonConfig)

	jsonLog, _ := json.MarshalIndent(s.systemLog.All(), "", "	")
	createJSONFile("log.json", jsonLog)

	jsonError, _ := json.MarshalIndent(s.guiErrors.All(), "", "	")
	createJSONFile("error.json", jsonError)

	heapRuntime := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
	createJSONFile("heapRuntime.txt", []byte(heapRuntime))

	t := time.Now().Format("2006-01-02T15.04.05")
	output := t + ".zip"

	path := baseDirs["config"] + "/support_bundle"

	e := os.MkdirAll(path, 0700)
	if e != nil {
		log.Fatalf("MkdirAll %q: %s", path, e)
	}

	newfilepath := path + "/" + output
	newfile, _ := os.Create(newfilepath)
	defer newfile.Close()

	files := []string{"config.json", "log.json", "error.json", "heapRuntime.txt"}
	ZipFiles(newfile, files)
	removeFiles(files)

	from, _ := os.Open(newfilepath)
	w.Header().Set("Content-Disposition", "attachment; filename="+output)
	w.Header().Set("Content-Type", "application/zip")
	io.Copy(w, from)
	sendJSON(w, map[string]string{"file_name": output})
}

func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
	stats := make(map[string]interface{})
	metrics.Each(func(name string, intf interface{}) {

Review comment (suggestion):
I think that in this method we can probably avoid creating all the temp files and instead buffer them in memory as byte slices or `bytes.Buffer` objects. The zip creator can use `bytes.Buffer`s to read from.

That also simplifies error handling and avoids issues with not being able to create temp files and whatnot.

Code after:
		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})
		} else {
			l.Warnln("Support bundle: failed to create errors.json:", err)
		}
	}