Skip to content

Commit 608e209

Browse files
authored
Merge pull request #9 from panz3r/main
add Requests logger
2 parents 2c6441e + b21a96b commit 608e209

8 files changed

Lines changed: 226 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,5 @@ This is not needed for most of your assets because their filenames should contai
155155
| SPA_MODE | `--spa` or `--spa <bool>` | When SPA mode if file for requested path does not exists server returns index.html from root of serving directory. SPA mode and directory listing cannot be enabled at the same time | `true` |
156156
| CACHE | `--cache` | When enabled f.Open reads are being cached using Two Queue LRU Cache in bits | `true` |
157157
| CACHE_BUFFER | `--cache-buffer <number>` | Specifies the maximum size of LRU cache in bytes | `51200` |
158+
| LOGGER | `--logger` | Enable requests logger | `false` |
159+
| LOG_PRETTY | `--log-pretty` | Print log messages in a pretty format instead of default JSON format | `false` |

src/app/app.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/andybalholm/brotli"
88
lru "github.com/hashicorp/golang-lru"
99
"go-http-server/param"
10+
"go-http-server/util"
1011
"golang.org/x/exp/slices"
1112
"io"
1213
"mime"
@@ -256,9 +257,16 @@ func (app *App) HandlerFuncNew(w http.ResponseWriter, r *http.Request) {
256257
}
257258

258259
func (app *App) Listen() {
260+
var handlerFunc http.Handler = http.HandlerFunc(app.HandlerFuncNew)
261+
if app.params.Logger {
262+
handlerFunc = util.LogRequestHandler(handlerFunc, &util.LogRequestHandlerOptions{
263+
Pretty: app.params.LogPretty,
264+
})
265+
}
266+
259267
app.server = &http.Server{
260268
Addr: fmt.Sprintf("%s:%d", app.params.Address, app.params.Port),
261-
Handler: http.HandlerFunc(app.HandlerFuncNew),
269+
Handler: handlerFunc,
262270
}
263271

264272
fmt.Printf("Server listening on http://%s\n", app.server.Addr)

src/go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@ module go-http-server
33
go 1.18
44

55
require (
6+
bou.ke/monkey v1.0.2
67
github.com/andybalholm/brotli v1.0.4
8+
github.com/felixge/httpsnoop v1.0.3
79
github.com/hashicorp/golang-lru v0.5.4
10+
github.com/rs/zerolog v1.29.1
811
github.com/urfave/cli/v2 v2.16.3
912
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
1013
)
1114

1215
require (
13-
bou.ke/monkey v1.0.2 // indirect
1416
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
17+
github.com/mattn/go-colorable v0.1.13 // indirect
18+
github.com/mattn/go-isatty v0.0.19 // indirect
1519
github.com/russross/blackfriday/v2 v2.1.0 // indirect
1620
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
21+
golang.org/x/sys v0.10.0 // indirect
1722
)

src/go.sum

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,25 @@ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=
22
bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA=
33
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
44
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
5+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
56
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
67
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
8+
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
9+
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
10+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
711
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
812
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
13+
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
14+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
15+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
16+
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
17+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
18+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
19+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
20+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
21+
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
22+
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
23+
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
924
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
1025
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
1126
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
@@ -14,3 +29,9 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
1429
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
1530
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
1631
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
32+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33+
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
37+
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

src/param/param.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ var Flags = []cli.Flag{
6969
Name: "cache-buffer",
7070
Value: 50 * 1024,
7171
},
72+
&cli.BoolFlag{
73+
EnvVars: []string{"LOGGER"},
74+
Name: "logger",
75+
Value: false,
76+
},
77+
&cli.BoolFlag{
78+
EnvVars: []string{"LOG_PRETTY"},
79+
Name: "log-pretty",
80+
Value: false,
81+
},
7282
}
7383

7484
type Params struct {
@@ -83,8 +93,9 @@ type Params struct {
8393
IgnoreCacheControlPaths []string
8494
CacheEnabled bool
8595
CacheBuffer int
96+
Logger bool
97+
LogPretty bool
8698
//DirectoryListing bool
87-
8899
}
89100

90101
func ContextToParams(c *cli.Context) *Params {
@@ -100,6 +111,8 @@ func ContextToParams(c *cli.Context) *Params {
100111
IgnoreCacheControlPaths: c.StringSlice("ignore-cache-control-paths"),
101112
CacheEnabled: c.Bool("cache"),
102113
CacheBuffer: c.Int("cache-buffer"),
114+
Logger: c.Bool("logger"),
115+
LogPretty: c.Bool("log-pretty"),
103116
//DirectoryListing: c.Bool("directory-listing"),
104117
}
105118
}

src/util/http.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package util
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"strings"
7+
)
8+
9+
// Request.RemoteAddress contains port, which we want to remove i.e.:
10+
// "[::1]:58292" => "[::1]"
11+
func ipAddrFromRemoteAddr(s string) string {
12+
// return full string for IPv6 inputs wihtout port
13+
if strings.LastIndex(s, "]") == len(s)-1 {
14+
return s
15+
}
16+
17+
idx := strings.LastIndex(s, ":")
18+
if idx == -1 {
19+
return s
20+
}
21+
22+
return s[:idx]
23+
}
24+
25+
// requestGetRemoteAddress returns ip address of the client making the request,
26+
// taking into account http proxies
27+
func requestGetRemoteAddress(r *http.Request) net.IP {
28+
hdr := r.Header
29+
30+
hdrRealIP := hdr.Get("X-Real-Ip")
31+
hdrForwardedFor := hdr.Get("X-Forwarded-For")
32+
if hdrRealIP == "" && hdrForwardedFor == "" {
33+
return net.ParseIP(ipAddrFromRemoteAddr(r.RemoteAddr))
34+
}
35+
36+
if hdrForwardedFor != "" {
37+
// X-Forwarded-For is potentially a list of addresses separated with ","
38+
parts := strings.Split(hdrForwardedFor, ",")
39+
fwdIPs := make([]net.IP, len(parts))
40+
for i, p := range parts {
41+
fwdIPs[i] = net.ParseIP(ipAddrFromRemoteAddr(strings.TrimSpace(p)))
42+
}
43+
// return first address
44+
return fwdIPs[0]
45+
}
46+
47+
return net.ParseIP(hdrRealIP)
48+
}

src/util/http_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package util
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
)
7+
8+
func TestIPAddrFromRemoteAddr(t *testing.T) {
9+
tests := []struct {
10+
remoteAddr string
11+
expected string
12+
}{
13+
{"[::1]:58292", "[::1]"},
14+
{"127.0.0.1:12345", "127.0.0.1"},
15+
{"[::1]", "[::1]"},
16+
{"127.0.0.1", "127.0.0.1"},
17+
}
18+
19+
for _, tt := range tests {
20+
actual := ipAddrFromRemoteAddr(tt.remoteAddr)
21+
if actual != tt.expected {
22+
t.Errorf("ipAddrFromRemoteAddr(%s): expected %s, got %s", tt.remoteAddr, tt.expected, actual)
23+
}
24+
}
25+
}
26+
27+
func TestRequestGetRemoteAddress(t *testing.T) {
28+
tests := []struct {
29+
headerRealIP string
30+
headerForwardedFor string
31+
remoteAddr string
32+
expected string
33+
}{
34+
{"", "", "127.0.0.1:12345", "127.0.0.1"},
35+
{"", "192.168.0.1, 127.0.0.1", "127.0.0.1:12345", "192.168.0.1"},
36+
{"192.168.0.1", "", "127.0.0.1:12345", "192.168.0.1"},
37+
{"192.168.0.1", "192.168.0.2, 127.0.0.1", "127.0.0.1:12345", "192.168.0.2"},
38+
}
39+
40+
for _, tt := range tests {
41+
req, _ := http.NewRequest("GET", "/", nil)
42+
req.Header.Set("X-Real-Ip", tt.headerRealIP)
43+
req.Header.Set("X-Forwarded-For", tt.headerForwardedFor)
44+
req.RemoteAddr = tt.remoteAddr
45+
46+
actual := requestGetRemoteAddress(req)
47+
if actual.String() != tt.expected {
48+
t.Errorf("requestGetRemoteAddress(%s, %s, %s): expected %s, got %s", tt.headerRealIP, tt.headerForwardedFor, tt.remoteAddr, tt.expected, actual)
49+
}
50+
}
51+
}

src/util/log.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package util
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"os"
7+
"time"
8+
9+
"github.com/felixge/httpsnoop"
10+
"github.com/rs/zerolog"
11+
"github.com/rs/zerolog/log"
12+
)
13+
14+
type LogRequestHandlerOptions struct {
15+
Pretty bool
16+
}
17+
18+
// LogReqInfo describes info about HTTP request
19+
type HTTPReqInfo struct {
20+
// GET etc.
21+
method string
22+
// requested path
23+
path string
24+
// response code, like 200, 404
25+
code int
26+
// number of bytes of the response sent
27+
size int64
28+
// how long did it take to
29+
duration time.Duration
30+
// client IP Address
31+
ipAddress net.IP
32+
// client UserAgent
33+
userAgent string
34+
// referer header
35+
referer string
36+
}
37+
38+
func logHTTPReqInfo(ri *HTTPReqInfo) {
39+
log.Info().
40+
Str("method", ri.method).
41+
Str("path", ri.path).
42+
Int("code", ri.code).
43+
Int64("size", ri.size).
44+
Dur("duration", ri.duration).
45+
IPAddr("ipAddress", ri.ipAddress).
46+
Str("userAgent", ri.userAgent).
47+
Str("referer", ri.referer).
48+
Send()
49+
}
50+
51+
func LogRequestHandler(h http.Handler, opt *LogRequestHandlerOptions) http.Handler {
52+
if opt.Pretty {
53+
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
54+
}
55+
56+
zerolog.DurationFieldUnit = time.Millisecond
57+
58+
fn := func(w http.ResponseWriter, r *http.Request) {
59+
// runs handler h and captures information about HTTP request
60+
mtr := httpsnoop.CaptureMetrics(h, w, r)
61+
62+
logHTTPReqInfo(&HTTPReqInfo{
63+
method: r.Method,
64+
path: r.URL.String(),
65+
code: mtr.Code,
66+
size: mtr.Written,
67+
duration: mtr.Duration,
68+
ipAddress: requestGetRemoteAddress(r),
69+
userAgent: r.Header.Get("User-Agent"),
70+
referer: r.Header.Get("Referer"),
71+
})
72+
}
73+
74+
return http.HandlerFunc(fn)
75+
}

0 commit comments

Comments
 (0)