This commit is contained in:
parent
c2b0d309fb
commit
675846ac1e
@ -7,9 +7,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -323,6 +325,7 @@ func (s *apiService) Serve() {
|
|||||||
debugMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
|
debugMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
|
||||||
debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
|
debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
|
||||||
debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
|
debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
|
||||||
|
debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle)
|
||||||
getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux))
|
getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux))
|
||||||
|
|
||||||
// A handler that splits requests between the two above and disables
|
// A handler that splits requests between the two above and disables
|
||||||
@ -1034,6 +1037,106 @@ func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic files as a JSON
|
||||||
|
if panicFiles, err := filepath.Glob(filepath.Join(baseDirs["config"], "panic*")); err == nil {
|
||||||
|
for _, f := range panicFiles {
|
||||||
|
if panicFile, err := ioutil.ReadFile(f); err != nil {
|
||||||
|
l.Warnf("Support bundle: failed to load %s: %s", filepath.Base(f), err)
|
||||||
|
} else {
|
||||||
|
files = append(files, fileEntry{name: filepath.Base(f), data: panicFile})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version and platform information as a JSON
|
||||||
|
if versionPlatform, err := json.MarshalIndent(map[string]string{
|
||||||
|
"now": time.Now().Format(time.RFC3339),
|
||||||
|
"version": Version,
|
||||||
|
"codename": Codename,
|
||||||
|
"longVersion": LongVersion,
|
||||||
|
"os": runtime.GOOS,
|
||||||
|
"arch": runtime.GOARCH,
|
||||||
|
}, "", " "); err == nil {
|
||||||
|
files = append(files, fileEntry{name: "version-platform.json.txt", data: versionPlatform})
|
||||||
|
} else {
|
||||||
|
l.Warnln("Failed to create versionPlatform.json: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report Data as a JSON
|
||||||
|
if usageReportingData, err := json.MarshalIndent(reportData(s.cfg, s.model, s.connectionsService, usageReportVersion, true), "", " "); err != nil {
|
||||||
|
l.Warnln("Support bundle: failed to create versionPlatform.json:", err)
|
||||||
|
} else {
|
||||||
|
files = append(files, fileEntry{name: "usage-reporting.json.txt", data: usageReportingData})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heap and CPU Proofs as a pprof extension
|
||||||
|
var heapBuffer, cpuBuffer bytes.Buffer
|
||||||
|
filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
|
||||||
|
runtime.GC()
|
||||||
|
pprof.WriteHeapProfile(&heapBuffer)
|
||||||
|
files = append(files, fileEntry{name: filename, data: heapBuffer.Bytes()})
|
||||||
|
|
||||||
|
const duration = 4 * time.Second
|
||||||
|
filename = fmt.Sprintf("syncthing-cpu-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
|
||||||
|
pprof.StartCPUProfile(&cpuBuffer)
|
||||||
|
time.Sleep(duration)
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
files = append(files, fileEntry{name: filename, data: cpuBuffer.Bytes()})
|
||||||
|
|
||||||
|
// Add buffer files to buffer zip
|
||||||
|
var zipFilesBuffer bytes.Buffer
|
||||||
|
if err := writeZip(&zipFilesBuffer, files); err != nil {
|
||||||
|
l.Warnln("Support bundle: failed to create support bundle zip:", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set zip file name and path
|
||||||
|
zipFileName := fmt.Sprintf("support-bundle-%s.zip", time.Now().Format("2018-01-02T15.04.05"))
|
||||||
|
zipFilePath := filepath.Join(baseDirs["config"], zipFileName)
|
||||||
|
|
||||||
|
// Write buffer zip to local zip file (back up)
|
||||||
|
if err := ioutil.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
|
||||||
|
l.Warnln("Support bundle: support bundle zip could not be created:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the buffer zip to client for download
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+zipFileName)
|
||||||
|
io.Copy(w, &zipFilesBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
|
func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
|
||||||
stats := make(map[string]interface{})
|
stats := make(map[string]interface{})
|
||||||
metrics.Each(func(name string, intf interface{}) {
|
metrics.Each(func(name string, intf interface{}) {
|
||||||
|
|||||||
@ -1261,6 +1261,7 @@ func cleanConfigDirectory() {
|
|||||||
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
|
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
|
||||||
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
|
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
|
||||||
"tmp-index-sorter.*": time.Minute, // these should never exist on startup
|
"tmp-index-sorter.*": time.Minute, // these should never exist on startup
|
||||||
|
"support-bundle-*": 30 * 24 * time.Hour, // keep old support bundle zip or folder for a month
|
||||||
}
|
}
|
||||||
|
|
||||||
for pat, dur := range patterns {
|
for pat, dur := range patterns {
|
||||||
|
|||||||
47
cmd/syncthing/support_bundle.go
Normal file
47
cmd/syncthing/support_bundle.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (C) 2018 The Syncthing Authors.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getRedactedConfig redacting some parts of config
|
||||||
|
func getRedactedConfig(s *apiService) config.Configuration {
|
||||||
|
rawConf := s.cfg.RawCopy()
|
||||||
|
rawConf.GUI.APIKey = "REDACTED"
|
||||||
|
if rawConf.GUI.Password != "" {
|
||||||
|
rawConf.GUI.Password = "REDACTED"
|
||||||
|
}
|
||||||
|
if rawConf.GUI.User != "" {
|
||||||
|
rawConf.GUI.User = "REDACTED"
|
||||||
|
}
|
||||||
|
return rawConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeZip writes a zip file containing the given entries
|
||||||
|
func writeZip(writer io.Writer, files []fileEntry) error {
|
||||||
|
zipWriter := zip.NewWriter(writer)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
zipFile, err := zipWriter.Create(file.name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = zipFile.Write(file.data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return zipWriter.Close()
|
||||||
|
}
|
||||||
@ -81,6 +81,8 @@
|
|||||||
<li class="divider" aria-hidden="true"></li>
|
<li class="divider" aria-hidden="true"></li>
|
||||||
<li><a href="" ng-click="advanced()"><span class="fas fa-fw fa-cogs"></span> <span translate>Advanced</span></a></li>
|
<li><a href="" ng-click="advanced()"><span class="fas fa-fw fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||||
<li><a href="" ng-click="logging.show()"><span class="far fa-fw fa-file-alt"></span> <span translate>Logs</span></a></li>
|
<li><a href="" ng-click="logging.show()"><span class="far fa-fw fa-file-alt"></span> <span translate>Logs</span></a></li>
|
||||||
|
<li class="divider" aria-hidden="true" ng-if="config.gui.debugging"></li>
|
||||||
|
<li><a href="/rest/debug/support" target="_blank" ng-if="config.gui.debugging"><span class="fa fa-user-md"></span> <span translate>Support Bundle</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user