Documentation
¶
Overview ¶
Package httplog provides a standard http.RoundTripper transport that can be used with standard HTTP clients to log the raw (outgoing) HTTP request and response.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultTransport = http.DefaultTransport
DefaultTransport is the default transport used by the HTTP logger.
Functions ¶
This section is empty.
Types ¶
type Option ¶ added in v0.2.0
type Option func(*RoundTripLogger)
Option is a roundtrip logger option.
func WithReqResBody ¶ added in v0.3.0
WithReqResBody is a roundtrip logger option to set whether or not to log the request and response body. Useful when body content is binary.
type RoundTripLogger ¶
type RoundTripLogger struct {
// contains filtered or unexported fields
}
RoundTripLogger provides a standard http.RoundTripper transport that can be used with standard HTTP clients to log the raw (outgoing) HTTP request and response.
Example (Logf) ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"github.com/kenshaw/httplog"
)
func main() {
ts := httptest.NewServer(writeHTML(`<body>hello</body>`))
defer ts.Close()
// do http request (logf has same signature as log.Printf)
transport := httplog.NewPrefixedRoundTripLogger(nil, logf)
cl := &http.Client{
Transport: transport,
}
req, err := http.NewRequest("GET", ts.URL, nil)
if err != nil {
panic(err)
}
res, err := cl.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
}
var (
cleanRE = regexp.MustCompile(`\n(->|<-) (Host|Date):.*`)
spaceRE = regexp.MustCompile(`(?m)\s+$`)
)
func logf(s string, v ...any) {
clean := cleanRE.ReplaceAllString(fmt.Sprintf(s, v...), "")
clean = spaceRE.ReplaceAllString(clean, "")
fmt.Println(clean)
}
func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/html")
_, _ = io.WriteString(res, strings.TrimSpace(content))
})
}
Output: -> GET / HTTP/1.1 -> User-Agent: Go-http-client/1.1 -> Accept-Encoding: gzip -> -> <- HTTP/1.1 200 OK <- Content-Length: 18 <- Content-Type: text/html <- <- <body>hello</body>
Example (Printf) ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"github.com/kenshaw/httplog"
)
func main() {
ts := httptest.NewServer(writeHTML(`<body>hello</body>`))
defer ts.Close()
// do http request (printf has same signature as fmt.Printf)
transport := httplog.NewPrefixedRoundTripLogger(nil, printf)
cl := &http.Client{
Transport: transport,
}
req, err := http.NewRequest("GET", ts.URL, nil)
if err != nil {
panic(err)
}
res, err := cl.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
}
var (
cleanRE = regexp.MustCompile(`\n(->|<-) (Host|Date):.*`)
spaceRE = regexp.MustCompile(`(?m)\s+$`)
)
func printf(s string, v ...any) (int, error) {
clean := cleanRE.ReplaceAllString(fmt.Sprintf(s, v...), "")
clean = spaceRE.ReplaceAllString(clean, "")
return fmt.Println(clean)
}
func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/html")
_, _ = io.WriteString(res, strings.TrimSpace(content))
})
}
Output: -> GET / HTTP/1.1 -> User-Agent: Go-http-client/1.1 -> Accept-Encoding: gzip -> -> <- HTTP/1.1 200 OK <- Content-Length: 18 <- Content-Type: text/html <- <- <body>hello</body>
Example (WithReqResBody) ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"github.com/kenshaw/httplog"
)
func main() {
ts := httptest.NewServer(writeHTML(`<body>hello</body>`))
defer ts.Close()
// do http request (logf has same signature as log.Printf)
transport := httplog.NewPrefixedRoundTripLogger(nil, logf, httplog.WithReqResBody(false, false))
cl := &http.Client{
Transport: transport,
}
req, err := http.NewRequest("GET", ts.URL, nil)
if err != nil {
panic(err)
}
res, err := cl.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
}
var (
cleanRE = regexp.MustCompile(`\n(->|<-) (Host|Date):.*`)
spaceRE = regexp.MustCompile(`(?m)\s+$`)
)
func logf(s string, v ...any) {
clean := cleanRE.ReplaceAllString(fmt.Sprintf(s, v...), "")
clean = spaceRE.ReplaceAllString(clean, "")
fmt.Println(clean)
}
func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/html")
_, _ = io.WriteString(res, strings.TrimSpace(content))
})
}
Output: -> GET / HTTP/1.1 -> User-Agent: Go-http-client/1.1 -> Accept-Encoding: gzip -> -> <- HTTP/1.1 200 OK <- Content-Length: 18 <- Content-Type: text/html <- <-
Example (Writer) ¶
package main
import (
"io"
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"github.com/kenshaw/httplog"
)
func main() {
ts := httptest.NewServer(writeHTML(`<body>hello</body>`))
defer ts.Close()
w := NewMyWriter(os.Stdout)
// do http request (w is a io.Writer)
transport := httplog.NewPrefixedRoundTripLogger(nil, w)
cl := &http.Client{
Transport: transport,
}
req, err := http.NewRequest("GET", ts.URL, nil)
if err != nil {
panic(err)
}
res, err := cl.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
}
var (
cleanRE = regexp.MustCompile(`\n(->|<-) (Host|Date):.*`)
spaceRE = regexp.MustCompile(`(?m)\s+$`)
)
func NewMyWriter(w io.Writer) *MyWriter {
return &MyWriter{w: w}
}
type MyWriter struct {
w io.Writer
}
func (w *MyWriter) Write(buf []byte) (int, error) {
clean := cleanRE.ReplaceAll(buf, nil)
clean = spaceRE.ReplaceAll(clean, nil)
return os.Stdout.Write(append(clean, '\n'))
}
func writeHTML(content string) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/html")
_, _ = io.WriteString(res, strings.TrimSpace(content))
})
}
Output: -> GET / HTTP/1.1 -> User-Agent: Go-http-client/1.1 -> Accept-Encoding: gzip -> -> <- HTTP/1.1 200 OK <- Content-Length: 18 <- Content-Type: text/html <- <- <body>hello</body>
func NewPrefixedRoundTripLogger ¶
func NewPrefixedRoundTripLogger(transport http.RoundTripper, logger any, opts ...Option) *RoundTripLogger
NewPrefixedRoundTripLogger creates a new HTTP transport that logs the raw (outgoing) HTTP request and response to the provided logger.
Prefixes requests and responses with "-> " and "<-", respectively. Adds an additional blank line ("\n\n") to the output of requests and responses.
Valid types for logger:
io.Writer
func(string, ...interface{}) (int, error) // fmt.Printf
func(string, ...interface{}) // log.Printf
Note: will panic() when an unknown logger type is passed.
func NewRoundTripLogger ¶
func NewRoundTripLogger(transport http.RoundTripper, reqf, resf func([]byte), opts ...Option) *RoundTripLogger
NewRoundTripLogger creates a new HTTP transport that logs the raw (outgoing) HTTP request and response.
func (*RoundTripLogger) RoundTrip ¶
RoundTrip satisfies the http.RoundTripper interface.