httplog

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2021 License: MIT Imports: 5 Imported by: 10

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.

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()
}

NewPrefixedTripLogger provides a convenient wrapper around using the standard library's io.Writer, and standard output func signatures for fmt.Printf and log.Printf.

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