revendor with latest lgr (logging)

This commit is contained in:
Umputun
2019-03-05 22:31:37 -06:00
parent 084a7a9f99
commit 2105b5ddfe
14 changed files with 233 additions and 161 deletions
+6 -6
View File
@@ -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
View File
@@ -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
+6 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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[:])
}