add logFilter to debug request and response

This commit is contained in:
anoymouscoder
2021-01-19 14:00:08 +08:00
parent 2c9cd98d03
commit 535165d7f0
6 changed files with 194 additions and 47 deletions

View File

@@ -0,0 +1,130 @@
// Copyright 2020 beego
//
// 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 log
import (
"context"
"io"
"net/http"
"net/http/httputil"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
)
// FilterChainBuilder can build a log filter
type FilterChainBuilder struct {
printableContentTypes []string // only print the body of included mime types of request and response
log func(f interface{}, v ...interface{}) // custom log function
}
// BuilderOption option constructor
type BuilderOption func(*FilterChainBuilder)
type logInfo struct {
req []byte
resp []byte
err error
}
var defaultprintableContentTypes = []string{
"text/plain", "text/xml", "text/html", "text/csv",
"text/calendar", "text/javascript", "text/javascript",
"text/css",
}
// NewFilterChainBuilder initialize a filterChainBuilder, pass options to customize
func NewFilterChainBuilder(opts ...BuilderOption) *FilterChainBuilder {
res := &FilterChainBuilder{
printableContentTypes: defaultprintableContentTypes,
log: logs.Debug,
}
for _, o := range opts {
o(res)
}
return res
}
// WithLog return option constructor modify log function
func WithLog(f func(f interface{}, v ...interface{})) BuilderOption {
return func(h *FilterChainBuilder) {
h.log = f
}
}
// WithprintableContentTypes return option constructor modify printableContentTypes
func WithprintableContentTypes(types []string) BuilderOption {
return func(h *FilterChainBuilder) {
h.printableContentTypes = types
}
}
// FilterChain can print the request after FilterChain processing and response before processsing
func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
info := &logInfo{}
defer info.print(builder.log)
resp, err := next(ctx, req)
info.err = err
contentType := req.GetRequest().Header.Get("Content-Type")
shouldPrintBody := builder.shouldPrintBody(contentType, req.GetRequest().Body)
dump, err := httputil.DumpRequest(req.GetRequest(), shouldPrintBody)
info.req = dump
if err != nil {
logs.Error(err)
}
if resp != nil {
contentType = resp.Header.Get("Content-Type")
shouldPrintBody = builder.shouldPrintBody(contentType, resp.Body)
dump, err = httputil.DumpResponse(resp, shouldPrintBody)
info.resp = dump
if err != nil {
logs.Error(err)
}
}
return resp, err
}
}
func (builder *FilterChainBuilder) shouldPrintBody(contentType string, body io.ReadCloser) bool {
if contains(builder.printableContentTypes, contentType) {
return true
}
if body != nil {
logs.Warn("printableContentTypes do not contain %s, if you want to print request and response body please add it.", contentType)
}
return false
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func (info *logInfo) print(log func(f interface{}, v ...interface{})) {
log("Request: ====================")
log("%q", info.req)
log("Response: ===================")
log("%q", info.resp)
if info.err != nil {
log("Error: ======================")
log("%q", info.err)
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2020 beego
//
// 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 log
import (
"context"
"net/http"
"testing"
"time"
"github.com/beego/beego/v2/client/httplib"
"github.com/stretchr/testify/assert"
)
func TestFilterChain(t *testing.T) {
next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
time.Sleep(100 * time.Millisecond)
return &http.Response{
StatusCode: 404,
}, nil
}
builder := NewFilterChainBuilder()
filter := builder.FilterChain(next)
req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego")
resp, err := filter(context.Background(), req)
assert.NotNil(t, resp)
assert.Nil(t, err)
}
func TestContains(t *testing.T) {
jsonType := "application/json"
cases := []struct {
Name string
Types []string
ContentType string
Expected bool
}{
{"case1", []string{jsonType}, jsonType, true},
{"case2", []string{"text/plain"}, jsonType, false},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if ans := contains(c.Types, c.ContentType); ans != c.Expected {
t.Fatalf("Types: %v, ContentType: %v, expected %v, but %v got",
c.Types, c.ContentType, c.Expected, ans)
}
})
}
}