From 193c8f65cf44e064aad7ca97e3d19f5caca33255 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 3 Oct 2025 00:30:25 -0600 Subject: [PATCH] feat(httplog): add http logger for debugging --- cmd/httplog/go.mod | 14 ++++++ cmd/httplog/go.sum | 13 ++++++ cmd/httplog/main.go | 102 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 cmd/httplog/go.mod create mode 100644 cmd/httplog/go.sum create mode 100644 cmd/httplog/main.go diff --git a/cmd/httplog/go.mod b/cmd/httplog/go.mod new file mode 100644 index 0000000..e00d849 --- /dev/null +++ b/cmd/httplog/go.mod @@ -0,0 +1,14 @@ +module github.com/therootcompany/golib/cmd/httplog + +go 1.24.6 + +require ( + github.com/fatih/color v1.18.0 + github.com/therootcompany/golib/colorjson v1.0.0 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/cmd/httplog/go.sum b/cmd/httplog/go.sum new file mode 100644 index 0000000..6621443 --- /dev/null +++ b/cmd/httplog/go.sum @@ -0,0 +1,13 @@ +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/therootcompany/golib/colorjson v1.0.0 h1:ihsmHrY9mRrClO94KSx4ed3bql1eaLnzOT+1QEnWlug= +github.com/therootcompany/golib/colorjson v1.0.0/go.mod h1:bE0wCyOsRFQnz22+TnQu4D0+FPl+ZugaaE79bjgDqRw= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/cmd/httplog/main.go b/cmd/httplog/main.go new file mode 100644 index 0000000..e45a983 --- /dev/null +++ b/cmd/httplog/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "strings" + + "github.com/fatih/color" + "github.com/therootcompany/golib/colorjson" +) + +var jsonf = colorjson.NewFormatter() + +func main() { + jsonf.Indent = 3 + color.NoColor = false // TODO manual override via flags + + mux := http.NewServeMux() + mux.HandleFunc("GET /", handler) + mux.HandleFunc("POST /", handler) + mux.HandleFunc("PATCH /", handler) + mux.HandleFunc("PUT /", handler) + mux.HandleFunc("DELETE /", handler) + + addr := "localhost:8088" + fmt.Printf("Listening on %s...\n\n", addr) + log.Fatal(http.ListenAndServe(addr, mux)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + // Log method, path, and query + var query string + if len(r.URL.RawQuery) > 0 { + query = "?" + r.URL.RawQuery + } + log.Printf("%s %s%s", r.Method, r.URL.Path, query) + + // Find max header name length for alignment + maxLen := len("HOST") + for name := range r.Header { + if len(name) > maxLen { + maxLen = len(name) + } + } + maxLen += 1 + + fmt.Printf(" %-"+fmt.Sprintf("%d", maxLen+1)+"s %s\n", "HOST", r.Host) + for name, values := range r.Header { + for _, value := range values { + fmt.Printf(" %-"+fmt.Sprintf("%d", maxLen+1)+"s %s\n", name+":", value) + } + } + fmt.Fprintf(os.Stderr, "\n") + + body, err := io.ReadAll(r.Body) + switch strings.ToUpper(r.Method) { + case "GET", "DELETE": + if len(body) > 0 { + fmt.Fprintf(os.Stderr, "Unexpected body:\n%q\n", string(body)) + } + return + case "POST", "PATCH", "PUT": + break + default: + fmt.Fprintf(os.Stderr, "Unexpected method\n") + return + } + defer fmt.Println() + + // Read request body + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to read body:\n%q\n", string(body)) + return + } + defer func() { + _ = r.Body.Close() + }() + + // Parse and pretty-print JSON, or raw body + var text string + var data any + if err := json.Unmarshal(body, &data); err == nil { + body, _ = jsonf.Marshal(data) + } + + text = string(body) + text = prefixLines(text, " ") + text = strings.TrimSpace(text) + fmt.Printf(" %s\n", text) +} + +func prefixLines(text, prefix string) string { + lines := strings.Split(text, "\n") + for i, line := range lines { + lines[i] = prefix + line + } + return strings.Join(lines, "\n") +}