httplog

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2024 License: MIT Imports: 5 Imported by: 9

README

httplog

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.

Example

// _example/example.go
package main

import (
	"log"
	"net/http"
	"os"

	"github.com/kenshaw/httplog"
)

func main() {
	cl := &http.Client{
		Transport: httplog.NewPrefixedRoundTripLogger(nil, os.Stdout),
		// without request or response body
		// Transport: httplog.NewPrefixedRoundTripLogger(nil, os.Stdout, httplog.WithReqResBody(false, false)),
	}
	req, err := http.NewRequest("GET", "https://google.com", nil)
	if err != nil {
		log.Fatal(err)
	}
	res, err := cl.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
}

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

View Source
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

func WithReqResBody(req, res bool) Option

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 ...interface{}) {
	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 ...interface{}) (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 ...interface{}) {
	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 interface{}, 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

func (l *RoundTripLogger) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip satisfies the http.RoundTripper interface.

Directories

Path Synopsis
_example/example.go
_example/example.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL