rest.Logger   F
last analyzed

Complexity

Conditions 15

Size

Total Lines 58
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 34
dl 0
loc 58
rs 2.9998
c 0
b 0
f 0
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like rest.Logger often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package rest
2
3
import (
4
	"bytes"
5
	"io/ioutil"
6
	"log"
7
	"net/http"
8
	"net/url"
9
	"os"
10
	"regexp"
11
	"runtime/debug"
12
	"strings"
13
	"time"
14
15
	"github.com/go-chi/chi/middleware"
16
	//"github.com/go-chi/render"
17
)
18
19
// JSON is a map alias, just for convenience
20
type JSON map[string]interface{}
21
22
// AppInfo adds custom app-info to the response header
23
func AppInfo(app string, version string) func(http.Handler) http.Handler {
24
	f := func(h http.Handler) http.Handler {
25
		fn := func(w http.ResponseWriter, r *http.Request) {
26
			w.Header().Set("App-Name", app)
27
			w.Header().Set("App-Version", version)
28
			if mhost := os.Getenv("MHOST"); mhost != "" {
29
				w.Header().Set("Host", mhost)
30
			}
31
			h.ServeHTTP(w, r)
32
		}
33
		return http.HandlerFunc(fn)
34
	}
35
	return f
36
}
37
38
// CORS adds allow origin headers
39
func CORS(next http.Handler) http.Handler {
40
	fn := func(w http.ResponseWriter, r *http.Request) {
41
		w.Header().Set("Access-Control-Allow-Origin", "*")
42
		w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
43
		next.ServeHTTP(w, r)
44
	}
45
	return http.HandlerFunc(fn)
46
}
47
48
// Ping middleware response with pong to /ping. Stops chain if ping request detected
49
func Ping(next http.Handler) http.Handler {
50
	fn := func(w http.ResponseWriter, r *http.Request) {
51
52
		if r.Method == "GET" && strings.HasSuffix(strings.ToLower(r.URL.Path), "/ping") {
53
			w.Header().Set("Content-Type", "text/plain")
54
			w.WriteHeader(http.StatusOK)
55
			if _, err := w.Write([]byte("pong")); err != nil {
56
				log.Printf("[WARN] can't send pong, %s", err)
57
			}
58
			return
59
		}
60
		next.ServeHTTP(w, r)
61
	}
62
	return http.HandlerFunc(fn)
63
}
64
65
// Recoverer is a middleware that recovers from panics, logs the panic and returns a HTTP 500 status if possible.
66
func Recoverer(next http.Handler) http.Handler {
67
	fn := func(w http.ResponseWriter, r *http.Request) {
68
		defer func() {
69
			if rvr := recover(); rvr != nil {
70
				log.Printf("[WARN] request panic, %v", rvr)
71
				debug.PrintStack()
72
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
73
			}
74
		}()
75
		next.ServeHTTP(w, r)
76
	}
77
	return http.HandlerFunc(fn)
78
}
79
80
// LoggerFlag type
81
type LoggerFlag int
82
83
// logger flags enum
84
const (
85
	LogAll LoggerFlag = iota
86
	LogBody
87
)
88
const maxBody = 1024
89
90
var reMultWhtsp = regexp.MustCompile(`[\s\p{Zs}]{2,}`)
91
92
// Logger middleware prints http log. Customized by set of LoggerFlag
93
func Logger(flags ...LoggerFlag) func(http.Handler) http.Handler {
94
95
	inFlags := func(f LoggerFlag) bool {
96
		for _, flg := range flags {
97
			if flg == LogAll || flg == f {
98
				return true
99
			}
100
		}
101
		return false
102
	}
103
104
	f := func(h http.Handler) http.Handler {
105
106
		fn := func(w http.ResponseWriter, r *http.Request) {
107
			ww := middleware.NewWrapResponseWriter(w, 1)
108
109
			body := func() (result string) {
110
				if inFlags(LogBody) {
111
					if content, err := ioutil.ReadAll(r.Body); err == nil {
112
						result = string(content)
113
						r.Body = ioutil.NopCloser(bytes.NewReader(content))
114
115
						if len(result) > 0 {
116
							result = strings.ReplaceAll(result, "\n", " ")
117
							result = reMultWhtsp.ReplaceAllString(result, " ")
118
						}
119
120
						if len(result) > maxBody {
121
							result = result[:maxBody] + "..."
122
						}
123
					}
124
				}
125
				return result
126
			}()
127
128
			t1 := time.Now()
129
			defer func() {
130
				t2 := time.Now()
131
132
				q := r.URL.String()
133
				if qun, err := url.QueryUnescape(q); err == nil {
134
					q = qun
135
				}
136
				// hide id and pin
137
				regex := regexp.MustCompile(`favicon\.ico$`)
138
				if !regex.MatchString(q) {
139
					log.Printf("[DEBUG] REST %s - %s - %s - %d (%d) - %v %s",
140
						r.Method, q, strings.Split(r.RemoteAddr, ":")[0],
141
						ww.Status(), ww.BytesWritten(), t2.Sub(t1), body)
142
				}
143
			}()
144
145
			h.ServeHTTP(ww, r)
146
		}
147
		return http.HandlerFunc(fn)
148
	}
149
150
	return f
151
}
152