Skip to content

Commit

Permalink
Merge pull request #9 from panz3r/main
Browse files Browse the repository at this point in the history
add Requests logger
  • Loading branch information
LbP22 committed Aug 1, 2023
2 parents 2c6441e + b21a96b commit 608e209
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,5 @@ This is not needed for most of your assets because their filenames should contai
| 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` |
| CACHE | `--cache` | When enabled f.Open reads are being cached using Two Queue LRU Cache in bits | `true` |
| CACHE_BUFFER | `--cache-buffer <number>` | Specifies the maximum size of LRU cache in bytes | `51200` |
| LOGGER | `--logger` | Enable requests logger | `false` |
| LOG_PRETTY | `--log-pretty` | Print log messages in a pretty format instead of default JSON format | `false` |
10 changes: 9 additions & 1 deletion src/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/andybalholm/brotli"
lru "github.com/hashicorp/golang-lru"
"go-http-server/param"
"go-http-server/util"
"golang.org/x/exp/slices"
"io"
"mime"
Expand Down Expand Up @@ -256,9 +257,16 @@ func (app *App) HandlerFuncNew(w http.ResponseWriter, r *http.Request) {
}

func (app *App) Listen() {
var handlerFunc http.Handler = http.HandlerFunc(app.HandlerFuncNew)
if app.params.Logger {
handlerFunc = util.LogRequestHandler(handlerFunc, &util.LogRequestHandlerOptions{
Pretty: app.params.LogPretty,
})
}

app.server = &http.Server{
Addr: fmt.Sprintf("%s:%d", app.params.Address, app.params.Port),
Handler: http.HandlerFunc(app.HandlerFuncNew),
Handler: handlerFunc,
}

fmt.Printf("Server listening on http://%s\n", app.server.Addr)
Expand Down
7 changes: 6 additions & 1 deletion src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ module go-http-server
go 1.18

require (
bou.ke/monkey v1.0.2
github.com/andybalholm/brotli v1.0.4
github.com/felixge/httpsnoop v1.0.3
github.com/hashicorp/golang-lru v0.5.4
github.com/rs/zerolog v1.29.1
github.com/urfave/cli/v2 v2.16.3
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
)

require (
bou.ke/monkey v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.10.0 // indirect
)
21 changes: 21 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=
bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
Expand All @@ -14,3 +29,9 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
15 changes: 14 additions & 1 deletion src/param/param.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ var Flags = []cli.Flag{
Name: "cache-buffer",
Value: 50 * 1024,
},
&cli.BoolFlag{
EnvVars: []string{"LOGGER"},
Name: "logger",
Value: false,
},
&cli.BoolFlag{
EnvVars: []string{"LOG_PRETTY"},
Name: "log-pretty",
Value: false,
},
}

type Params struct {
Expand All @@ -83,8 +93,9 @@ type Params struct {
IgnoreCacheControlPaths []string
CacheEnabled bool
CacheBuffer int
Logger bool
LogPretty bool
//DirectoryListing bool

}

func ContextToParams(c *cli.Context) *Params {
Expand All @@ -100,6 +111,8 @@ func ContextToParams(c *cli.Context) *Params {
IgnoreCacheControlPaths: c.StringSlice("ignore-cache-control-paths"),
CacheEnabled: c.Bool("cache"),
CacheBuffer: c.Int("cache-buffer"),
Logger: c.Bool("logger"),
LogPretty: c.Bool("log-pretty"),
//DirectoryListing: c.Bool("directory-listing"),
}
}
48 changes: 48 additions & 0 deletions src/util/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package util

import (
"net"
"net/http"
"strings"
)

// Request.RemoteAddress contains port, which we want to remove i.e.:
// "[::1]:58292" => "[::1]"
func ipAddrFromRemoteAddr(s string) string {
// return full string for IPv6 inputs wihtout port
if strings.LastIndex(s, "]") == len(s)-1 {
return s
}

idx := strings.LastIndex(s, ":")
if idx == -1 {
return s
}

return s[:idx]
}

// requestGetRemoteAddress returns ip address of the client making the request,
// taking into account http proxies
func requestGetRemoteAddress(r *http.Request) net.IP {
hdr := r.Header

hdrRealIP := hdr.Get("X-Real-Ip")
hdrForwardedFor := hdr.Get("X-Forwarded-For")
if hdrRealIP == "" && hdrForwardedFor == "" {
return net.ParseIP(ipAddrFromRemoteAddr(r.RemoteAddr))
}

if hdrForwardedFor != "" {
// X-Forwarded-For is potentially a list of addresses separated with ","
parts := strings.Split(hdrForwardedFor, ",")
fwdIPs := make([]net.IP, len(parts))
for i, p := range parts {
fwdIPs[i] = net.ParseIP(ipAddrFromRemoteAddr(strings.TrimSpace(p)))
}
// return first address
return fwdIPs[0]
}

return net.ParseIP(hdrRealIP)
}
51 changes: 51 additions & 0 deletions src/util/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package util

import (
"net/http"
"testing"
)

func TestIPAddrFromRemoteAddr(t *testing.T) {
tests := []struct {
remoteAddr string
expected string
}{
{"[::1]:58292", "[::1]"},
{"127.0.0.1:12345", "127.0.0.1"},
{"[::1]", "[::1]"},
{"127.0.0.1", "127.0.0.1"},
}

for _, tt := range tests {
actual := ipAddrFromRemoteAddr(tt.remoteAddr)
if actual != tt.expected {
t.Errorf("ipAddrFromRemoteAddr(%s): expected %s, got %s", tt.remoteAddr, tt.expected, actual)
}
}
}

func TestRequestGetRemoteAddress(t *testing.T) {
tests := []struct {
headerRealIP string
headerForwardedFor string
remoteAddr string
expected string
}{
{"", "", "127.0.0.1:12345", "127.0.0.1"},
{"", "192.168.0.1, 127.0.0.1", "127.0.0.1:12345", "192.168.0.1"},
{"192.168.0.1", "", "127.0.0.1:12345", "192.168.0.1"},
{"192.168.0.1", "192.168.0.2, 127.0.0.1", "127.0.0.1:12345", "192.168.0.2"},
}

for _, tt := range tests {
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("X-Real-Ip", tt.headerRealIP)
req.Header.Set("X-Forwarded-For", tt.headerForwardedFor)
req.RemoteAddr = tt.remoteAddr

actual := requestGetRemoteAddress(req)
if actual.String() != tt.expected {
t.Errorf("requestGetRemoteAddress(%s, %s, %s): expected %s, got %s", tt.headerRealIP, tt.headerForwardedFor, tt.remoteAddr, tt.expected, actual)
}
}
}
75 changes: 75 additions & 0 deletions src/util/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package util

import (
"net"
"net/http"
"os"
"time"

"github.com/felixge/httpsnoop"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)

type LogRequestHandlerOptions struct {
Pretty bool
}

// LogReqInfo describes info about HTTP request
type HTTPReqInfo struct {
// GET etc.
method string
// requested path
path string
// response code, like 200, 404
code int
// number of bytes of the response sent
size int64
// how long did it take to
duration time.Duration
// client IP Address
ipAddress net.IP
// client UserAgent
userAgent string
// referer header
referer string
}

func logHTTPReqInfo(ri *HTTPReqInfo) {
log.Info().
Str("method", ri.method).
Str("path", ri.path).
Int("code", ri.code).
Int64("size", ri.size).
Dur("duration", ri.duration).
IPAddr("ipAddress", ri.ipAddress).
Str("userAgent", ri.userAgent).
Str("referer", ri.referer).
Send()
}

func LogRequestHandler(h http.Handler, opt *LogRequestHandlerOptions) http.Handler {
if opt.Pretty {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}

zerolog.DurationFieldUnit = time.Millisecond

fn := func(w http.ResponseWriter, r *http.Request) {
// runs handler h and captures information about HTTP request
mtr := httpsnoop.CaptureMetrics(h, w, r)

logHTTPReqInfo(&HTTPReqInfo{
method: r.Method,
path: r.URL.String(),
code: mtr.Code,
size: mtr.Written,
duration: mtr.Duration,
ipAddress: requestGetRemoteAddress(r),
userAgent: r.Header.Get("User-Agent"),
referer: r.Header.Get("Referer"),
})
}

return http.HandlerFunc(fn)
}

0 comments on commit 608e209

Please sign in to comment.