mirror of
https://github.com/hanzoai/docdb.git
synced 2026-10-30 18:39:39 +00:00
161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
// Copyright 2021 Hanzo AI Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package debug
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"maps"
|
|
"net"
|
|
"net/http"
|
|
runtimedebug "runtime/debug"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/hanzoai/docdb/build/version"
|
|
"github.com/hanzoai/docdb/internal/util/logging"
|
|
)
|
|
|
|
// addToZip adds a new file to the zip archive, adding encountered errors to errs.
|
|
//
|
|
// Passed [io.ReadCloser] is always closed.
|
|
func addToZip(w *zip.Writer, name string, r io.ReadCloser, errs map[string]error) {
|
|
var err error
|
|
|
|
defer func() {
|
|
if e := r.Close(); e != nil && err == nil {
|
|
err = e
|
|
}
|
|
|
|
errs[name] = err
|
|
}()
|
|
|
|
f, err := w.CreateHeader(&zip.FileHeader{
|
|
Name: name,
|
|
Method: zip.Deflate,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = io.Copy(f, r)
|
|
}
|
|
|
|
// filterExpvar filters out sensitive information from /debug/vars output.
|
|
func filterExpvar(ctx context.Context, r io.ReadCloser, l *slog.Logger) io.ReadCloser {
|
|
defer r.Close() //nolint:errcheck // we are only reading it
|
|
|
|
var res bytes.Buffer
|
|
|
|
var data map[string]any
|
|
if err := json.NewDecoder(r).Decode(&data); err != nil {
|
|
l.ErrorContext(ctx, "Failed to decode expvar", logging.Error(err))
|
|
return io.NopCloser(&res)
|
|
}
|
|
|
|
delete(data, "cmdline")
|
|
|
|
e := json.NewEncoder(&res)
|
|
e.SetIndent("", " ")
|
|
|
|
if err := e.Encode(data); err != nil {
|
|
l.ErrorContext(ctx, "Failed to encode expvar", logging.Error(err))
|
|
}
|
|
|
|
return io.NopCloser(&res)
|
|
}
|
|
|
|
// archiveHandler returns a handler that creates a zip archive with various debug information.
|
|
func archiveHandler(l *slog.Logger) http.HandlerFunc {
|
|
return func(rw http.ResponseWriter, r *http.Request) {
|
|
name := fmt.Sprintf("docdb-%s.zip", time.Now().Format("2006-01-02-15-04-05"))
|
|
|
|
rw.Header().Set("Content-Type", "application/zip")
|
|
rw.Header().Set("Content-Disposition", "attachment; filename="+name)
|
|
|
|
ctx := r.Context()
|
|
zipWriter := zip.NewWriter(rw)
|
|
errs := map[string]error{}
|
|
|
|
defer func() {
|
|
files := slices.Sorted(maps.Keys(errs))
|
|
|
|
var buf bytes.Buffer
|
|
for _, f := range files {
|
|
buf.WriteString(fmt.Sprintf("%s: %v\n", f, errs[f]))
|
|
}
|
|
|
|
addToZip(zipWriter, "errors.txt", io.NopCloser(&buf), errs)
|
|
|
|
if err := zipWriter.Close(); err != nil {
|
|
l.ErrorContext(ctx, "Failed to close archive", logging.Error(err))
|
|
}
|
|
|
|
l.InfoContext(ctx, "Debug archive created", slog.String("name", name))
|
|
}()
|
|
|
|
b, _ := json.MarshalIndent(version.Get(), "", " ")
|
|
addToZip(zipWriter, "version.json", io.NopCloser(bytes.NewReader(b)), errs)
|
|
|
|
info, _ := runtimedebug.ReadBuildInfo()
|
|
b, _ = json.MarshalIndent(info, "", " ")
|
|
addToZip(zipWriter, "buildinfo.json", io.NopCloser(bytes.NewReader(b)), errs)
|
|
|
|
host := ctx.Value(http.LocalAddrContextKey).(net.Addr)
|
|
|
|
for _, f := range []struct {
|
|
file string
|
|
path string
|
|
}{
|
|
{file: "metrics.txt", path: "/debug/metrics"},
|
|
{file: "vars.json", path: "/debug/vars"},
|
|
{file: "profile.pprof", path: "/debug/pprof/profile?seconds=10"},
|
|
{file: "goroutine.pprof", path: "/debug/pprof/goroutine"},
|
|
{file: "block.pprof", path: "/debug/pprof/block"},
|
|
{file: "heap.pprof", path: "/debug/pprof/heap?gc=1"},
|
|
{file: "trace.out", path: "/debug/pprof/trace?seconds=10"},
|
|
} {
|
|
fReq, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+host.String()+f.path, nil)
|
|
if err != nil {
|
|
errs[f.file] = err
|
|
continue
|
|
}
|
|
|
|
l.DebugContext(
|
|
ctx, "Fetching file for archive",
|
|
slog.String("file", f.file), slog.String("url", fReq.URL.String()),
|
|
)
|
|
|
|
resp, err := http.DefaultClient.Do(fReq)
|
|
if err != nil {
|
|
errs[f.file] = err
|
|
continue
|
|
}
|
|
|
|
r := resp.Body
|
|
if f.path == "/debug/vars" {
|
|
r = filterExpvar(ctx, r, l)
|
|
}
|
|
|
|
addToZip(zipWriter, f.file, r, errs)
|
|
}
|
|
}
|
|
}
|