revendor with latest lgr (logging)
This commit is contained in:
Generated
+6
-6
@@ -135,12 +135,12 @@
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b1f422f560b103f435f8501f854f238f1ce00d0f8146e76eacdfa71f2c8d8c0"
|
||||
digest = "1:6734b6a2ee0f6ccf35d8f3f72ce8c1e4f11e7060b284443502668c0805ff6aa2"
|
||||
name = "github.com/go-pkgz/lgr"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "dbd940fec9650260d15aa071cab658d0502ed92f"
|
||||
version = "v0.3.2"
|
||||
revision = "6e5c31cef8b3f015033c6857e3368a08f97fa679"
|
||||
version = "v0.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:62aeed92576ea10c25389334254f957594b4b5ae8ff71a53d3aa9df5dc6961eb"
|
||||
@@ -162,7 +162,7 @@
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:27df73e1f59fffb9aaa2c28ba81561f177710576f18ad602564949355fac3e23"
|
||||
digest = "1:74e7f0d5cf9c383695eabb20764c189c4ac898d89883ab31732d838874ac0998"
|
||||
name = "github.com/go-pkgz/rest"
|
||||
packages = [
|
||||
".",
|
||||
@@ -170,8 +170,8 @@
|
||||
"logger",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "27af5e3ba9439ec0df51c30794f473befe0a0e6d"
|
||||
version = "v1.3.1"
|
||||
revision = "f726ac4d98016b5009883c2d3c20a9c8ce873ee1"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:92b44856ee15e8a98b91d751a60b512017e0ba227d1ed9d2c02ad13d67062ff8"
|
||||
|
||||
+2
-2
@@ -62,10 +62,10 @@ func main() {
|
||||
|
||||
func setupLog(dbg bool) {
|
||||
if dbg {
|
||||
log.Setup(log.Debug, log.CallerFile, log.Msec, log.LevelBraces)
|
||||
log.Setup(log.Debug, log.CallerFile, log.Msec, log.LevelBraces, log.CallerIgnore("logger"))
|
||||
return
|
||||
}
|
||||
log.Setup(log.Msec, log.LevelBraces, log.CallerPkg, log.CallerIgnore("logger"))
|
||||
log.Setup(log.Msec, log.LevelBraces, log.CallerPkg, log.CallerIgnore("logger", "rest"))
|
||||
}
|
||||
|
||||
// getDump reads runtime stack and returns as a string
|
||||
|
||||
@@ -183,13 +183,13 @@ func (s *Rest) routes() chi.Router {
|
||||
authHandler, avatarHandler := s.Authenticator.Handlers()
|
||||
|
||||
router.Group(func(r chi.Router) {
|
||||
l := logger.New(logger.Flags(logger.All), logger.Log(log.Default()), logger.IPfn(ipFn), logger.Prefix("[INFO]"))
|
||||
l := logger.New(logger.Log(log.Default()), logger.WithBody, logger.IPfn(ipFn), logger.Prefix("[INFO]"))
|
||||
r.Use(l.Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(5, nil)))
|
||||
r.Mount("/auth", authHandler)
|
||||
})
|
||||
|
||||
router.Group(func(r chi.Router) {
|
||||
r.Use(logger.New(logger.Flags(logger.None)).Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil)))
|
||||
r.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil)))
|
||||
r.Mount("/avatar", avatarHandler)
|
||||
})
|
||||
|
||||
@@ -199,7 +199,7 @@ func (s *Rest) routes() chi.Router {
|
||||
router.Route("/api/v1", func(rapi chi.Router) {
|
||||
|
||||
rapi.Group(func(rava chi.Router) {
|
||||
rava.Use(logger.New(logger.Flags(logger.None)).Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil)))
|
||||
rava.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil)))
|
||||
rava.Mount("/avatar", avatarHandler)
|
||||
})
|
||||
|
||||
@@ -207,7 +207,7 @@ func (s *Rest) routes() chi.Router {
|
||||
rapi.Group(func(ropen chi.Router) {
|
||||
ropen.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)))
|
||||
ropen.Use(authMiddleware.Trace)
|
||||
ropen.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()),
|
||||
ropen.Use(logger.New(logger.Log(log.Default()), logger.WithBody,
|
||||
logger.Prefix("[INFO]"), logger.IPfn(ipFn)).Handler)
|
||||
ropen.Get("/find", s.findCommentsCtrl)
|
||||
ropen.Get("/id/{id}", s.commentByIDCtrl)
|
||||
@@ -228,7 +228,7 @@ func (s *Rest) routes() chi.Router {
|
||||
rapi.Group(func(rauth chi.Router) {
|
||||
rauth.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)))
|
||||
rauth.Use(authMiddleware.Auth)
|
||||
rauth.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()),
|
||||
rauth.Use(logger.New(logger.Log(log.Default()), logger.WithBody,
|
||||
logger.Prefix("[INFO]"), logger.IPfn(ipFn)).Handler)
|
||||
rauth.Get("/user", s.userInfoCtrl)
|
||||
rauth.Get("/userdata", s.userAllDataCtrl)
|
||||
@@ -245,7 +245,7 @@ func (s *Rest) routes() chi.Router {
|
||||
}
|
||||
rauth.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(lmt, nil)))
|
||||
rauth.Use(authMiddleware.Auth)
|
||||
rauth.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()),
|
||||
rauth.Use(logger.New(logger.Log(log.Default()), logger.WithBody,
|
||||
logger.Prefix("[DEBUG]"), logger.IPfn(ipFn)).Handler)
|
||||
|
||||
rauth.Put("/comment/{id}", s.updateCommentCtrl)
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- export TZ=America/Chicago
|
||||
- curl -L https://git.io/vp6lP | sh
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.13.2
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$(pwd)/bin:$PATH
|
||||
|
||||
@@ -15,5 +15,5 @@ script:
|
||||
- GO111MODULE=on go get ./...
|
||||
- GO111MODULE=on go mod vendor
|
||||
- GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- ./bin/gometalinter --deadline=120s --exclude=test --exclude=mock --exclude=vendor --exclude=_example --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=megacheck --enable=ineffassign --enable=varcheck --enable=unconvert --enable=deadcode --enable=interfacer --enable=gotype ./... || travis_terminate 1;
|
||||
- golangci-lint run || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
+10
-10
@@ -2,20 +2,20 @@
|
||||
|
||||
## install
|
||||
|
||||
`go get github/go-pkgz/lgr`
|
||||
`go get github.com/go-pkgz/lgr`
|
||||
|
||||
## usage
|
||||
|
||||
```go
|
||||
l := lgr.New(lgr.Debug, lgr.CallerFile) // allow debug and caller file info
|
||||
l.Logf("INFO some important err message, %v", err)
|
||||
l.Logf("DEBUG some less important err message, %v", err)
|
||||
l.Logf("INFO some important message, %v", err)
|
||||
l.Logf("DEBUG some less important message, %v", err)
|
||||
```
|
||||
|
||||
output looks like this:
|
||||
```
|
||||
2018/01/07 13:02:34.000 INFO {svc/handler.go:101 h.MyFunc1} some important err message, can't open file`
|
||||
2018/01/07 13:02:34.015 DEBUG {svc/handler.go:155 h.MyFunc2} some less important err message, file is too small`
|
||||
2018/01/07 13:02:34.000 INFO {svc/handler.go:101 h.MyFunc1} some important message, can't open file`
|
||||
2018/01/07 13:02:34.015 DEBUG {svc/handler.go:155 h.MyFunc2} some less important message, file is too small`
|
||||
```
|
||||
|
||||
_Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
@@ -32,7 +32,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
|
||||
`lgr.New` call accepts functional options:
|
||||
|
||||
- `lgr.Debug` - turn debug mode on. This allows messages with "DEBUG" level (filtered overwise)
|
||||
- `lgr.Debug` - turn debug mode on to allow messages with "DEBUG" level (filtered overwise)
|
||||
- `lgr.CallerFile` - adds the caller file info
|
||||
- `lgr.CallerFunc` - adds the caller function info
|
||||
- `lgr.CallerPkg` - adds the caller package
|
||||
@@ -48,11 +48,11 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
- `DEBUG` will be filtered unless `lgr.Debug` option defined
|
||||
- `INFO` and `WARN` don't have any special behavior attached
|
||||
- `ERROR` sends messages to both out and err writers
|
||||
- `PANIC` and `FATAL` send messages to both out and err writers. In addition sends dump of callers and runtime info to err only, and call `os.Exit(1)`.
|
||||
- `PANIC` and `FATAL` send messages to both out and err writers. In addition sends dump of callers and runtime info to err only, and calls `os.Exit(1)`.
|
||||
|
||||
### global logger
|
||||
|
||||
Users should avoid global logger and pass the concrete logger as a dependency. However, in some cases global logger may be needed, for example migration from stdlib `log` to `lgr`. For such cases `log "github.com/go-pkgz/lgr"` can be imported instead of `log` package.
|
||||
Users **should avoid** global logger and pass the concrete logger as a dependency. However, in some cases a global logger may be needed, for example migration from stdlib `log` to `lgr`. For such cases `log "github.com/go-pkgz/lgr"` can be imported instead of `log` package.
|
||||
|
||||
Global logger provides `lgr.Printf`, `lgr.Print` and `lgr.Fatalf` functions. User can customize the logger by calling `lgr.Setup(options ...)`. The instance of this logger can be retried with `lgr.Default()`
|
||||
|
||||
Global logger provides `lgr.Printf`, `lgr.Print` and `lgr.Fatalf` functions. User can customize the logger by calling `lgr.Setup(options ...)`. The instance of this logger can be retrieved with `lgr.Default()`
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ func Fatalf(format string, args ...interface{}) {
|
||||
// Setup default logger with options
|
||||
func Setup(opts ...Option) {
|
||||
def = New(opts...)
|
||||
def.skipCallers = 2
|
||||
def.callerSkip = 2
|
||||
}
|
||||
|
||||
// Default returns pre-constructed def logger (debug off, callers disabled)
|
||||
|
||||
+20
-11
@@ -21,12 +21,13 @@ type Logger struct {
|
||||
callerFile bool
|
||||
callerFunc bool
|
||||
callerPkg bool
|
||||
callerSkip int
|
||||
ignoredPkgCallers []string
|
||||
now nowFn
|
||||
fatal panicFn
|
||||
skipCallers int
|
||||
levelBraces bool
|
||||
msec bool
|
||||
|
||||
now nowFn
|
||||
fatal panicFn
|
||||
levelBraces bool
|
||||
msec bool
|
||||
}
|
||||
|
||||
type nowFn func() time.Time
|
||||
@@ -36,11 +37,11 @@ type panicFn func()
|
||||
// Two writers can be passed optionally - first for out and second for err
|
||||
func New(options ...Option) *Logger {
|
||||
res := Logger{
|
||||
now: time.Now,
|
||||
fatal: func() { os.Exit(1) },
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
skipCallers: 1,
|
||||
now: time.Now,
|
||||
fatal: func() { os.Exit(1) },
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
callerSkip: 1,
|
||||
}
|
||||
for _, opt := range options {
|
||||
opt(&res)
|
||||
@@ -72,7 +73,7 @@ func (l *Logger) Logf(format string, args ...interface{}) {
|
||||
bld.WriteString(" ")
|
||||
|
||||
if l.callerFile || l.callerFunc || l.callerPkg {
|
||||
if pc, file, line, ok := runtime.Caller(l.skipCallers); ok {
|
||||
if pc, file, line, ok := runtime.Caller(l.callerSkip); ok {
|
||||
|
||||
funcName, fileInfo := "", ""
|
||||
|
||||
@@ -217,6 +218,14 @@ func CallerIgnore(ignores ...string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// CallerSkip sets how many trace levels to skip.
|
||||
// by default this value is 1 , i.e. skip logger level only
|
||||
func CallerSkip(n int) Option {
|
||||
return func(l *Logger) {
|
||||
l.callerSkip = n
|
||||
}
|
||||
}
|
||||
|
||||
// LevelBraces adds [] to level
|
||||
func LevelBraces(l *Logger) {
|
||||
l.levelBraces = true
|
||||
|
||||
+10
@@ -59,6 +59,16 @@ Metrics middleware responds to GET /metrics with list of [expvar](https://golang
|
||||
|
||||
BlackWords middleware doesn't allow user-defined words in the request body.
|
||||
|
||||
### SizeLimit middleware
|
||||
|
||||
SizeLimit middleware checks if body size is above the limit and returns `StatusRequestEntityTooLarge` (413)
|
||||
|
||||
### Trace middleware
|
||||
|
||||
It looks for `X-Request-ID` header and makes it as a random id
|
||||
(if not found), then populates it to the result's header
|
||||
and to the request's context.
|
||||
|
||||
## Helpers
|
||||
|
||||
- `rest.JSON` - map alias, just for convenience `type JSON map[string]interface{}`
|
||||
|
||||
+1
-1
@@ -39,5 +39,5 @@ In addition to `Get` and `Flush` methods, memory cache also support limits for a
|
||||
return []byte("1234567890"), nil
|
||||
})
|
||||
|
||||
lc.Flush("scope1") // invalidate cache for scope1
|
||||
lc.Flush(cache.Flusher("site1").Scopes("scope1")) // invalidate cache for scope1
|
||||
```
|
||||
|
||||
+12
-6
@@ -1,3 +1,9 @@
|
||||
// Package cache implements a wrapper on top of hashicorp/golang-lru with guava-style loader func.
|
||||
// In addition for Get (i.e. load from cache if found and put to cache if absent) it adds a Key struct
|
||||
// with record ID, site ID and invalidation scopes. Scopes used to evict matching records.
|
||||
// Additional limits added for total cache size, mac number of keys and max size of the value. If exceeded it won't
|
||||
// put to cache but will call loader func.
|
||||
// Usually the cache involved on []byte response level, i.e. post-marshaling right before response's send.
|
||||
package cache
|
||||
|
||||
import (
|
||||
@@ -8,16 +14,16 @@ import (
|
||||
|
||||
// LoadingCache defines interface for caching
|
||||
type LoadingCache interface {
|
||||
Get(key Key, fn func() ([]byte, error)) (data []byte, err error)
|
||||
Flush(req FlusherRequest)
|
||||
Get(key Key, fn func() ([]byte, error)) (data []byte, err error) // load from cache if found or put to cache and return
|
||||
Flush(req FlusherRequest) // evict matched records
|
||||
}
|
||||
|
||||
type cacheWithOpts interface {
|
||||
LoadingCache
|
||||
setMaxValSize(max int) error
|
||||
setMaxKeys(max int) error
|
||||
setMaxCacheSize(max int64) error
|
||||
setPostFlushFn(postFlushFn func()) error
|
||||
setMaxValSize(max int) error // max value size, in bytes
|
||||
setMaxKeys(max int) error // max number of keys
|
||||
setMaxCacheSize(max int64) error // max cache size (total values) in bytes
|
||||
setPostFlushFn(postFlushFn func()) error // optional callback after flush
|
||||
}
|
||||
|
||||
// Key for cache
|
||||
|
||||
+108
-92
@@ -4,41 +4,28 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var reMultWhtsp = regexp.MustCompile(`[\s\p{Zs}]{2,}`)
|
||||
|
||||
// Middleware for logging rest requests
|
||||
// Middleware is a logger for rest requests.
|
||||
type Middleware struct {
|
||||
prefix string
|
||||
logBody bool
|
||||
maxBodySize int
|
||||
flags []Flag
|
||||
ipFn func(ip string) string
|
||||
userFn func(r *http.Request) (string, error)
|
||||
subjFn func(r *http.Request) (string, error)
|
||||
log Backend
|
||||
}
|
||||
|
||||
// Flag type
|
||||
type Flag int
|
||||
|
||||
// logger flags enum
|
||||
const (
|
||||
All Flag = iota
|
||||
User
|
||||
Body
|
||||
None
|
||||
)
|
||||
|
||||
// Backend is logging backend
|
||||
type Backend interface {
|
||||
Logf(format string, args ...interface{})
|
||||
@@ -50,19 +37,18 @@ func (s stdBackend) Logf(format string, args ...interface{}) {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Logger returns default logger middleware with REST prefix
|
||||
// Logger is a default logger middleware with "REST" prefix
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
l := New(Prefix("REST"))
|
||||
return l.Handler(next)
|
||||
|
||||
}
|
||||
|
||||
// New makes rest Logger with given options
|
||||
// New makes rest logger with given options
|
||||
func New(options ...Option) *Middleware {
|
||||
res := Middleware{
|
||||
prefix: "",
|
||||
maxBodySize: 1024,
|
||||
flags: []Flag{All},
|
||||
log: stdBackend{},
|
||||
}
|
||||
for _, opt := range options {
|
||||
@@ -75,28 +61,28 @@ func New(options ...Option) *Middleware {
|
||||
func (l *Middleware) Handler(next http.Handler) http.Handler {
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ww := newCustomResponseWriter(w)
|
||||
|
||||
if l.inLogFlags(None) { // skip logging
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
user := ""
|
||||
if l.userFn != nil {
|
||||
if u, err := l.userFn(r); err == nil {
|
||||
user = u
|
||||
}
|
||||
}
|
||||
|
||||
ww := newCustomResponseWriter(w)
|
||||
body, user := l.getBodyAndUser(r)
|
||||
body := l.getBody(r)
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
t2 := time.Now()
|
||||
|
||||
q := l.sanitizeQuery(r.URL.String())
|
||||
if qun, err := url.QueryUnescape(q); err == nil {
|
||||
q = qun
|
||||
}
|
||||
|
||||
remoteIP := strings.Split(r.RemoteAddr, ":")[0]
|
||||
if strings.HasPrefix(r.RemoteAddr, "[") {
|
||||
remoteIP = strings.Split(r.RemoteAddr, "]:")[0] + "]"
|
||||
u := *r.URL // shallow copy
|
||||
u.RawQuery = l.sanitizeQuery(u.RawQuery)
|
||||
rawurl := u.String()
|
||||
if unescURL, err := url.QueryUnescape(rawurl); err == nil {
|
||||
rawurl = unescURL
|
||||
}
|
||||
|
||||
remoteIP := l.remoteIP(r)
|
||||
if l.ipFn != nil { // mask ip with ipFn
|
||||
remoteIP = l.ipFn(remoteIP)
|
||||
}
|
||||
@@ -107,7 +93,7 @@ func (l *Middleware) Handler(next http.Handler) http.Handler {
|
||||
bld.WriteString(" ")
|
||||
}
|
||||
|
||||
bld.WriteString(fmt.Sprintf("%s - %s - %s - %d (%d) - %v", r.Method, q, remoteIP, ww.status, ww.size, t2.Sub(t1)))
|
||||
bld.WriteString(fmt.Sprintf("%s - %s - %s - %d (%d) - %v", r.Method, rawurl, remoteIP, ww.status, ww.size, t2.Sub(t1)))
|
||||
|
||||
if user != "" {
|
||||
bld.WriteString(" - ")
|
||||
@@ -139,84 +125,114 @@ func (l *Middleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func (l *Middleware) getBodyAndUser(r *http.Request) (body string, user string) {
|
||||
ctx := r.Context()
|
||||
if ctx == nil {
|
||||
return "", ""
|
||||
var reMultWhtsp = regexp.MustCompile(`[\s\p{Zs}]{2,}`)
|
||||
|
||||
func (l *Middleware) getBody(r *http.Request) string {
|
||||
if !l.logBody {
|
||||
return ""
|
||||
}
|
||||
|
||||
if l.inLogFlags(Body) {
|
||||
if content, err := ioutil.ReadAll(r.Body); err == nil {
|
||||
body = string(content)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(content))
|
||||
|
||||
if len(body) > 0 {
|
||||
body = strings.Replace(body, "\n", " ", -1)
|
||||
body = reMultWhtsp.ReplaceAllString(body, " ")
|
||||
}
|
||||
|
||||
if len(body) > l.maxBodySize {
|
||||
body = body[:l.maxBodySize] + "..."
|
||||
}
|
||||
}
|
||||
reader, body, hasMore, err := peek(r.Body, int64(l.maxBodySize))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if l.inLogFlags(User) && l.userFn != nil {
|
||||
u, err := l.userFn(r)
|
||||
if err == nil && u != "" {
|
||||
user = u
|
||||
}
|
||||
// "The Server will close the request body. The ServeHTTP Handler does not need to."
|
||||
// https://golang.org/pkg/net/http/#Request
|
||||
// So we can use ioutil.NopCloser() to make io.ReadCloser.
|
||||
// Note that below assignment is not approved by the docs:
|
||||
// "Except for reading the body, handlers should not modify the provided Request."
|
||||
// https://golang.org/pkg/net/http/#Handler
|
||||
r.Body = ioutil.NopCloser(reader)
|
||||
|
||||
if len(body) > 0 {
|
||||
body = strings.Replace(body, "\n", " ", -1)
|
||||
body = reMultWhtsp.ReplaceAllString(body, " ")
|
||||
}
|
||||
|
||||
return body, user
|
||||
if hasMore {
|
||||
body += "..."
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func (l *Middleware) inLogFlags(f Flag) bool {
|
||||
for _, flg := range l.flags {
|
||||
if (flg == All && f != None) || flg == f {
|
||||
return true
|
||||
}
|
||||
// peek the first n bytes as string
|
||||
func peek(r io.Reader, n int64) (reader io.Reader, s string, hasMore bool, err error) {
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
return false
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.CopyN(buf, r, n+1)
|
||||
if err == io.EOF {
|
||||
return buf, buf.String(), false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return r, "", false, err
|
||||
}
|
||||
|
||||
// one extra byte is successfully read
|
||||
s = buf.String()
|
||||
s = s[:len(s)-1]
|
||||
|
||||
return io.MultiReader(buf, r), s, true, nil
|
||||
}
|
||||
|
||||
var hideWords = []string{"password", "passwd", "secret", "credentials", "token"}
|
||||
var keysToHide = []string{"password", "passwd", "secret", "credentials", "token"}
|
||||
|
||||
// hide query values for hideWords. May change order of query params
|
||||
func (l *Middleware) sanitizeQuery(inp string) string {
|
||||
// Hide query values for keysToHide. May change order of query params.
|
||||
// May escape unescaped query params.
|
||||
func (l *Middleware) sanitizeQuery(rawQuery string) string {
|
||||
// note that we skip non-nil error further
|
||||
query, err := url.ParseQuery(rawQuery)
|
||||
|
||||
inHiddenWords := func(str string) bool {
|
||||
for _, w := range hideWords {
|
||||
if strings.EqualFold(w, str) {
|
||||
isHidden := func(key string) bool {
|
||||
for _, k := range keysToHide {
|
||||
if strings.EqualFold(k, key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(inp, "?", 2)
|
||||
if len(parts) < 2 {
|
||||
return inp
|
||||
}
|
||||
|
||||
q, e := url.ParseQuery(parts[1])
|
||||
if e != nil || len(q) == 0 {
|
||||
return inp
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
for k, v := range q {
|
||||
if inHiddenWords(k) {
|
||||
res = append(res, fmt.Sprintf("%s=********", k))
|
||||
} else {
|
||||
res = append(res, fmt.Sprintf("%s=%v", k, v[0]))
|
||||
present := false
|
||||
for key, values := range query {
|
||||
if isHidden(key) {
|
||||
present = true
|
||||
for i := range values {
|
||||
values[i] = "********"
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(res) // to make testing persistent
|
||||
return parts[0] + "?" + strings.Join(res, "&")
|
||||
|
||||
// short circuit
|
||||
if (err == nil) && !present {
|
||||
return rawQuery
|
||||
}
|
||||
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
// customResponseWriter implements ResponseWriter and keeping status and size
|
||||
// remoteIP gets address from X-Forwarded-For and than from request's remote address
|
||||
func (l *Middleware) remoteIP(r *http.Request) (remoteIP string) {
|
||||
|
||||
if remoteIP = r.Header.Get("X-Forwarded-For"); remoteIP == "" {
|
||||
remoteIP = r.RemoteAddr
|
||||
}
|
||||
remoteIP = strings.Split(remoteIP, ":")[0]
|
||||
if strings.HasPrefix(remoteIP, "[") {
|
||||
remoteIP = strings.Split(remoteIP, "]:")[0] + "]"
|
||||
}
|
||||
return remoteIP
|
||||
}
|
||||
|
||||
// customResponseWriter is an HTTP response logger that keeps HTTP status code and
|
||||
// the number of bytes written.
|
||||
// It implements http.ResponseWriter, http.Flusher and http.Hijacker.
|
||||
// Note that type assertion from http.ResponseWriter(customResponseWriter) to
|
||||
// http.Flusher and http.Hijacker is always succeed but underlying http.ResponseWriter
|
||||
// may not implement them.
|
||||
type customResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
@@ -230,27 +246,27 @@ func newCustomResponseWriter(w http.ResponseWriter) *customResponseWriter {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements ResponseWriter and saves status
|
||||
// WriteHeader implements http.ResponseWriter and saves status
|
||||
func (c *customResponseWriter) WriteHeader(status int) {
|
||||
c.status = status
|
||||
c.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
// WriteHeader implements ResponseWriter and tracking size
|
||||
// Write implements http.ResponseWriter and tracks number of bytes written
|
||||
func (c *customResponseWriter) Write(b []byte) (int, error) {
|
||||
size, err := c.ResponseWriter.Write(b)
|
||||
c.size += size
|
||||
return size, err
|
||||
}
|
||||
|
||||
// Flush implements ResponseWriter
|
||||
// Flush implements http.Flusher
|
||||
func (c *customResponseWriter) Flush() {
|
||||
if f, ok := c.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack implements ResponseWriter
|
||||
// Hijack implements http.Hijacker
|
||||
func (c *customResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := c.ResponseWriter.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
|
||||
+11
-13
@@ -7,14 +7,12 @@ import (
|
||||
// Option func type
|
||||
type Option func(l *Middleware)
|
||||
|
||||
// Flags functional option defines output modes
|
||||
func Flags(flags ...Flag) Option {
|
||||
return func(l *Middleware) {
|
||||
l.flags = flags
|
||||
}
|
||||
// WithBody triggers request body logging. Body size is limited (default 1k)
|
||||
func WithBody(l *Middleware) {
|
||||
l.logBody = true
|
||||
}
|
||||
|
||||
// MaxBodySize functional option defines the largest body size to log.
|
||||
// MaxBodySize sets size of the logged part of the request body.
|
||||
func MaxBodySize(max int) Option {
|
||||
return func(l *Middleware) {
|
||||
if max >= 0 {
|
||||
@@ -23,35 +21,35 @@ func MaxBodySize(max int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix functional option defines log line prefix.
|
||||
// Prefix sets log line prefix.
|
||||
func Prefix(prefix string) Option {
|
||||
return func(l *Middleware) {
|
||||
l.prefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
// IPfn functional option defines ip masking function.
|
||||
// IPfn sets IP masking function. If ipFn is nil then IP address will be logged as is.
|
||||
func IPfn(ipFn func(ip string) string) Option {
|
||||
return func(l *Middleware) {
|
||||
l.ipFn = ipFn
|
||||
}
|
||||
}
|
||||
|
||||
// UserFn functional option defines user name function.
|
||||
// UserFn triggers user name logging if userFn is not nil.
|
||||
func UserFn(userFn func(r *http.Request) (string, error)) Option {
|
||||
return func(l *Middleware) {
|
||||
l.userFn = userFn
|
||||
}
|
||||
}
|
||||
|
||||
// SubjFn functional option defines subject function.
|
||||
func SubjFn(userFn func(r *http.Request) (string, error)) Option {
|
||||
// SubjFn triggers subject logging if subjFn is not nil.
|
||||
func SubjFn(subjFn func(r *http.Request) (string, error)) Option {
|
||||
return func(l *Middleware) {
|
||||
l.subjFn = userFn
|
||||
l.subjFn = subjFn
|
||||
}
|
||||
}
|
||||
|
||||
// Log functional option defines loging backend.
|
||||
// Log sets logging backend.
|
||||
func Log(log Backend) Option {
|
||||
return func(l *Middleware) {
|
||||
l.log = log
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SizeLimit middleware checks if body size is above the limit and returns StatusRequestEntityTooLarge (413)
|
||||
func SizeLimit(size int64) func(http.Handler) http.Handler {
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// check ContentLength
|
||||
if r.ContentLength > size {
|
||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
// check size of the actual body
|
||||
content, err := ioutil.ReadAll(io.LimitReader(r.Body, size+1))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if int64(len(content)) > size {
|
||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(content))
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
+4
-10
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -38,17 +39,10 @@ func GetTraceID(r *http.Request) string {
|
||||
}
|
||||
|
||||
func randToken() string {
|
||||
fallback := func() string {
|
||||
return fmt.Sprintf("%x", time.Now().Nanosecond())
|
||||
}
|
||||
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fallback()
|
||||
return fmt.Sprintf("%x", time.Now().Nanosecond())
|
||||
}
|
||||
s := sha1.New()
|
||||
if _, err := s.Write(b); err != nil {
|
||||
return fallback()
|
||||
}
|
||||
return fmt.Sprintf("%x", s.Sum(nil))
|
||||
sum := sha1.Sum(b)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user