switch to chi v4
This commit is contained in:
@@ -262,7 +262,7 @@ func (s *Rest) routes() chi.Router {
|
||||
rauth.Put("/comment/{id}", s.updateCommentCtrl)
|
||||
rauth.Post("/comment", s.createCommentCtrl)
|
||||
rauth.With(rejectAnonUser).Put("/vote/{id}", s.voteCtrl)
|
||||
rauth.Post("/deleteme", s.deleteMeCtrl)
|
||||
rauth.With(rejectAnonUser).Post("/deleteme", s.deleteMeCtrl)
|
||||
})
|
||||
|
||||
rapi.Group(func(rauth chi.Router) {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ require (
|
||||
github.com/didip/tollbooth v4.0.0+incompatible
|
||||
github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||
github.com/go-chi/chi v3.3.2+incompatible
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/cors v1.0.0
|
||||
github.com/go-chi/render v1.0.0
|
||||
github.com/go-pkgz/auth v0.5.0
|
||||
|
||||
@@ -22,6 +22,8 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
|
||||
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.0 h1:cLJlkaTB4xfx5rWhtoB0BSXsXVJKWFqv08Y3cR1bZKA=
|
||||
|
||||
+9
-9
@@ -1,18 +1,18 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
|
||||
install:
|
||||
- go get -u golang.org/x/tools/cmd/goimports
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
script:
|
||||
- go get -d -t ./...
|
||||
- go vet ./...
|
||||
- golint ./...
|
||||
- go test ./...
|
||||
- >
|
||||
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :
|
||||
go_version=$(go version);
|
||||
if [ ${go_version:13:4} = "1.12" ]; then
|
||||
go get -u golang.org/x/tools/cmd/goimports;
|
||||
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
|
||||
fi
|
||||
|
||||
|
||||
+23
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## v4.0.0 (2019-01-10)
|
||||
|
||||
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8
|
||||
- router: respond with 404 on router with no routes (#362)
|
||||
- router: additional check to ensure wildcard is at the end of a url pattern (#333)
|
||||
- middleware: deprecate use of http.CloseNotifier (#347)
|
||||
- middleware: fix RedirectSlashes to include query params on redirect (#334)
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0
|
||||
|
||||
|
||||
## v3.3.4 (2019-01-07)
|
||||
|
||||
- Minor middleware improvements. No changes to core library/router. Moving v3 into its
|
||||
- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4
|
||||
|
||||
|
||||
## v3.3.3 (2018-08-27)
|
||||
|
||||
- Minor release
|
||||
- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3
|
||||
|
||||
|
||||
## v3.3.2 (2017-12-22)
|
||||
|
||||
- Support to route trailing slashes on mounted sub-routers (#281)
|
||||
|
||||
+27
-32
@@ -3,7 +3,7 @@
|
||||
|
||||
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
|
||||
|
||||
`chi` is a lightweight, idiomatic and composable router for building Go 1.7+ HTTP services. It's
|
||||
`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's
|
||||
especially good at helping you write large REST API services that are kept maintainable as your
|
||||
project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to
|
||||
handle signaling, cancelation and request-scoped values across a handler chain.
|
||||
@@ -31,18 +31,12 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h
|
||||
* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts
|
||||
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
|
||||
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
|
||||
* **No external dependencies** - plain ol' Go 1.7+ stdlib + net/http
|
||||
* **No external dependencies** - plain ol' Go stdlib + net/http
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
* [rest](https://github.com/go-chi/chi/blob/master/_examples/rest/main.go) - REST APIs made easy, productive and maintainable
|
||||
* [logging](https://github.com/go-chi/chi/blob/master/_examples/logging/main.go) - Easy structured logging for any backend
|
||||
* [limits](https://github.com/go-chi/chi/blob/master/_examples/limits/main.go) - Timeouts and Throttling
|
||||
* [todos-resource](https://github.com/go-chi/chi/blob/master/_examples/todos-resource/main.go) - Struct routers/handlers, an example of another code layout style
|
||||
* [versions](https://github.com/go-chi/chi/blob/master/_examples/versions/main.go) - Demo of `chi/render` subpkg
|
||||
* [fileserver](https://github.com/go-chi/chi/blob/master/_examples/fileserver/main.go) - Easily serve static files
|
||||
* [graceful](https://github.com/go-chi/chi/blob/master/_examples/graceful/main.go) - Graceful context signaling and server shutdown
|
||||
See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples.
|
||||
|
||||
|
||||
**As easy as:**
|
||||
@@ -70,8 +64,8 @@ Here is a little preview of how routing looks like with chi. Also take a look at
|
||||
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
|
||||
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
|
||||
|
||||
I highly recommend reading the source of the [examples](#examples) listed above, they will show you all the features
|
||||
of chi and serve as a good form of documentation.
|
||||
I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed
|
||||
above, they will show you all the features of chi and serve as a good form of documentation.
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -232,7 +226,7 @@ type Router interface {
|
||||
}
|
||||
|
||||
// Routes interface adds two methods for router traversal, which is also
|
||||
// used by the `docgen` subpackage to generation documentation for Routers.
|
||||
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
|
||||
type Routes interface {
|
||||
// Routes returns the routing tree in an easily traversable structure.
|
||||
Routes() []Route
|
||||
@@ -261,7 +255,7 @@ friendly with any middleware in the community. This offers much better extensibi
|
||||
of packages and is at the heart of chi's purpose.
|
||||
|
||||
Here is an example of a standard net/http middleware handler using the new request context
|
||||
available in Go 1.7+. This middleware sets a hypothetical user identifier on the request
|
||||
available in Go. This middleware sets a hypothetical user identifier on the request
|
||||
context and calls the next handler in the chain.
|
||||
|
||||
```go
|
||||
@@ -347,6 +341,7 @@ Please see https://github.com/go-chi for additional packages.
|
||||
| package | description |
|
||||
|:---------------------------------------------------|:-------------------------------------------------------------
|
||||
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
|
||||
| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime |
|
||||
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
|
||||
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
|
||||
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
|
||||
@@ -374,33 +369,33 @@ and..
|
||||
|
||||
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
|
||||
|
||||
Results as of Aug 31, 2017 on Go 1.9.0
|
||||
Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
|
||||
|
||||
```shell
|
||||
BenchmarkChi_Param 3000000 607 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param5 2000000 935 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param20 1000000 1944 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParamWrite 2000000 664 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubStatic 2000000 627 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubParam 2000000 847 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubAll 10000 175556 ns/op 87700 B/op 609 allocs/op
|
||||
BenchmarkChi_GPlusStatic 3000000 566 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusParam 2000000 652 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlus2Params 2000000 767 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusAll 200000 9794 ns/op 5616 B/op 39 allocs/op
|
||||
BenchmarkChi_ParseStatic 3000000 590 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseParam 2000000 656 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Parse2Params 2000000 715 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseAll 100000 18045 ns/op 11232 B/op 78 allocs/op
|
||||
BenchmarkChi_StaticAll 10000 108871 ns/op 67827 B/op 471 allocs/op
|
||||
BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op
|
||||
BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op
|
||||
BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op
|
||||
BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op
|
||||
```
|
||||
|
||||
Comparison with other routers: https://gist.github.com/pkieltyka/c089f309abeb179cfc4deaa519956d8c
|
||||
Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
|
||||
|
||||
NOTE: the allocs in the benchmark above are from the calls to http.Request's
|
||||
`WithContext(context.Context)` method that clones the http.Request, sets the `Context()`
|
||||
on the duplicated (alloc'd) request and returns it the new request object. This is just
|
||||
how setting context on a request in Go 1.7+ works.
|
||||
how setting context on a request in Go works.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
+7
-7
@@ -84,13 +84,13 @@ func (x *Context) URLParam(key string) string {
|
||||
//
|
||||
// For example,
|
||||
//
|
||||
// func Instrument(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// next.ServeHTTP(w, r)
|
||||
// routePattern := chi.RouteContext(r.Context()).RoutePattern()
|
||||
// measure(w, r, routePattern)
|
||||
// })
|
||||
// }
|
||||
// func Instrument(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// next.ServeHTTP(w, r)
|
||||
// routePattern := chi.RouteContext(r.Context()).RoutePattern()
|
||||
// measure(w, r, routePattern)
|
||||
// })
|
||||
// }
|
||||
func (x *Context) RoutePattern() string {
|
||||
routePattern := strings.Join(x.RoutePatterns, "")
|
||||
return strings.Replace(routePattern, "/*/", "/", -1)
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
// +build go1.7,!go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CloseNotify is a middleware that cancels ctx when the underlying
|
||||
// connection has gone away. It can be used to cancel long operations
|
||||
// on the server when the client disconnects before the response is ready.
|
||||
//
|
||||
// Note: this behaviour is standard in Go 1.8+, so the middleware does nothing
|
||||
// on 1.8+ and exists just for backwards compatibility.
|
||||
func CloseNotify(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
cn, ok := w.(http.CloseNotifier)
|
||||
if !ok {
|
||||
panic("chi/middleware: CloseNotify expects http.ResponseWriter to implement http.CloseNotifier interface")
|
||||
}
|
||||
closeNotifyCh := cn.CloseNotify()
|
||||
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeNotifyCh:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CloseNotify is a middleware that cancels ctx when the underlying
|
||||
// connection has gone away. It can be used to cancel long operations
|
||||
// on the server when the client disconnects before the response is ready.
|
||||
//
|
||||
// Note: this behaviour is standard in Go 1.8+, so the middleware does nothing
|
||||
// on 1.8+ and exists just for backwards compatibility.
|
||||
func CloseNotify(next http.Handler) http.Handler {
|
||||
return next
|
||||
}
|
||||
+167
-104
@@ -11,24 +11,98 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type encoding int
|
||||
var encoders = map[string]EncoderFunc{}
|
||||
|
||||
const (
|
||||
encodingNone encoding = iota
|
||||
encodingGzip
|
||||
encodingDeflate
|
||||
)
|
||||
var encodingPrecedence = []string{"br", "gzip", "deflate"}
|
||||
|
||||
func init() {
|
||||
// TODO:
|
||||
// lzma: Opera.
|
||||
// sdch: Chrome, Android. Gzip output + dictionary header.
|
||||
// br: Brotli, see https://github.com/go-chi/chi/pull/326
|
||||
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
SetEncoder("gzip", encoderGzip)
|
||||
|
||||
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
|
||||
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
|
||||
// checksum compared to CRC-32 used in "gzip" and thus is faster.
|
||||
//
|
||||
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
|
||||
// raw DEFLATE data only, without the mentioned zlib wrapper.
|
||||
// Because of this major confusion, most modern browsers try it
|
||||
// both ways, first looking for zlib headers.
|
||||
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
|
||||
//
|
||||
// The list of browsers having problems is quite big, see:
|
||||
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
|
||||
//
|
||||
// That's why we prefer gzip over deflate. It's just more reliable
|
||||
// and not significantly slower than gzip.
|
||||
SetEncoder("deflate", encoderDeflate)
|
||||
|
||||
// NOTE: Not implemented, intentionally:
|
||||
// case "compress": // LZW. Deprecated.
|
||||
// case "bzip2": // Too slow on-the-fly.
|
||||
// case "zopfli": // Too slow on-the-fly.
|
||||
// case "xz": // Too slow on-the-fly.
|
||||
}
|
||||
|
||||
// An EncoderFunc is a function that wraps the provided ResponseWriter with a
|
||||
// streaming compression algorithm and returns it.
|
||||
//
|
||||
// In case of failure, the function should return nil.
|
||||
type EncoderFunc func(w http.ResponseWriter, level int) io.Writer
|
||||
|
||||
// SetEncoder can be used to set the implementation of a compression algorithm.
|
||||
//
|
||||
// The encoding should be a standardised identifier. See:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
|
||||
//
|
||||
// For example, add the Brotli algortithm:
|
||||
//
|
||||
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
|
||||
//
|
||||
// middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
|
||||
// params := brotli_enc.NewBrotliParams()
|
||||
// params.SetQuality(level)
|
||||
// return brotli_enc.NewBrotliWriter(params, w)
|
||||
// })
|
||||
func SetEncoder(encoding string, fn EncoderFunc) {
|
||||
encoding = strings.ToLower(encoding)
|
||||
if encoding == "" {
|
||||
panic("the encoding can not be empty")
|
||||
}
|
||||
if fn == nil {
|
||||
panic("attempted to set a nil encoder function")
|
||||
}
|
||||
encoders[encoding] = fn
|
||||
|
||||
var e string
|
||||
for _, v := range encodingPrecedence {
|
||||
if v == encoding {
|
||||
e = v
|
||||
}
|
||||
}
|
||||
|
||||
if e == "" {
|
||||
encodingPrecedence = append([]string{e}, encodingPrecedence...)
|
||||
}
|
||||
}
|
||||
|
||||
var defaultContentTypes = map[string]struct{}{
|
||||
"text/html": struct{}{},
|
||||
"text/css": struct{}{},
|
||||
"text/plain": struct{}{},
|
||||
"text/javascript": struct{}{},
|
||||
"application/javascript": struct{}{},
|
||||
"application/x-javascript": struct{}{},
|
||||
"application/json": struct{}{},
|
||||
"application/atom+xml": struct{}{},
|
||||
"application/rss+xml": struct{}{},
|
||||
"text/html": {},
|
||||
"text/css": {},
|
||||
"text/plain": {},
|
||||
"text/javascript": {},
|
||||
"application/javascript": {},
|
||||
"application/x-javascript": {},
|
||||
"application/json": {},
|
||||
"application/atom+xml": {},
|
||||
"application/rss+xml": {},
|
||||
"image/svg+xml": {},
|
||||
}
|
||||
|
||||
// DefaultCompress is a middleware that compresses response
|
||||
@@ -43,6 +117,11 @@ func DefaultCompress(next http.Handler) http.Handler {
|
||||
// body of a given content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a given
|
||||
// compression level.
|
||||
//
|
||||
// NOTE: make sure to set the Content-Type header on your response
|
||||
// otherwise this middleware will not compress the response body. For ex, in
|
||||
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
|
||||
// or set it manually.
|
||||
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
contentTypes := defaultContentTypes
|
||||
if len(types) > 0 {
|
||||
@@ -54,159 +133,143 @@ func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
mcw := &maybeCompressResponseWriter{
|
||||
encoder, encoding := selectEncoder(r.Header)
|
||||
|
||||
cw := &compressResponseWriter{
|
||||
ResponseWriter: w,
|
||||
w: w,
|
||||
contentTypes: contentTypes,
|
||||
encoding: selectEncoding(r.Header),
|
||||
encoder: encoder,
|
||||
encoding: encoding,
|
||||
level: level,
|
||||
}
|
||||
defer mcw.Close()
|
||||
defer cw.Close()
|
||||
|
||||
next.ServeHTTP(mcw, r)
|
||||
next.ServeHTTP(cw, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func selectEncoding(h http.Header) encoding {
|
||||
enc := h.Get("Accept-Encoding")
|
||||
func selectEncoder(h http.Header) (EncoderFunc, string) {
|
||||
header := h.Get("Accept-Encoding")
|
||||
|
||||
switch {
|
||||
// TODO:
|
||||
// case "br": // Brotli, experimental. Firefox 2016, to-be-in Chromium.
|
||||
// case "lzma": // Opera.
|
||||
// case "sdch": // Chrome, Android. Gzip output + dictionary header.
|
||||
// Parse the names of all accepted algorithms from the header.
|
||||
accepted := strings.Split(strings.ToLower(header), ",")
|
||||
|
||||
case strings.Contains(enc, "gzip"):
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
return encodingGzip
|
||||
|
||||
case strings.Contains(enc, "deflate"):
|
||||
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
|
||||
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
|
||||
// checksum compared to CRC-32 used in "gzip" and thus is faster.
|
||||
//
|
||||
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
|
||||
// raw DEFLATE data only, without the mentioned zlib wrapper.
|
||||
// Because of this major confusion, most modern browsers try it
|
||||
// both ways, first looking for zlib headers.
|
||||
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
|
||||
//
|
||||
// The list of browsers having problems is quite big, see:
|
||||
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
|
||||
//
|
||||
// That's why we prefer gzip over deflate. It's just more reliable
|
||||
// and not significantly slower than gzip.
|
||||
return encodingDeflate
|
||||
|
||||
// NOTE: Not implemented, intentionally:
|
||||
// case "compress": // LZW. Deprecated.
|
||||
// case "bzip2": // Too slow on-the-fly.
|
||||
// case "zopfli": // Too slow on-the-fly.
|
||||
// case "xz": // Too slow on-the-fly.
|
||||
// Find supported encoder by accepted list by precedence
|
||||
for _, name := range encodingPrecedence {
|
||||
if fn, ok := encoders[name]; ok && matchAcceptEncoding(accepted, name) {
|
||||
return fn, name
|
||||
}
|
||||
}
|
||||
|
||||
return encodingNone
|
||||
// No encoder found to match the accepted encoding
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
type maybeCompressResponseWriter struct {
|
||||
func matchAcceptEncoding(accepted []string, encoding string) bool {
|
||||
for _, v := range accepted {
|
||||
if strings.Index(v, encoding) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type compressResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
w io.Writer
|
||||
encoding encoding
|
||||
encoder EncoderFunc
|
||||
encoding string
|
||||
contentTypes map[string]struct{}
|
||||
level int
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) WriteHeader(code int) {
|
||||
if w.wroteHeader {
|
||||
func (cw *compressResponseWriter) WriteHeader(code int) {
|
||||
if cw.wroteHeader {
|
||||
return
|
||||
}
|
||||
w.wroteHeader = true
|
||||
defer w.ResponseWriter.WriteHeader(code)
|
||||
cw.wroteHeader = true
|
||||
defer cw.ResponseWriter.WriteHeader(code)
|
||||
|
||||
// Already compressed data?
|
||||
if w.ResponseWriter.Header().Get("Content-Encoding") != "" {
|
||||
if cw.Header().Get("Content-Encoding") != "" {
|
||||
return
|
||||
}
|
||||
// The content-length after compression is unknown
|
||||
w.ResponseWriter.Header().Del("Content-Length")
|
||||
|
||||
// Parse the first part of the Content-Type response header.
|
||||
contentType := ""
|
||||
parts := strings.Split(w.ResponseWriter.Header().Get("Content-Type"), ";")
|
||||
parts := strings.Split(cw.Header().Get("Content-Type"), ";")
|
||||
if len(parts) > 0 {
|
||||
contentType = parts[0]
|
||||
}
|
||||
|
||||
// Is the content type compressable?
|
||||
if _, ok := w.contentTypes[contentType]; !ok {
|
||||
if _, ok := cw.contentTypes[contentType]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Select the compress writer.
|
||||
switch w.encoding {
|
||||
case encodingGzip:
|
||||
gw, err := gzip.NewWriterLevel(w.ResponseWriter, w.level)
|
||||
if err != nil {
|
||||
w.w = w.ResponseWriter
|
||||
return
|
||||
}
|
||||
w.w = gw
|
||||
w.ResponseWriter.Header().Set("Content-Encoding", "gzip")
|
||||
if cw.encoder != nil && cw.encoding != "" {
|
||||
if wr := cw.encoder(cw.ResponseWriter, cw.level); wr != nil {
|
||||
cw.w = wr
|
||||
cw.Header().Set("Content-Encoding", cw.encoding)
|
||||
|
||||
case encodingDeflate:
|
||||
dw, err := flate.NewWriter(w.ResponseWriter, w.level)
|
||||
if err != nil {
|
||||
w.w = w.ResponseWriter
|
||||
return
|
||||
// The content-length after compression is unknown
|
||||
cw.Header().Del("Content-Length")
|
||||
}
|
||||
w.w = dw
|
||||
w.ResponseWriter.Header().Set("Content-Encoding", "deflate")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Write(p []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
func (cw *compressResponseWriter) Write(p []byte) (int, error) {
|
||||
if !cw.wroteHeader {
|
||||
cw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return w.w.Write(p)
|
||||
return cw.w.Write(p)
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Flush() {
|
||||
if f, ok := w.w.(http.Flusher); ok {
|
||||
func (cw *compressResponseWriter) Flush() {
|
||||
if f, ok := cw.w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := w.w.(http.Hijacker); ok {
|
||||
func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := cw.w.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) CloseNotify() <-chan bool {
|
||||
if cn, ok := w.w.(http.CloseNotifier); ok {
|
||||
return cn.CloseNotify()
|
||||
func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if ps, ok := cw.w.(http.Pusher); ok {
|
||||
return ps.Push(target, opts)
|
||||
}
|
||||
|
||||
// If the underlying writer does not implement http.CloseNotifier, return
|
||||
// a channel that never receives a value. The semantics here is that the
|
||||
// client never disconnnects before the request is processed by the
|
||||
// http.Handler, which is close enough to the default behavior (when
|
||||
// CloseNotify() is not even called).
|
||||
return make(chan bool, 1)
|
||||
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Close() error {
|
||||
if c, ok := w.w.(io.WriteCloser); ok {
|
||||
func (cw *compressResponseWriter) Close() error {
|
||||
if c, ok := cw.w.(io.WriteCloser); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
|
||||
}
|
||||
|
||||
func encoderGzip(w http.ResponseWriter, level int) io.Writer {
|
||||
gw, err := gzip.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return gw
|
||||
}
|
||||
|
||||
func encoderDeflate(w http.ResponseWriter, level int) io.Writer {
|
||||
dw, err := flate.NewWriter(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return dw
|
||||
}
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (w *maybeCompressResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if ps, ok := w.w.(http.Pusher); ok {
|
||||
return ps.Push(target, opts)
|
||||
}
|
||||
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
|
||||
}
|
||||
+6
@@ -26,6 +26,12 @@ func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handl
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ContentLength == 0 {
|
||||
// skip check for empty content body
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type")))
|
||||
if i := strings.Index(s, ";"); i > -1 {
|
||||
s = s[0:i]
|
||||
|
||||
+22
-18
@@ -16,7 +16,7 @@ var (
|
||||
// DefaultLogger is called by the Logger middleware handler to log each request.
|
||||
// Its made a package-level variable so that it can be reconfigured for custom
|
||||
// logging configurations.
|
||||
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags)})
|
||||
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
|
||||
)
|
||||
|
||||
// Logger is a middleware that logs the start and end of each request, along
|
||||
@@ -81,29 +81,32 @@ type LoggerInterface interface {
|
||||
|
||||
// DefaultLogFormatter is a simple logger that implements a LogFormatter.
|
||||
type DefaultLogFormatter struct {
|
||||
Logger LoggerInterface
|
||||
Logger LoggerInterface
|
||||
NoColor bool
|
||||
}
|
||||
|
||||
// NewLogEntry creates a new LogEntry for the request.
|
||||
func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
|
||||
useColor := !l.NoColor
|
||||
entry := &defaultLogEntry{
|
||||
DefaultLogFormatter: l,
|
||||
request: r,
|
||||
buf: &bytes.Buffer{},
|
||||
useColor: useColor,
|
||||
}
|
||||
|
||||
reqID := GetReqID(r.Context())
|
||||
if reqID != "" {
|
||||
cW(entry.buf, nYellow, "[%s] ", reqID)
|
||||
cW(entry.buf, useColor, nYellow, "[%s] ", reqID)
|
||||
}
|
||||
cW(entry.buf, nCyan, "\"")
|
||||
cW(entry.buf, bMagenta, "%s ", r.Method)
|
||||
cW(entry.buf, useColor, nCyan, "\"")
|
||||
cW(entry.buf, useColor, bMagenta, "%s ", r.Method)
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
cW(entry.buf, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
|
||||
cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
|
||||
|
||||
entry.buf.WriteString("from ")
|
||||
entry.buf.WriteString(r.RemoteAddr)
|
||||
@@ -114,33 +117,34 @@ func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
|
||||
|
||||
type defaultLogEntry struct {
|
||||
*DefaultLogFormatter
|
||||
request *http.Request
|
||||
buf *bytes.Buffer
|
||||
request *http.Request
|
||||
buf *bytes.Buffer
|
||||
useColor bool
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
switch {
|
||||
case status < 200:
|
||||
cW(l.buf, bBlue, "%03d", status)
|
||||
cW(l.buf, l.useColor, bBlue, "%03d", status)
|
||||
case status < 300:
|
||||
cW(l.buf, bGreen, "%03d", status)
|
||||
cW(l.buf, l.useColor, bGreen, "%03d", status)
|
||||
case status < 400:
|
||||
cW(l.buf, bCyan, "%03d", status)
|
||||
cW(l.buf, l.useColor, bCyan, "%03d", status)
|
||||
case status < 500:
|
||||
cW(l.buf, bYellow, "%03d", status)
|
||||
cW(l.buf, l.useColor, bYellow, "%03d", status)
|
||||
default:
|
||||
cW(l.buf, bRed, "%03d", status)
|
||||
cW(l.buf, l.useColor, bRed, "%03d", status)
|
||||
}
|
||||
|
||||
cW(l.buf, bBlue, " %dB", bytes)
|
||||
cW(l.buf, l.useColor, bBlue, " %dB", bytes)
|
||||
|
||||
l.buf.WriteString(" in ")
|
||||
if elapsed < 500*time.Millisecond {
|
||||
cW(l.buf, nGreen, "%s", elapsed)
|
||||
cW(l.buf, l.useColor, nGreen, "%s", elapsed)
|
||||
} else if elapsed < 5*time.Second {
|
||||
cW(l.buf, nYellow, "%s", elapsed)
|
||||
cW(l.buf, l.useColor, nYellow, "%s", elapsed)
|
||||
} else {
|
||||
cW(l.buf, nRed, "%s", elapsed)
|
||||
cW(l.buf, l.useColor, nRed, "%s", elapsed)
|
||||
}
|
||||
|
||||
l.Logger.Print(l.buf.String())
|
||||
@@ -148,7 +152,7 @@ func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
|
||||
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
|
||||
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
|
||||
cW(panicEntry.buf, bRed, "panic: %+v", v)
|
||||
cW(panicEntry.buf, l.useColor, bRed, "panic: %+v", v)
|
||||
l.Logger.Print(panicEntry.buf.String())
|
||||
l.Logger.Print(string(stack))
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ var epoch = time.Unix(0, 0).Format(time.RFC1123)
|
||||
// Taken from https://github.com/mytrile/nocache
|
||||
var noCacheHeaders = map[string]string{
|
||||
"Expires": epoch,
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate, private, max-age=0",
|
||||
"Cache-Control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
"X-Accel-Expires": "0",
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ var xRealIP = http.CanonicalHeaderKey("X-Real-IP")
|
||||
// You should only use this middleware if you can trust the headers passed to
|
||||
// you (in particular, the two headers this middleware uses), for example
|
||||
// because you have placed a reverse proxy like HAProxy or nginx in front of
|
||||
// Goji. If your reverse proxies are configured to pass along arbitrary header
|
||||
// chi. If your reverse proxies are configured to pass along arbitrary header
|
||||
// values from the client, or if you use this middleware without a reverse
|
||||
// proxy, malicious clients will be able to make you very sad (or, depending on
|
||||
// how you're using RemoteAddr, vulnerable to an attack of some sort).
|
||||
|
||||
+7
-3
@@ -17,7 +17,7 @@ import (
|
||||
// Key to use when setting the request ID.
|
||||
type ctxKeyRequestID int
|
||||
|
||||
// RequestIDKey is the key that holds th unique request ID in a request context.
|
||||
// RequestIDKey is the key that holds the unique request ID in a request context.
|
||||
const RequestIDKey ctxKeyRequestID = 0
|
||||
|
||||
var prefix string
|
||||
@@ -62,9 +62,13 @@ func init() {
|
||||
// counter.
|
||||
func RequestID(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
myid := atomic.AddUint64(&reqid, 1)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, myid))
|
||||
requestID := r.Header.Get("X-Request-Id")
|
||||
if requestID == "" {
|
||||
myid := atomic.AddUint64(&reqid, 1)
|
||||
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
|
||||
}
|
||||
ctx = context.WithValue(ctx, RequestIDKey, requestID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
|
||||
+9
-1
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
@@ -28,6 +29,9 @@ func StripSlashes(next http.Handler) http.Handler {
|
||||
|
||||
// RedirectSlashes is a middleware that will match request paths with a trailing
|
||||
// slash and redirect to the same path, less the trailing slash.
|
||||
//
|
||||
// NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer,
|
||||
// see https://github.com/go-chi/chi/issues/343
|
||||
func RedirectSlashes(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var path string
|
||||
@@ -38,7 +42,11 @@ func RedirectSlashes(next http.Handler) http.Handler {
|
||||
path = r.URL.Path
|
||||
}
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
if r.URL.RawQuery != "" {
|
||||
path = fmt.Sprintf("%s?%s", path[:len(path)-1], r.URL.RawQuery)
|
||||
} else {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
http.Redirect(w, r, path, 301)
|
||||
return
|
||||
}
|
||||
|
||||
+3
-3
@@ -52,12 +52,12 @@ func init() {
|
||||
}
|
||||
|
||||
// colorWrite
|
||||
func cW(w io.Writer, color []byte, s string, args ...interface{}) {
|
||||
if isTTY {
|
||||
func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) {
|
||||
if isTTY && useColor {
|
||||
w.Write(color)
|
||||
}
|
||||
fmt.Fprintf(w, s, args...)
|
||||
if isTTY {
|
||||
if isTTY && useColor {
|
||||
w.Write(reset)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ import (
|
||||
//
|
||||
// ie. a route/handler may look like:
|
||||
//
|
||||
// r.Get("/long", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// r.Get("/long", func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// processTime := time.Duration(rand.Intn(4)+1) * time.Second
|
||||
//
|
||||
// select {
|
||||
|
||||
+47
-12
@@ -10,6 +10,32 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
|
||||
// hook into various parts of the response process.
|
||||
func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
_, ps := w.(http.Pusher)
|
||||
if fl && ps {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
|
||||
// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
|
||||
// into various parts of the response process.
|
||||
type WrapResponseWriter interface {
|
||||
@@ -47,6 +73,7 @@ func (b *basicWriter) WriteHeader(code int) {
|
||||
b.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Write(buf []byte) (int, error) {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
n, err := b.ResponseWriter.Write(buf)
|
||||
@@ -60,20 +87,25 @@ func (b *basicWriter) Write(buf []byte) (int, error) {
|
||||
b.bytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *basicWriter) maybeWriteHeader() {
|
||||
if !b.wroteHeader {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Status() int {
|
||||
return b.code
|
||||
}
|
||||
|
||||
func (b *basicWriter) BytesWritten() int {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
func (b *basicWriter) Tee(w io.Writer) {
|
||||
b.tee = w
|
||||
}
|
||||
|
||||
func (b *basicWriter) Unwrap() http.ResponseWriter {
|
||||
return b.ResponseWriter
|
||||
}
|
||||
@@ -83,13 +115,15 @@ type flushWriter struct {
|
||||
}
|
||||
|
||||
func (f *flushWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.Flusher = &flushWriter{}
|
||||
|
||||
// httpFancyWriter is a HTTP writer that additionally satisfies http.CloseNotifier,
|
||||
// httpFancyWriter is a HTTP writer that additionally satisfies
|
||||
// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case
|
||||
// of wrapping the http.ResponseWriter that package http gives you, in order to
|
||||
// make the proxied object support the full method set of the proxied object.
|
||||
@@ -97,18 +131,22 @@ type httpFancyWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) CloseNotify() <-chan bool {
|
||||
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
func (f *httpFancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||
if f.basicWriter.tee != nil {
|
||||
n, err := io.Copy(&f.basicWriter, r)
|
||||
@@ -122,12 +160,12 @@ func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
var _ http.CloseNotifier = &httpFancyWriter{}
|
||||
var _ http.Flusher = &httpFancyWriter{}
|
||||
var _ http.Hijacker = &httpFancyWriter{}
|
||||
var _ http.Pusher = &http2FancyWriter{}
|
||||
var _ io.ReaderFrom = &httpFancyWriter{}
|
||||
|
||||
// http2FancyWriter is a HTTP2 writer that additionally satisfies http.CloseNotifier,
|
||||
// http2FancyWriter is a HTTP2 writer that additionally satisfies
|
||||
// http.Flusher, and io.ReaderFrom. It exists for the common case
|
||||
// of wrapping the http.ResponseWriter that package http gives you, in order to
|
||||
// make the proxied object support the full method set of the proxied object.
|
||||
@@ -135,14 +173,11 @@ type http2FancyWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) CloseNotify() <-chan bool {
|
||||
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
func (f *http2FancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.CloseNotifier = &http2FancyWriter{}
|
||||
var _ http.Flusher = &http2FancyWriter{}
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
// +build go1.7,!go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
|
||||
// hook into various parts of the response process.
|
||||
func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
if cn && fl {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if cn && fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
|
||||
// hook into various parts of the response process.
|
||||
func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
_, ps := w.(http.Pusher)
|
||||
if cn && fl && ps {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if cn && fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
||||
var _ http.Pusher = &http2FancyWriter{}
|
||||
+2
-1
@@ -60,7 +60,8 @@ func NewMux() *Mux {
|
||||
func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Ensure the mux has some routes defined on the mux
|
||||
if mx.handler == nil {
|
||||
panic("chi: attempting to route to a mux with no handlers.")
|
||||
mx.NotFoundHandler().ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a routing context already exists from a parent router.
|
||||
|
||||
+12
-10
@@ -33,15 +33,15 @@ var mALL = mCONNECT | mDELETE | mGET | mHEAD |
|
||||
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
|
||||
|
||||
var methodMap = map[string]methodTyp{
|
||||
"CONNECT": mCONNECT,
|
||||
"DELETE": mDELETE,
|
||||
"GET": mGET,
|
||||
"HEAD": mHEAD,
|
||||
"OPTIONS": mOPTIONS,
|
||||
"PATCH": mPATCH,
|
||||
"POST": mPOST,
|
||||
"PUT": mPUT,
|
||||
"TRACE": mTRACE,
|
||||
http.MethodConnect: mCONNECT,
|
||||
http.MethodDelete: mDELETE,
|
||||
http.MethodGet: mGET,
|
||||
http.MethodHead: mHEAD,
|
||||
http.MethodOptions: mOPTIONS,
|
||||
http.MethodPatch: mPATCH,
|
||||
http.MethodPost: mPOST,
|
||||
http.MethodPut: mPUT,
|
||||
http.MethodTrace: mTRACE,
|
||||
}
|
||||
|
||||
// RegisterMethod adds support for custom HTTP method handlers, available
|
||||
@@ -706,7 +706,9 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
|
||||
}
|
||||
|
||||
// Wildcard pattern as finale
|
||||
// TODO: should we panic if there is stuff after the * ???
|
||||
if ws < len(pattern)-1 {
|
||||
panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead")
|
||||
}
|
||||
return ntCatchAll, "*", "", 0, ws, len(pattern)
|
||||
}
|
||||
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 100
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
misspell:
|
||||
locale: US
|
||||
lll:
|
||||
line-length: 140
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- performance
|
||||
- style
|
||||
- experimental
|
||||
disabled-checks:
|
||||
- wrapperFunc
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
fast: false
|
||||
|
||||
|
||||
run:
|
||||
# modules-download-mode: vendor
|
||||
skip-dirs:
|
||||
- vendor
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.16.x
|
||||
+2
-3
@@ -13,7 +13,6 @@ before_install:
|
||||
|
||||
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;
|
||||
- golangci-lint run || travis_terminate 1;
|
||||
- GO111MODULE=on go test -v -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- golangci-lint run || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
+11
-7
@@ -32,7 +32,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
|
||||
`lgr.New` call accepts functional options:
|
||||
|
||||
- `lgr.Debug` - turn debug mode on to allow messages with "DEBUG" level (filtered overwise)
|
||||
- `lgr.Debug` - turn debug mode on to allow messages with "DEBUG" level (filtered otherwise)
|
||||
- `lgr.Out(io.Writer)` - sets the output writer, default `os.Stdout`
|
||||
- `lgr.Err(io.Writer)` - sets the error writer, default `os.Stderr`
|
||||
- `lgr.CallerFile` - adds the caller file info
|
||||
@@ -42,6 +42,8 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
- `lgr.Msec` - adds milliseconds to timestamp
|
||||
- `lgr.Format` - sets custom template, overwrite all other formatting modifiers.
|
||||
|
||||
example: `l := lgr.New(lgr.Debug, lgr.Msec)`
|
||||
|
||||
#### formatting templates:
|
||||
|
||||
Several predefined templates provided and can be passed directly to `lgr.Format`, i.e. `lgr.Format(lgr.WithMsec)`
|
||||
@@ -58,9 +60,11 @@ Several predefined templates provided and can be passed directly to `lgr.Format`
|
||||
User can make a custom template and pass it directly to `lgr.Format`. For example:
|
||||
|
||||
```go
|
||||
lgr.Format(`{{.Level}} - {{.DT.Format "2006-01-02T15:04:05Z07:00") - {{.CallerPkg}} - {{.Message}}`)
|
||||
lgr.Format(`{{.Level}} - {{.DT.Format "2006-01-02T15:04:05Z07:00"}} - {{.CallerPkg}} - {{.Message}}`)
|
||||
```
|
||||
)
|
||||
|
||||
_Note: formatter (predefined or custom) adds measurable overhead - the cost will depend on the version of Go, but is between 30
|
||||
and 50% in recent tests with 1.12. You can validate this in your environment via benchmarks: `go test -bench=. -run=Bench`_
|
||||
|
||||
### levels
|
||||
|
||||
@@ -70,8 +74,9 @@ User can make a custom template and pass it directly to `lgr.Format`. For exampl
|
||||
- `DEBUG` will be filtered unless `lgr.Debug` or `lgr.Trace` options 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 calls `os.Exit(1)`.
|
||||
|
||||
- `FATAL` and send messages to both out and err writers and exit(1)
|
||||
- `PANIC` does the same as `FATAL` but in addition sends dump of callers and runtime info to err.
|
||||
|
||||
### adaptors
|
||||
|
||||
`lgr` logger can be converted to `io.Writer` or `*log.Logger`
|
||||
@@ -79,11 +84,10 @@ User can make a custom template and pass it directly to `lgr.Format`. For exampl
|
||||
- `lgr.ToWriter(l lgr.L, level string) io.Writer` - makes io.Writer forwarding write ops to underlying `lgr.L`
|
||||
- `lgr.ToStdLogger(l lgr.L, level string) *log.Logger` - makes standard logger on top of `lgr.L`
|
||||
|
||||
_`level` parameter is optional, if defined will enforce the level._
|
||||
_`level` parameter is optional, if defined (non-empty) will enforce the level._
|
||||
|
||||
### global logger
|
||||
|
||||
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 retrieved with `lgr.Default()`
|
||||
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@ type Writer struct {
|
||||
level string // if defined added to each message
|
||||
}
|
||||
|
||||
// Write to lgr.L, trim EOL
|
||||
// Write to lgr.L
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
w.Logf(strings.TrimSuffix(w.level+string(p), "\n"))
|
||||
w.Logf(w.level + string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@ package lgr
|
||||
|
||||
import (
|
||||
stdlog "log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var def = New() // default logger doesn't allow DEBUG and doesn't add caller info
|
||||
@@ -37,7 +36,7 @@ func Print(line string) {
|
||||
// Fatalf simplifies replacement of std logger
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
def.logf(format, args...)
|
||||
os.Exit(1)
|
||||
def.fatal()
|
||||
}
|
||||
|
||||
// Setup default logger with options
|
||||
|
||||
+61
-120
@@ -1,10 +1,11 @@
|
||||
// Package lgr provides a simple logger with some extras. Primary way to log is Logf method.
|
||||
// The logger's output can be customized in 2 ways:
|
||||
// - by passing formatting template, i.e. lgr.New(lgr.Format(lgr.Short))
|
||||
// - by setting individual formatting flags, i.e. lgr.New(lgr.Msec, lgr.CallerFunc)
|
||||
// Leveled output works for messages based on level prefix, i.e. Logf("INFO some message") means INFO level.
|
||||
// - by passing formatting template, i.e. lgr.New(lgr.Format(lgr.Short))
|
||||
// Leveled output works for messages based on text prefix, i.e. Logf("INFO some message") means INFO level.
|
||||
// Debug and trace levels can be filtered based on lgr.Trace and lgr.Debug options.
|
||||
// ERROR, FATAL and PANIC levels send to err as well. Both FATAL and PANIC also print stack trace and terminate caller application with os.Exit(1)
|
||||
// ERROR, FATAL and PANIC levels send to err as well. FATAL terminate caller application with os.Exit(1)
|
||||
// and PANIC also prints stack trace.
|
||||
|
||||
package lgr
|
||||
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
@@ -59,7 +61,7 @@ type Logger struct {
|
||||
type nowFn func() time.Time
|
||||
type panicFn func()
|
||||
|
||||
// layout holds all parts to construct the final message with template
|
||||
// layout holds all parts to construct the final message with template or with individual flags
|
||||
type layout struct {
|
||||
DT time.Time
|
||||
Level string
|
||||
@@ -85,27 +87,28 @@ func New(options ...Option) *Logger {
|
||||
opt(&res)
|
||||
}
|
||||
|
||||
var err error
|
||||
if res.format == "" {
|
||||
res.format = res.templateFromOptions()
|
||||
if res.format != "" {
|
||||
// formatter defined
|
||||
var err error
|
||||
res.templ, err = template.New("lgr").Parse(res.format)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid template %s, error %v. switched to %s\n", res.format, err, Short)
|
||||
res.format = Short
|
||||
res.templ = template.Must(template.New("lgrDefault").Parse(Short))
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
if err = res.templ.Execute(&buf, layout{}); err != nil {
|
||||
fmt.Printf("failed to execute template %s, error %v. switched to %s\n", res.format, err, Short)
|
||||
res.format = Short
|
||||
res.templ = template.Must(template.New("lgrDefault").Parse(Short))
|
||||
}
|
||||
}
|
||||
|
||||
res.templ, err = template.New("lgr").Parse(res.format)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid template %s, error %v. switched to %s\n", res.format, err, Short)
|
||||
res.format = Short
|
||||
res.templ = template.Must(template.New("lgrDefault").Parse(Short))
|
||||
}
|
||||
// set *On flags once for optimization on multiple Logf calls
|
||||
res.callerOn = strings.Contains(res.format, "{{.Caller") || res.callerFile || res.callerFunc || res.callerPkg
|
||||
res.levelBracesOn = strings.Contains(res.format, "[{{.Level}}]") || res.levelBraces
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
if err = res.templ.Execute(&buf, layout{}); err != nil {
|
||||
fmt.Printf("failed to execute template %s, error %v. switched to %s\n", res.format, err, Short)
|
||||
res.format = Short
|
||||
res.templ = template.Must(template.New("lgrDefault").Parse(Short))
|
||||
}
|
||||
|
||||
res.callerOn = strings.Contains(res.format, "{{.Caller")
|
||||
res.levelBracesOn = strings.Contains(res.format, "[{{.Level}}]")
|
||||
return &res
|
||||
}
|
||||
|
||||
@@ -128,7 +131,7 @@ func (l *Logger) logf(format string, args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
ci := callerInfo{}
|
||||
var ci callerInfo
|
||||
if l.callerOn { // optimization to avoid expensive caller evaluation if caller info not in the template
|
||||
ci = l.reportCaller(l.callerDepth)
|
||||
}
|
||||
@@ -136,21 +139,26 @@ func (l *Logger) logf(format string, args ...interface{}) {
|
||||
elems := layout{
|
||||
DT: l.now(),
|
||||
Level: l.formatLevel(lv),
|
||||
Message: strings.TrimSuffix(msg, "\n"),
|
||||
Message: strings.TrimSuffix(msg, "\n"), // output adds EOL, trim from the message if passed
|
||||
CallerFunc: ci.FuncName,
|
||||
CallerFile: ci.File,
|
||||
CallerPkg: ci.Pkg,
|
||||
CallerLine: ci.Line,
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
err := l.templ.Execute(&buf, elems) // once constructed, a template may be executed safely in parallel.
|
||||
if err != nil {
|
||||
fmt.Printf("failed to execute template, %v\n", err)
|
||||
var data []byte
|
||||
if l.format == "" {
|
||||
data = []byte(l.formatWithOptions(elems))
|
||||
} else {
|
||||
buf := bytes.Buffer{}
|
||||
err := l.templ.Execute(&buf, elems) // once constructed, a template may be executed safely in parallel.
|
||||
if err != nil {
|
||||
fmt.Printf("failed to execute template, %v\n", err) // should never happen
|
||||
}
|
||||
data = buf.Bytes()
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
data = append(data, '\n')
|
||||
|
||||
data := buf.Bytes()
|
||||
if l.levelBracesOn { // rearrange space in short levels
|
||||
data = bytes.Replace(data, []byte("[WARN ]"), []byte("[WARN] "), 1)
|
||||
data = bytes.Replace(data, []byte("[INFO ]"), []byte("[INFO] "), 1)
|
||||
@@ -230,51 +238,51 @@ func (l *Logger) reportCaller(calldepth int) (res callerInfo) {
|
||||
return res
|
||||
}
|
||||
|
||||
// make template from option flags
|
||||
func (l *Logger) templateFromOptions() (res string) {
|
||||
// speed-optimized version of formatter, used with individual options only, i.e. without Format call
|
||||
func (l *Logger) formatWithOptions(elems layout) (res string) {
|
||||
|
||||
const (
|
||||
// escape { and } from templates to allow "{some/blah}" output for caller
|
||||
openCallerBrace = `{{"{"}}`
|
||||
closeCallerBrace = `{{"}"}}`
|
||||
)
|
||||
|
||||
orElse := func(flag bool, value string, elseValue string) string {
|
||||
orElse := func(flag bool, fnTrue func() string, fnFalse func() string) string {
|
||||
if flag {
|
||||
return value
|
||||
return fnTrue()
|
||||
}
|
||||
return elseValue
|
||||
return fnFalse()
|
||||
}
|
||||
nothing := func() string { return "" }
|
||||
|
||||
var parts []string
|
||||
parts := make([]string, 0, 4)
|
||||
|
||||
parts = append(parts, orElse(l.msec, `{{.DT.Format "2006/01/02 15:04:05.000"}}`, `{{.DT.Format "2006/01/02 15:04:05"}}`))
|
||||
parts = append(parts, orElse(l.levelBraces, `[{{.Level}}]`, `{{.Level}}`))
|
||||
parts = append(parts, orElse(l.msec,
|
||||
func() string { return elems.DT.Format("2006/01/02 15:04:05.000") },
|
||||
func() string { return elems.DT.Format("2006/01/02 15:04:05") },
|
||||
))
|
||||
|
||||
parts = append(parts, orElse(l.levelBraces,
|
||||
func() string { return `[` + elems.Level + `]` },
|
||||
func() string { return elems.Level },
|
||||
))
|
||||
|
||||
if l.callerFile || l.callerFunc || l.callerPkg {
|
||||
var callerParts []string
|
||||
if v := orElse(l.callerFile, `{{.CallerFile}}:{{.CallerLine}}`, ""); v != "" {
|
||||
v := orElse(l.callerFile, func() string { return elems.CallerFile + ":" + strconv.Itoa(elems.CallerLine) }, nothing)
|
||||
if v != "" {
|
||||
callerParts = append(callerParts, v)
|
||||
}
|
||||
if v := orElse(l.callerFunc, `{{.CallerFunc}}`, ""); v != "" {
|
||||
if v := orElse(l.callerFunc, func() string { return elems.CallerFunc }, nothing); v != "" {
|
||||
callerParts = append(callerParts, v)
|
||||
}
|
||||
if v := orElse(l.callerPkg, `{{.CallerPkg}}`, ""); v != "" {
|
||||
if v := orElse(l.callerPkg, func() string { return elems.CallerPkg }, nothing); v != "" {
|
||||
callerParts = append(callerParts, v)
|
||||
}
|
||||
parts = append(parts, openCallerBrace+strings.Join(callerParts, " ")+closeCallerBrace)
|
||||
parts = append(parts, "{"+strings.Join(callerParts, " ")+"}")
|
||||
}
|
||||
parts = append(parts, "{{.Message}}")
|
||||
|
||||
parts = append(parts, elems.Message)
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// formatLevel aligns level to 5 chars
|
||||
func (l *Logger) formatLevel(lv string) string {
|
||||
|
||||
if lv == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
spaces := ""
|
||||
if len(lv) == 4 {
|
||||
spaces = " "
|
||||
@@ -305,70 +313,3 @@ func getDump() []byte {
|
||||
}
|
||||
return stacktrace[:length]
|
||||
}
|
||||
|
||||
// Option func type
|
||||
type Option func(l *Logger)
|
||||
|
||||
// Out sets out writer, stdout by default
|
||||
func Out(w io.Writer) Option {
|
||||
return func(l *Logger) {
|
||||
l.stdout = w
|
||||
}
|
||||
}
|
||||
|
||||
// Err sets error writer, stderr by default
|
||||
func Err(w io.Writer) Option {
|
||||
return func(l *Logger) {
|
||||
l.stderr = w
|
||||
}
|
||||
}
|
||||
|
||||
// Debug turn on dbg mode
|
||||
func Debug(l *Logger) {
|
||||
l.dbg = true
|
||||
}
|
||||
|
||||
// Trace turn on trace + dbg mode
|
||||
func Trace(l *Logger) {
|
||||
l.dbg = true
|
||||
l.trace = true
|
||||
}
|
||||
|
||||
// CallerDepth sets number of stack frame skipped for caller reporting, 0 by default
|
||||
func CallerDepth(n int) Option {
|
||||
return func(l *Logger) {
|
||||
l.callerDepth = n
|
||||
}
|
||||
}
|
||||
|
||||
// Format sets output layout, overwrites all options for individual parts, i.e. Caller*, Msec and LevelBraces
|
||||
func Format(f string) Option {
|
||||
return func(l *Logger) {
|
||||
l.format = f
|
||||
}
|
||||
}
|
||||
|
||||
// CallerFunc adds caller info with function name. Ignored if Format option used.
|
||||
func CallerFunc(l *Logger) {
|
||||
l.callerFunc = true
|
||||
}
|
||||
|
||||
// CallerPkg adds caller's package name. Ignored if Format option used.
|
||||
func CallerPkg(l *Logger) {
|
||||
l.callerPkg = true
|
||||
}
|
||||
|
||||
// LevelBraces surrounds level with [], i.e. [INFO]. Ignored if Format option used.
|
||||
func LevelBraces(l *Logger) {
|
||||
l.levelBraces = true
|
||||
}
|
||||
|
||||
// CallerFile adds caller info with file, and line number. Ignored if Format option used.
|
||||
func CallerFile(l *Logger) {
|
||||
l.callerFile = true
|
||||
}
|
||||
|
||||
// Msec adds .msec to timestamp. Ignored if Format option used.
|
||||
func Msec(l *Logger) {
|
||||
l.msec = true
|
||||
}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package lgr
|
||||
|
||||
import "io"
|
||||
|
||||
// Option func type
|
||||
type Option func(l *Logger)
|
||||
|
||||
// Out sets out writer, stdout by default
|
||||
func Out(w io.Writer) Option {
|
||||
return func(l *Logger) {
|
||||
l.stdout = w
|
||||
}
|
||||
}
|
||||
|
||||
// Err sets error writer, stderr by default
|
||||
func Err(w io.Writer) Option {
|
||||
return func(l *Logger) {
|
||||
l.stderr = w
|
||||
}
|
||||
}
|
||||
|
||||
// Debug turn on dbg mode
|
||||
func Debug(l *Logger) {
|
||||
l.dbg = true
|
||||
}
|
||||
|
||||
// Trace turn on trace + dbg mode
|
||||
func Trace(l *Logger) {
|
||||
l.dbg = true
|
||||
l.trace = true
|
||||
}
|
||||
|
||||
// CallerDepth sets number of stack frame skipped for caller reporting, 0 by default
|
||||
func CallerDepth(n int) Option {
|
||||
return func(l *Logger) {
|
||||
l.callerDepth = n
|
||||
}
|
||||
}
|
||||
|
||||
// Format sets output layout, overwrites all options for individual parts, i.e. Caller*, Msec and LevelBraces
|
||||
func Format(f string) Option {
|
||||
return func(l *Logger) {
|
||||
l.format = f
|
||||
}
|
||||
}
|
||||
|
||||
// CallerFunc adds caller info with function name. Ignored if Format option used.
|
||||
func CallerFunc(l *Logger) {
|
||||
l.callerFunc = true
|
||||
}
|
||||
|
||||
// CallerPkg adds caller's package name. Ignored if Format option used.
|
||||
func CallerPkg(l *Logger) {
|
||||
l.callerPkg = true
|
||||
}
|
||||
|
||||
// LevelBraces surrounds level with [], i.e. [INFO]. Ignored if Format option used.
|
||||
func LevelBraces(l *Logger) {
|
||||
l.levelBraces = true
|
||||
}
|
||||
|
||||
// CallerFile adds caller info with file, and line number. Ignored if Format option used.
|
||||
func CallerFile(l *Logger) {
|
||||
l.callerFile = true
|
||||
}
|
||||
|
||||
// Msec adds .msec to timestamp. Ignored if Format option used.
|
||||
func Msec(l *Logger) {
|
||||
l.msec = true
|
||||
}
|
||||
Vendored
+2
-2
@@ -23,7 +23,7 @@ github.com/globalsign/mgo/bson
|
||||
github.com/globalsign/mgo/internal/sasl
|
||||
github.com/globalsign/mgo/internal/scram
|
||||
github.com/globalsign/mgo/internal/json
|
||||
# github.com/go-chi/chi v3.3.2+incompatible
|
||||
# github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/chi
|
||||
github.com/go-chi/chi/middleware
|
||||
# github.com/go-chi/cors v1.0.0
|
||||
@@ -39,7 +39,7 @@ github.com/go-pkgz/auth/logger
|
||||
github.com/go-pkgz/auth/middleware
|
||||
# github.com/go-pkgz/lcw v0.2.0
|
||||
github.com/go-pkgz/lcw
|
||||
# github.com/go-pkgz/lgr v0.6.1
|
||||
# github.com/go-pkgz/lgr v0.6.2
|
||||
github.com/go-pkgz/lgr
|
||||
# github.com/go-pkgz/mongo v1.1.2
|
||||
github.com/go-pkgz/mongo
|
||||
|
||||
Reference in New Issue
Block a user