~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
improve logging
- example/main.go +5 -14
- example/pages/api/recover/get.go +17 -0
- go.mod +2 -3
- go.sum +4 -8
- http.go +98 -28
example/main.go
CHANGED
|
@@ -17,8 +17,8 @@ import (
|
|
|
17
17
|
"github.com/pyros2097/gromer/example/pages/api/todos"
|
|
18
18
|
"github.com/pyros2097/gromer/example/pages"
|
|
19
19
|
"github.com/pyros2097/gromer/example/pages/about"
|
|
20
|
+
"github.com/pyros2097/gromer/example/pages/api/recover"
|
|
20
21
|
"github.com/pyros2097/gromer/example/pages/api/todos/_todoId_"
|
|
21
|
-
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
//go:embed assets/*
|
|
@@ -27,7 +27,8 @@ var assetsFS embed.FS
|
|
|
27
27
|
func main() {
|
|
28
28
|
isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
29
29
|
r := mux.NewRouter()
|
|
30
|
+
r.Use(gromer.LogMiddleware)
|
|
30
|
-
r.NotFoundHandler =
|
|
31
|
+
r.NotFoundHandler = gromer.NotFoundHandler
|
|
31
32
|
r.PathPrefix("/assets/").Handler(wrapCache(http.FileServer(http.FS(assetsFS))))
|
|
32
33
|
handle(r, "GET", "/api", gromer.ApiExplorer(apiDefinitions()))
|
|
33
34
|
handle(r, "GET", "/about", about.GET)
|
|
@@ -36,6 +37,7 @@ func main() {
|
|
|
36
37
|
handle(r, "PUT", "/api/todos/{todoId}", todos_todoId_.PUT)
|
|
37
38
|
handle(r, "GET", "/api/todos", todos.GET)
|
|
38
39
|
handle(r, "POST", "/api/todos", todos.POST)
|
|
40
|
+
handle(r, "GET", "/api/recover", recover.GET)
|
|
39
41
|
handle(r, "GET", "/", pages.GET)
|
|
40
42
|
|
|
41
43
|
if !isLambda {
|
|
@@ -59,16 +61,8 @@ func wrapCache(h http.Handler) http.Handler {
|
|
|
59
61
|
})
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
func notFound(w http.ResponseWriter, r *http.Request) {
|
|
63
|
-
gromer.LogReq(404, r)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
64
|
func handle(router *mux.Router, method, route string, h interface{}) {
|
|
67
65
|
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
68
|
-
var status int
|
|
69
|
-
defer func() {
|
|
70
|
-
gromer.LogReq(status, r)
|
|
71
|
-
}()
|
|
72
66
|
ctx, err := context.WithContext(c.WithValue(
|
|
73
67
|
c.WithValue(
|
|
74
68
|
c.WithValue(r.Context(), "assetsFS", assetsFS),
|
|
@@ -78,10 +72,7 @@ func handle(router *mux.Router, method, route string, h interface{}) {
|
|
|
78
72
|
gromer.RespondError(w, 500, err)
|
|
79
73
|
return
|
|
80
74
|
}
|
|
81
|
-
|
|
75
|
+
gromer.PerformRequest(route, h, ctx, w, r)
|
|
82
|
-
if err != nil {
|
|
83
|
-
log.Error().Stack().Err(err).Msg("")
|
|
84
|
-
}
|
|
85
76
|
}).Methods(method)
|
|
86
77
|
}
|
|
87
78
|
|
example/pages/api/recover/get.go
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package recover
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
type Params struct {
|
|
9
|
+
Limit int `json:"limit"`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func GET(ctx context.Context, params Params) (*Params, int, error) {
|
|
13
|
+
arr := []string{}
|
|
14
|
+
v := arr[55]
|
|
15
|
+
fmt.Printf("%s", v)
|
|
16
|
+
return ¶ms, 200, nil
|
|
17
|
+
}
|
go.mod
CHANGED
|
@@ -6,13 +6,12 @@ require (
|
|
|
6
6
|
github.com/apex/gateway/v2 v2.0.0
|
|
7
7
|
github.com/aymerick/raymond v2.0.2+incompatible
|
|
8
8
|
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible
|
|
9
|
-
github.com/fatih/color v1.13.0
|
|
10
9
|
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
|
|
11
|
-
github.com/go-playground/validator/v10 v10.9.0
|
|
10
|
+
github.com/go-playground/validator/v10 v10.9.0
|
|
12
11
|
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
|
|
13
12
|
github.com/google/uuid v1.3.0
|
|
14
13
|
github.com/gorilla/mux v1.8.0
|
|
15
|
-
github.com/iancoleman/strcase v0.2.0
|
|
14
|
+
github.com/iancoleman/strcase v0.2.0
|
|
16
15
|
github.com/lib/pq v1.10.4
|
|
17
16
|
github.com/markbates/inflect v1.0.4
|
|
18
17
|
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
go.sum
CHANGED
|
@@ -153,8 +153,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
|
|
153
153
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
154
154
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
|
155
155
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
156
|
-
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
|
157
|
-
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
|
158
156
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
|
159
157
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
|
160
158
|
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf h1:NrF81UtW8gG2LBGkXFQFqlfNnvMt9WdB46sfdJY4oqc=
|
|
@@ -167,6 +165,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
|
|
167
165
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
168
166
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
169
167
|
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
|
168
|
+
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
|
170
169
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
171
170
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
172
171
|
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
|
@@ -296,12 +295,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|
|
296
295
|
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
297
296
|
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
|
298
297
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
299
|
-
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
300
298
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
299
|
+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
301
300
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
302
301
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
303
|
-
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
304
302
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
303
|
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
305
304
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
306
305
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
307
306
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
|
@@ -311,12 +310,8 @@ github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
|
|
311
310
|
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
312
311
|
github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
|
|
313
312
|
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
|
|
314
|
-
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
|
315
|
-
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
316
313
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
|
317
314
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
318
|
-
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
|
319
|
-
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
320
315
|
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
|
321
316
|
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
|
322
317
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
@@ -334,6 +329,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
|
|
334
329
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
|
335
330
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
336
331
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
332
|
+
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
|
337
333
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
|
338
334
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
339
335
|
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
|
http.go
CHANGED
|
@@ -2,25 +2,38 @@ package gromer
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"net"
|
|
5
7
|
"net/http"
|
|
6
8
|
"os"
|
|
7
9
|
"reflect"
|
|
8
10
|
"regexp"
|
|
11
|
+
"runtime/debug"
|
|
9
12
|
"strconv"
|
|
10
13
|
"strings"
|
|
11
14
|
"time"
|
|
12
15
|
|
|
13
|
-
"github.com/fatih/color"
|
|
14
16
|
"github.com/go-playground/validator/v10"
|
|
15
17
|
"github.com/gorilla/mux"
|
|
16
18
|
"github.com/iancoleman/strcase"
|
|
17
19
|
"github.com/rs/zerolog"
|
|
18
20
|
"github.com/rs/zerolog/log"
|
|
21
|
+
"github.com/rs/zerolog/pkgerrors"
|
|
19
22
|
)
|
|
20
23
|
|
|
24
|
+
var info *debug.BuildInfo
|
|
25
|
+
var IsLambda bool
|
|
26
|
+
|
|
21
27
|
func init() {
|
|
28
|
+
IsLambda = os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
29
|
+
info, _ = debug.ReadBuildInfo()
|
|
22
|
-
zerolog.
|
|
30
|
+
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
|
31
|
+
if !IsLambda {
|
|
23
|
-
|
|
32
|
+
log.Logger = log.Output(zerolog.ConsoleWriter{
|
|
33
|
+
Out: os.Stdout,
|
|
34
|
+
TimeFormat: zerolog.TimeFormatUnix,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
@@ -29,6 +42,10 @@ func RespondError(w http.ResponseWriter, status int, err error) {
|
|
|
29
42
|
merror := map[string]interface{}{
|
|
30
43
|
"error": err.Error(),
|
|
31
44
|
}
|
|
45
|
+
if status >= 500 {
|
|
46
|
+
log.Error().Str("type", "panic").Msg(err.Error())
|
|
47
|
+
merror["error"] = "Internal Server Error"
|
|
48
|
+
}
|
|
32
49
|
validationErrors, ok := err.(validator.ValidationErrors)
|
|
33
50
|
if ok {
|
|
34
51
|
emap := map[string]string{}
|
|
@@ -63,7 +80,7 @@ func GetRouteParams(route string) []string {
|
|
|
63
80
|
return params
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request)
|
|
83
|
+
func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
|
67
84
|
params := GetRouteParams(route)
|
|
68
85
|
args := []reflect.Value{reflect.ValueOf(ctx)}
|
|
69
86
|
funcType := reflect.TypeOf(h)
|
|
@@ -76,13 +93,13 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
76
93
|
structType := funcType.In(icount - 1)
|
|
77
94
|
instance := reflect.New(structType)
|
|
78
95
|
if structType.Kind() != reflect.Struct {
|
|
79
|
-
|
|
96
|
+
log.Fatal().Msgf("router '%s' func final param should be a struct", route)
|
|
80
97
|
}
|
|
81
98
|
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
|
|
82
99
|
err := json.NewDecoder(r.Body).Decode(instance.Interface())
|
|
83
100
|
if err != nil {
|
|
84
101
|
RespondError(w, 400, err)
|
|
85
|
-
return
|
|
102
|
+
return
|
|
86
103
|
}
|
|
87
104
|
} else if r.Method == "GET" {
|
|
88
105
|
rv := instance.Elem()
|
|
@@ -103,7 +120,7 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
103
120
|
v, err := strconv.ParseInt(jsonValue, 10, base)
|
|
104
121
|
if err != nil {
|
|
105
122
|
RespondError(w, 400, err)
|
|
106
|
-
return
|
|
123
|
+
return
|
|
107
124
|
}
|
|
108
125
|
f.SetInt(v)
|
|
109
126
|
}
|
|
@@ -114,12 +131,12 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
114
131
|
v, err := time.Parse(time.RFC3339, jsonValue)
|
|
115
132
|
if err != nil {
|
|
116
133
|
RespondError(w, 400, err)
|
|
117
|
-
return
|
|
134
|
+
return
|
|
118
135
|
}
|
|
119
136
|
f.Set(reflect.ValueOf(v))
|
|
120
137
|
}
|
|
121
138
|
} else {
|
|
122
|
-
|
|
139
|
+
log.Fatal().Msgf("Uknown query param: '%s' '%s'", jsonName, jsonValue)
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
}
|
|
@@ -132,37 +149,90 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
132
149
|
responseError := values[2].Interface()
|
|
133
150
|
if responseError != nil {
|
|
134
151
|
RespondError(w, responseStatus, responseError.(error))
|
|
135
|
-
return
|
|
152
|
+
return
|
|
136
153
|
}
|
|
137
154
|
if v, ok := response.(HtmlPage); ok {
|
|
138
155
|
w.Header().Set("Content-Type", "text/html")
|
|
139
156
|
// This has to be at end always
|
|
140
157
|
w.WriteHeader(responseStatus)
|
|
141
158
|
v.WriteHtml(w)
|
|
142
|
-
return
|
|
159
|
+
return
|
|
143
160
|
}
|
|
144
161
|
w.Header().Set("Content-Type", "application/json")
|
|
145
162
|
// This has to be at end always
|
|
146
163
|
w.WriteHeader(responseStatus)
|
|
147
164
|
data, _ := json.Marshal(response)
|
|
148
165
|
w.Write(data)
|
|
149
|
-
return 200, nil
|
|
150
166
|
}
|
|
151
167
|
|
|
168
|
+
type writeCounter int64
|
|
169
|
+
|
|
152
|
-
func
|
|
170
|
+
func (wc *writeCounter) Write(p []byte) (n int, err error) {
|
|
153
|
-
|
|
171
|
+
*wc += writeCounter(len(p))
|
|
154
|
-
|
|
172
|
+
return len(p), nil
|
|
155
|
-
a = color.FgRed
|
|
156
|
-
} else if status >= 400 {
|
|
157
|
-
a = color.FgYellow
|
|
158
|
-
|
|
173
|
+
}
|
|
159
|
-
m := color.FgCyan
|
|
160
|
-
if r.Method == "POST" {
|
|
161
|
-
m = color.FgYellow
|
|
162
|
-
|
|
174
|
+
func headerSize(h http.Header) int64 {
|
|
163
|
-
|
|
175
|
+
var wc writeCounter
|
|
176
|
+
h.Write(&wc)
|
|
164
|
-
|
|
177
|
+
return int64(wc) + 2 // for CRLF
|
|
165
|
-
m = color.FgRed
|
|
166
|
-
|
|
178
|
+
}
|
|
179
|
+
|
|
167
|
-
|
|
180
|
+
type LogResponseWriter struct {
|
|
181
|
+
http.ResponseWriter
|
|
182
|
+
responseStatusCode int
|
|
183
|
+
responseContentLength int
|
|
184
|
+
responseHeaderSize int
|
|
168
185
|
}
|
|
186
|
+
|
|
187
|
+
func NewLogResponseWriter(w http.ResponseWriter) *LogResponseWriter {
|
|
188
|
+
return &LogResponseWriter{ResponseWriter: w}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func (w *LogResponseWriter) WriteHeader(code int) {
|
|
192
|
+
w.ResponseWriter.WriteHeader(code)
|
|
193
|
+
w.responseStatusCode = code
|
|
194
|
+
w.responseHeaderSize = int(headerSize(w.Header()))
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func (w *LogResponseWriter) Write(body []byte) (int, error) {
|
|
199
|
+
w.responseContentLength += len(body)
|
|
200
|
+
return w.ResponseWriter.Write(body)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
var LogMiddleware = mux.MiddlewareFunc(func(next http.Handler) http.Handler {
|
|
204
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
205
|
+
defer func() {
|
|
206
|
+
if err := recover(); err != nil {
|
|
207
|
+
stack := string(debug.Stack())
|
|
208
|
+
RespondError(w, 500, fmt.Errorf("panic: %+v\n %s", err, stack))
|
|
209
|
+
}
|
|
210
|
+
}()
|
|
211
|
+
startTime := time.Now()
|
|
212
|
+
logRespWriter := NewLogResponseWriter(w)
|
|
213
|
+
next.ServeHTTP(logRespWriter, r)
|
|
214
|
+
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
|
215
|
+
if len(ip) > 0 && ip[0] == '[' {
|
|
216
|
+
ip = ip[1 : len(ip)-1]
|
|
217
|
+
}
|
|
218
|
+
log.Info().
|
|
219
|
+
Str("method", r.Method).
|
|
220
|
+
Str("url", r.URL.String()).
|
|
221
|
+
Int("header_size", int(headerSize(r.Header))).
|
|
222
|
+
Int64("body_size", r.ContentLength).
|
|
223
|
+
Str("host", r.Host).
|
|
224
|
+
// Str("agent", r.UserAgent()).
|
|
225
|
+
Str("referer", r.Referer()).
|
|
226
|
+
Str("proto", r.Proto).
|
|
227
|
+
Str("remote_ip", ip).
|
|
228
|
+
Int("status", logRespWriter.responseStatusCode).
|
|
229
|
+
Int("resp_header_size", logRespWriter.responseHeaderSize).
|
|
230
|
+
Int("resp_body_size", logRespWriter.responseContentLength).
|
|
231
|
+
Str("latency", time.Since(startTime).String()).
|
|
232
|
+
Msg("")
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
var NotFoundHandler = LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
237
|
+
RespondError(w, 404, fmt.Errorf("path '%s' not found", r.URL.String()))
|
|
238
|
+
}))
|