mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-18 13:35:37 -04:00
Merge pull request #9714 from kobergj/BumpTusdPkg
[full-ci] Bump Reva & Tusd
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
Enhancement: Bump reva
|
||||
|
||||
https://github.com/owncloud/ocis/pull/9715
|
||||
https://github.com/owncloud/ocis/pull/9714
|
||||
https://github.com/owncloud/ocis/pull/9715
|
||||
|
||||
5
changelog/unreleased/bump-tusd.md
Normal file
5
changelog/unreleased/bump-tusd.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Bump tusd pkg to v2
|
||||
|
||||
Bumps the tusd pkg to v2.4.0
|
||||
|
||||
https://github.com/owncloud/ocis/pull/9714
|
||||
6
go.mod
6
go.mod
@@ -15,7 +15,7 @@ require (
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20240724121416-062c4e3046cb
|
||||
github.com/cs3org/reva/v2 v2.22.1-0.20240730105121-548644c31544
|
||||
github.com/cs3org/reva/v2 v2.22.1-0.20240806075425-8bcdd93dfa20
|
||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||
github.com/egirna/icap-client v0.1.1
|
||||
@@ -88,7 +88,7 @@ require (
|
||||
github.com/test-go/testify v1.1.4
|
||||
github.com/thejerf/suture/v4 v4.0.5
|
||||
github.com/tidwall/gjson v1.17.1
|
||||
github.com/tus/tusd v1.13.0
|
||||
github.com/tus/tusd/v2 v2.4.0
|
||||
github.com/unrolled/secure v1.14.0
|
||||
github.com/urfave/cli/v2 v2.27.2
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
@@ -156,7 +156,6 @@ require (
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.5 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f // indirect
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/ceph/go-ceph v0.18.0 // indirect
|
||||
@@ -226,6 +225,7 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v1.8.9 // indirect
|
||||
github.com/google/flatbuffers v2.0.8+incompatible // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
|
||||
github.com/google/renameio/v2 v2.0.0 // indirect
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
|
||||
3
vendor/github.com/bmizerany/pat/.gitignore
generated
vendored
3
vendor/github.com/bmizerany/pat/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
||||
*.prof
|
||||
*.out
|
||||
example/example
|
||||
19
vendor/github.com/bmizerany/pat/LICENSE
generated
vendored
19
vendor/github.com/bmizerany/pat/LICENSE
generated
vendored
@@ -1,19 +0,0 @@
|
||||
Copyright (C) 2012 by Keith Rarick, Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
82
vendor/github.com/bmizerany/pat/README.md
generated
vendored
82
vendor/github.com/bmizerany/pat/README.md
generated
vendored
@@ -1,82 +0,0 @@
|
||||
# pat (formerly pat.go) - A Sinatra style pattern muxer for Go's net/http library
|
||||
|
||||
[](https://godoc.org/github.com/bmizerany/pat)
|
||||
|
||||
## INSTALL
|
||||
|
||||
$ go get github.com/bmizerany/pat
|
||||
|
||||
## USE
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"github.com/bmizerany/pat"
|
||||
"log"
|
||||
)
|
||||
|
||||
// hello world, the web server
|
||||
func HelloServer(w http.ResponseWriter, req *http.Request) {
|
||||
io.WriteString(w, "hello, "+req.URL.Query().Get(":name")+"!\n")
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := pat.New()
|
||||
m.Get("/hello/:name", http.HandlerFunc(HelloServer))
|
||||
|
||||
// Register this pat with the default serve mux so that other packages
|
||||
// may also be exported. (i.e. /debug/pprof/*)
|
||||
http.Handle("/", m)
|
||||
err := http.ListenAndServe(":12345", nil)
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It's that simple.
|
||||
|
||||
For more information, see:
|
||||
http://godoc.org/github.com/bmizerany/pat
|
||||
|
||||
## CONTRIBUTORS
|
||||
|
||||
* Alexis Svinartchouk (@zvin)
|
||||
* Blake Mizerany (@bmizerany)
|
||||
* Brian Ketelsen (@bketelsen)
|
||||
* Bryan Matsuo (@bmatsuo)
|
||||
* Caleb Spare (@cespare)
|
||||
* Evan Shaw (@edsrzf)
|
||||
* Gary Burd (@garyburd)
|
||||
* George Rogers (@georgerogers42)
|
||||
* Keith Rarick (@kr)
|
||||
* Matt Williams (@mattyw)
|
||||
* Mike Stipicevic (@wickedchicken)
|
||||
* Nick Saika (@nesv)
|
||||
* Timothy Cyrus (@tcyrus)
|
||||
* binqin (@binku87)
|
||||
|
||||
## LICENSE
|
||||
|
||||
Copyright (C) 2012 by Keith Rarick, Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
314
vendor/github.com/bmizerany/pat/mux.go
generated
vendored
314
vendor/github.com/bmizerany/pat/mux.go
generated
vendored
@@ -1,314 +0,0 @@
|
||||
// Package pat implements a simple URL pattern muxer
|
||||
package pat
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PatternServeMux is an HTTP request multiplexer. It matches the URL of each
|
||||
// incoming request against a list of registered patterns with their associated
|
||||
// methods and calls the handler for the pattern that most closely matches the
|
||||
// URL.
|
||||
//
|
||||
// Pattern matching attempts each pattern in the order in which they were
|
||||
// registered.
|
||||
//
|
||||
// Patterns may contain literals or captures. Capture names start with a colon
|
||||
// and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
|
||||
// matches literally. The portion of the URL matching each name ends with an
|
||||
// occurrence of the character in the pattern immediately following the name,
|
||||
// or a /, whichever comes first. It is possible for a name to match the empty
|
||||
// string.
|
||||
//
|
||||
// Example pattern with one capture:
|
||||
// /hello/:name
|
||||
// Will match:
|
||||
// /hello/blake
|
||||
// /hello/keith
|
||||
// Will not match:
|
||||
// /hello/blake/
|
||||
// /hello/blake/foo
|
||||
// /foo
|
||||
// /foo/bar
|
||||
//
|
||||
// Example 2:
|
||||
// /hello/:name/
|
||||
// Will match:
|
||||
// /hello/blake/
|
||||
// /hello/keith/foo
|
||||
// /hello/blake
|
||||
// /hello/keith
|
||||
// Will not match:
|
||||
// /foo
|
||||
// /foo/bar
|
||||
//
|
||||
// A pattern ending with a slash will add an implicit redirect for its non-slash
|
||||
// version. For example: Get("/foo/", handler) also registers
|
||||
// Get("/foo", handler) as a redirect. You may override it by registering
|
||||
// Get("/foo", anotherhandler) before the slash version.
|
||||
//
|
||||
// Retrieve the capture from the r.URL.Query().Get(":name") in a handler (note
|
||||
// the colon). If a capture name appears more than once, the additional values
|
||||
// are appended to the previous values (see
|
||||
// http://golang.org/pkg/net/url/#Values)
|
||||
//
|
||||
// A trivial example server is:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "io"
|
||||
// "net/http"
|
||||
// "github.com/bmizerany/pat"
|
||||
// "log"
|
||||
// )
|
||||
//
|
||||
// // hello world, the web server
|
||||
// func HelloServer(w http.ResponseWriter, req *http.Request) {
|
||||
// io.WriteString(w, "hello, "+req.URL.Query().Get(":name")+"!\n")
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// m := pat.New()
|
||||
// m.Get("/hello/:name", http.HandlerFunc(HelloServer))
|
||||
//
|
||||
// // Register this pat with the default serve mux so that other packages
|
||||
// // may also be exported. (i.e. /debug/pprof/*)
|
||||
// http.Handle("/", m)
|
||||
// err := http.ListenAndServe(":12345", nil)
|
||||
// if err != nil {
|
||||
// log.Fatal("ListenAndServe: ", err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// When "Method Not Allowed":
|
||||
//
|
||||
// Pat knows what methods are allowed given a pattern and a URI. For
|
||||
// convenience, PatternServeMux will add the Allow header for requests that
|
||||
// match a pattern for a method other than the method requested and set the
|
||||
// Status to "405 Method Not Allowed".
|
||||
//
|
||||
// If the NotFound handler is set, then it is used whenever the pattern doesn't
|
||||
// match the request path for the current method (and the Allow header is not
|
||||
// altered).
|
||||
type PatternServeMux struct {
|
||||
// NotFound, if set, is used whenever the request doesn't match any
|
||||
// pattern for its method. NotFound should be set before serving any
|
||||
// requests.
|
||||
NotFound http.Handler
|
||||
handlers map[string][]*patHandler
|
||||
}
|
||||
|
||||
// New returns a new PatternServeMux.
|
||||
func New() *PatternServeMux {
|
||||
return &PatternServeMux{handlers: make(map[string][]*patHandler)}
|
||||
}
|
||||
|
||||
// ServeHTTP matches r.URL.Path against its routing table using the rules
|
||||
// described above.
|
||||
func (p *PatternServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
for _, ph := range p.handlers[r.Method] {
|
||||
if params, ok := ph.try(r.URL.EscapedPath()); ok {
|
||||
if len(params) > 0 && !ph.redirect {
|
||||
r.URL.RawQuery = url.Values(params).Encode() + "&" + r.URL.RawQuery
|
||||
}
|
||||
ph.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.NotFound != nil {
|
||||
p.NotFound.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
allowed := make([]string, 0, len(p.handlers))
|
||||
for meth, handlers := range p.handlers {
|
||||
if meth == r.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ph := range handlers {
|
||||
if _, ok := ph.try(r.URL.EscapedPath()); ok {
|
||||
allowed = append(allowed, meth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowed) == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Allow", strings.Join(allowed, ", "))
|
||||
http.Error(w, "Method Not Allowed", 405)
|
||||
}
|
||||
|
||||
// Head will register a pattern with a handler for HEAD requests.
|
||||
func (p *PatternServeMux) Head(pat string, h http.Handler) {
|
||||
p.Add("HEAD", pat, h)
|
||||
}
|
||||
|
||||
// Get will register a pattern with a handler for GET requests.
|
||||
// It also registers pat for HEAD requests. If this needs to be overridden, use
|
||||
// Head before Get with pat.
|
||||
func (p *PatternServeMux) Get(pat string, h http.Handler) {
|
||||
p.Add("HEAD", pat, h)
|
||||
p.Add("GET", pat, h)
|
||||
}
|
||||
|
||||
// Post will register a pattern with a handler for POST requests.
|
||||
func (p *PatternServeMux) Post(pat string, h http.Handler) {
|
||||
p.Add("POST", pat, h)
|
||||
}
|
||||
|
||||
// Put will register a pattern with a handler for PUT requests.
|
||||
func (p *PatternServeMux) Put(pat string, h http.Handler) {
|
||||
p.Add("PUT", pat, h)
|
||||
}
|
||||
|
||||
// Del will register a pattern with a handler for DELETE requests.
|
||||
func (p *PatternServeMux) Del(pat string, h http.Handler) {
|
||||
p.Add("DELETE", pat, h)
|
||||
}
|
||||
|
||||
// Options will register a pattern with a handler for OPTIONS requests.
|
||||
func (p *PatternServeMux) Options(pat string, h http.Handler) {
|
||||
p.Add("OPTIONS", pat, h)
|
||||
}
|
||||
|
||||
// Patch will register a pattern with a handler for PATCH requests.
|
||||
func (p *PatternServeMux) Patch(pat string, h http.Handler) {
|
||||
p.Add("PATCH", pat, h)
|
||||
}
|
||||
|
||||
// Add will register a pattern with a handler for meth requests.
|
||||
func (p *PatternServeMux) Add(meth, pat string, h http.Handler) {
|
||||
p.add(meth, pat, h, false)
|
||||
}
|
||||
|
||||
func (p *PatternServeMux) add(meth, pat string, h http.Handler, redirect bool) {
|
||||
handlers := p.handlers[meth]
|
||||
for _, p1 := range handlers {
|
||||
if p1.pat == pat {
|
||||
return // found existing pattern; do nothing
|
||||
}
|
||||
}
|
||||
handler := &patHandler{
|
||||
pat: pat,
|
||||
Handler: h,
|
||||
redirect: redirect,
|
||||
}
|
||||
p.handlers[meth] = append(handlers, handler)
|
||||
|
||||
n := len(pat)
|
||||
if n > 0 && pat[n-1] == '/' {
|
||||
p.add(meth, pat[:n-1], http.HandlerFunc(addSlashRedirect), true)
|
||||
}
|
||||
}
|
||||
|
||||
func addSlashRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
u := *r.URL
|
||||
u.Path += "/"
|
||||
http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
// Tail returns the trailing string in path after the final slash for a pat ending with a slash.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// Tail("/hello/:title/", "/hello/mr/mizerany") == "mizerany"
|
||||
// Tail("/:a/", "/x/y/z") == "y/z"
|
||||
//
|
||||
func Tail(pat, path string) string {
|
||||
var i, j int
|
||||
for i < len(path) {
|
||||
switch {
|
||||
case j >= len(pat):
|
||||
if pat[len(pat)-1] == '/' {
|
||||
return path[i:]
|
||||
}
|
||||
return ""
|
||||
case pat[j] == ':':
|
||||
var nextc byte
|
||||
_, nextc, j = match(pat, isAlnum, j+1)
|
||||
_, _, i = match(path, matchPart(nextc), i)
|
||||
case path[i] == pat[j]:
|
||||
i++
|
||||
j++
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type patHandler struct {
|
||||
pat string
|
||||
http.Handler
|
||||
redirect bool
|
||||
}
|
||||
|
||||
func (ph *patHandler) try(path string) (url.Values, bool) {
|
||||
p := make(url.Values)
|
||||
var i, j int
|
||||
for i < len(path) {
|
||||
switch {
|
||||
case j >= len(ph.pat):
|
||||
if ph.pat != "/" && len(ph.pat) > 0 && ph.pat[len(ph.pat)-1] == '/' {
|
||||
return p, true
|
||||
}
|
||||
return nil, false
|
||||
case ph.pat[j] == ':':
|
||||
var name, val string
|
||||
var nextc byte
|
||||
name, nextc, j = match(ph.pat, isAlnum, j+1)
|
||||
val, _, i = match(path, matchPart(nextc), i)
|
||||
escval, err := url.QueryUnescape(val)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
p.Add(":"+name, escval)
|
||||
case path[i] == ph.pat[j]:
|
||||
i++
|
||||
j++
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if j != len(ph.pat) {
|
||||
return nil, false
|
||||
}
|
||||
return p, true
|
||||
}
|
||||
|
||||
func matchPart(b byte) func(byte) bool {
|
||||
return func(c byte) bool {
|
||||
return c != b && c != '/'
|
||||
}
|
||||
}
|
||||
|
||||
func match(s string, f func(byte) bool, i int) (matched string, next byte, j int) {
|
||||
j = i
|
||||
for j < len(s) && f(s[j]) {
|
||||
j++
|
||||
}
|
||||
if j < len(s) {
|
||||
next = s[j]
|
||||
}
|
||||
return s[i:j], next, j
|
||||
}
|
||||
|
||||
func isAlpha(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func isAlnum(ch byte) bool {
|
||||
return isAlpha(ch) || isDigit(ch)
|
||||
}
|
||||
30
vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go
generated
vendored
30
vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/copy.go
generated
vendored
@@ -127,7 +127,7 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string)
|
||||
return
|
||||
}
|
||||
|
||||
cp := s.prepareCopy(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, src, false), spacelookup.MakeRelativeReference(dstSpace, dst, false), &sublog)
|
||||
cp := s.prepareCopy(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, src, false), spacelookup.MakeRelativeReference(dstSpace, dst, false), &sublog, dstSpace.GetRoot().GetStorageId() == utils.ShareStorageProviderID)
|
||||
if cp == nil {
|
||||
return
|
||||
}
|
||||
@@ -362,7 +362,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s
|
||||
return
|
||||
}
|
||||
|
||||
cp := s.prepareCopy(ctx, w, r, &srcRef, &dstRef, &sublog)
|
||||
cp := s.prepareCopy(ctx, w, r, &srcRef, &dstRef, &sublog, dstRef.GetResourceId().GetStorageId() == utils.ShareStorageProviderID)
|
||||
if cp == nil {
|
||||
return
|
||||
}
|
||||
@@ -552,7 +552,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, log *zerolog.Logger) *copy {
|
||||
func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, log *zerolog.Logger, destInShareJail bool) *copy {
|
||||
isChild, err := s.referenceIsChildOf(ctx, s.gatewaySelector, dstRef, srcRef)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
@@ -675,11 +675,6 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re
|
||||
if dstStatRes.Status.Code == rpc.Code_CODE_OK {
|
||||
successCode = http.StatusNoContent // 204 if target already existed, see https://tools.ietf.org/html/rfc4918#section-9.8.5
|
||||
|
||||
if utils.IsSpaceRoot(dstStatRes.GetInfo()) {
|
||||
log.Error().Msg("overwriting is not allowed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
if !overwrite {
|
||||
log.Warn().Bool("overwrite", overwrite).Msg("dst already exists")
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
@@ -688,10 +683,29 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re
|
||||
errors.HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5
|
||||
return nil
|
||||
}
|
||||
|
||||
if utils.IsSpaceRoot(dstStatRes.GetInfo()) {
|
||||
log.Error().Msg("overwriting is not allowed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete existing tree when overwriting a directory or replacing a file with a directory
|
||||
if dstStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER ||
|
||||
(dstStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_FILE &&
|
||||
srcStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER) {
|
||||
|
||||
// we must not allow to override mountpoints - so we check if we have access to the parent. If not this is a mountpoint
|
||||
if destInShareJail {
|
||||
dir, file := filepath.Split(dstRef.GetPath())
|
||||
if dir == "/" || dir == "" || file == "" {
|
||||
log.Error().Msg("must not overwrite mount points")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("must not overwrite mount points"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
delReq := &provider.DeleteRequest{Ref: dstRef}
|
||||
delRes, err := client.Delete(ctx, delReq)
|
||||
if err != nil {
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go
generated
vendored
@@ -42,7 +42,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/rs/zerolog"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
)
|
||||
|
||||
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/ocm/storage/received/upload.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/ocm/storage/received/upload.go
generated
vendored
@@ -35,7 +35,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
@@ -266,7 +266,7 @@ func (u *upload) GetInfo(ctx context.Context) (tusd.FileInfo, error) {
|
||||
return u.Info, nil
|
||||
}
|
||||
|
||||
func (u *upload) GetReader(ctx context.Context) (io.Reader, error) {
|
||||
func (u *upload) GetReader(ctx context.Context) (io.ReadCloser, error) {
|
||||
return os.Open(u.BinPath())
|
||||
}
|
||||
|
||||
|
||||
24
vendor/github.com/cs3org/reva/v2/pkg/rgrpc/keepalive.go
generated
vendored
Normal file
24
vendor/github.com/cs3org/reva/v2/pkg/rgrpc/keepalive.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package rgrpc
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_serverMaxConnectionAgeEnv = "GRPC_MAX_CONNECTION_AGE"
|
||||
|
||||
// same default as grpc
|
||||
infinity = time.Duration(math.MaxInt64)
|
||||
_defaultMaxConnectionAge = infinity
|
||||
)
|
||||
|
||||
// GetMaxConnectionAge returns the maximum grpc connection age.
|
||||
func GetMaxConnectionAge() time.Duration {
|
||||
d, err := time.ParseDuration(os.Getenv(_serverMaxConnectionAgeEnv))
|
||||
if err != nil {
|
||||
return _defaultMaxConnectionAge
|
||||
}
|
||||
return d
|
||||
}
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/rgrpc/rgrpc.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/rgrpc/rgrpc.go
generated
vendored
@@ -42,6 +42,7 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
@@ -245,6 +246,9 @@ func (s *Server) registerServices() error {
|
||||
if s.conf.TLSSettings.tlsConfig != nil {
|
||||
opts = append(opts, grpc.Creds(credentials.NewTLS(s.conf.TLSSettings.tlsConfig)))
|
||||
}
|
||||
opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{
|
||||
MaxConnectionAge: GetMaxConnectionAge(), // this forces clients to reconnect after 30 seconds, triggering a new DNS lookup to pick up new IPs
|
||||
}))
|
||||
|
||||
grpcServer := grpc.NewServer(opts...)
|
||||
|
||||
|
||||
36
vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/manager/tus/tus.go
generated
vendored
36
vendor/github.com/cs3org/reva/v2/pkg/rhttp/datatx/manager/tus/tus.go
generated
vendored
@@ -20,13 +20,13 @@ package tus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
"github.com/rs/zerolog"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/storage"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -99,7 +100,7 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
|
||||
config := tusd.Config{
|
||||
StoreComposer: composer,
|
||||
NotifyCompleteUploads: true,
|
||||
Logger: log.New(appctx.GetLogger(context.Background()), "", 0),
|
||||
Logger: slog.New(tusdLogger{log: appctx.GetLogger(context.Background())}), // Note: this is a noop logger
|
||||
}
|
||||
|
||||
if m.conf.CorsEnabled {
|
||||
@@ -222,3 +223,32 @@ func setHeaders(fs storage.FS, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.Header().Set(net.HeaderOCFileID, storagespace.FormatResourceID(resourceid))
|
||||
}
|
||||
|
||||
// tusdLogger is a logger implementation (slog) for tusd that uses zerolog.
|
||||
type tusdLogger struct {
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
// Handle handles the record
|
||||
func (l tusdLogger) Handle(_ context.Context, r slog.Record) error {
|
||||
switch r.Level {
|
||||
case slog.LevelDebug:
|
||||
l.log.Debug().Msg(r.Message)
|
||||
case slog.LevelInfo:
|
||||
l.log.Info().Msg(r.Message)
|
||||
case slog.LevelWarn:
|
||||
l.log.Warn().Msg(r.Message)
|
||||
case slog.LevelError:
|
||||
l.log.Error().Msg(r.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled returns true
|
||||
func (l tusdLogger) Enabled(_ context.Context, _ slog.Level) bool { return true }
|
||||
|
||||
// WithAttrs is not implemented
|
||||
func (l tusdLogger) WithAttrs(_ []slog.Attr) slog.Handler { return l }
|
||||
|
||||
// WithGroup is not implemented
|
||||
func (l tusdLogger) WithGroup(_ string) slog.Handler { return l }
|
||||
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/cephfs/upload.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/cephfs/upload.go
generated
vendored
@@ -39,7 +39,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
|
||||
func (fs *cephfs) Upload(ctx context.Context, req storage.UploadRequest, uff storage.UploadFinishedFunc) (*provider.ResourceInfo, error) {
|
||||
@@ -299,7 +299,7 @@ func (upload *fileUpload) GetInfo(ctx context.Context) (tusd.FileInfo, error) {
|
||||
}
|
||||
|
||||
// GetReader returns an io.Reader for the upload
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (file io.Reader, err error) {
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (file io.ReadCloser, err error) {
|
||||
user := upload.fs.makeUser(upload.ctx)
|
||||
user.op(func(cv *cacheVal) {
|
||||
file, err = cv.mount.Open(upload.binPath, os.O_RDONLY, 0)
|
||||
|
||||
241
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/hello/hello.go
generated
vendored
Normal file
241
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/hello/hello.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package hello
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/storage"
|
||||
"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("hello", New)
|
||||
}
|
||||
|
||||
type hellofs struct {
|
||||
bootTime time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
storageid = "hello-storage-id"
|
||||
spaceid = "hello-space-id"
|
||||
rootid = "hello-root-id"
|
||||
fileid = "hello-file-id"
|
||||
filename = "Hello world.txt"
|
||||
content = "Hello world!"
|
||||
)
|
||||
|
||||
func (fs *hellofs) space(withRoot bool) *provider.StorageSpace {
|
||||
s := &provider.StorageSpace{
|
||||
Id: &provider.StorageSpaceId{OpaqueId: spaceid},
|
||||
Root: &provider.ResourceId{
|
||||
StorageId: storageid,
|
||||
SpaceId: spaceid,
|
||||
OpaqueId: rootid,
|
||||
},
|
||||
Quota: &provider.Quota{
|
||||
QuotaMaxBytes: uint64(len(content)),
|
||||
QuotaMaxFiles: 1,
|
||||
},
|
||||
Name: "Hello Space",
|
||||
SpaceType: "project",
|
||||
RootInfo: fs.rootInfo(),
|
||||
Mtime: utils.TimeToTS(fs.bootTime),
|
||||
}
|
||||
// FIXME move this to the CS3 API
|
||||
s.Opaque = utils.AppendPlainToOpaque(s.Opaque, "spaceAlias", "project/hello")
|
||||
|
||||
if withRoot {
|
||||
s.RootInfo = fs.rootInfo()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (fs *hellofs) rootInfo() *provider.ResourceInfo {
|
||||
return &provider.ResourceInfo{
|
||||
Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
|
||||
Id: &provider.ResourceId{
|
||||
StorageId: storageid,
|
||||
SpaceId: spaceid,
|
||||
OpaqueId: rootid,
|
||||
},
|
||||
Etag: calcEtag(fs.bootTime, rootid),
|
||||
MimeType: "httpd/unix-directory",
|
||||
Mtime: utils.TimeToTS(fs.bootTime),
|
||||
Path: ".",
|
||||
PermissionSet: &provider.ResourcePermissions{
|
||||
GetPath: true,
|
||||
GetQuota: true,
|
||||
InitiateFileDownload: true,
|
||||
Stat: true,
|
||||
ListContainer: true,
|
||||
},
|
||||
Size: uint64(len(content)),
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *hellofs) fileInfo() *provider.ResourceInfo {
|
||||
return &provider.ResourceInfo{
|
||||
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
|
||||
Id: &provider.ResourceId{
|
||||
StorageId: storageid,
|
||||
SpaceId: spaceid,
|
||||
OpaqueId: fileid,
|
||||
},
|
||||
Etag: calcEtag(fs.bootTime, fileid),
|
||||
MimeType: "text/plain",
|
||||
Mtime: utils.TimeToTS(fs.bootTime),
|
||||
Path: ".",
|
||||
PermissionSet: &provider.ResourcePermissions{
|
||||
GetPath: true,
|
||||
GetQuota: true,
|
||||
InitiateFileDownload: true,
|
||||
Stat: true,
|
||||
ListContainer: true,
|
||||
},
|
||||
Size: uint64(len(content)),
|
||||
ParentId: &provider.ResourceId{
|
||||
StorageId: storageid,
|
||||
SpaceId: spaceid,
|
||||
OpaqueId: rootid,
|
||||
},
|
||||
Name: filename,
|
||||
Space: fs.space(false),
|
||||
}
|
||||
}
|
||||
|
||||
func calcEtag(t time.Time, nodeid string) string {
|
||||
h := md5.New()
|
||||
_ = binary.Write(h, binary.BigEndian, t.Unix())
|
||||
_ = binary.Write(h, binary.BigEndian, int64(t.Nanosecond()))
|
||||
_ = binary.Write(h, binary.BigEndian, []byte(nodeid))
|
||||
etag := fmt.Sprintf(`"%x"`, h.Sum(nil))
|
||||
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
|
||||
}
|
||||
|
||||
// New returns an implementation to of the storage.FS interface that talks to
|
||||
// a local filesystem with user homes disabled.
|
||||
func New(_ map[string]interface{}, _ events.Stream) (storage.FS, error) {
|
||||
return &hellofs{
|
||||
bootTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles
|
||||
func (fs *hellofs) Shutdown(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListStorageSpaces lists the spaces in the storage.
|
||||
func (fs *hellofs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
|
||||
return []*provider.StorageSpace{fs.space(true)}, nil
|
||||
}
|
||||
|
||||
// GetQuota returns the quota on the referenced resource
|
||||
func (fs *hellofs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
|
||||
return uint64(len(content)), uint64(len(content)), 0, nil
|
||||
}
|
||||
|
||||
func (fs *hellofs) lookup(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
if ref.GetResourceId().GetStorageId() != storageid || ref.GetResourceId().GetSpaceId() != spaceid {
|
||||
return nil, errtypes.NotFound("")
|
||||
}
|
||||
|
||||
// switch root or file
|
||||
switch ref.GetResourceId().GetOpaqueId() {
|
||||
case rootid:
|
||||
switch ref.GetPath() {
|
||||
case "", ".":
|
||||
return fs.rootInfo(), nil
|
||||
case filename:
|
||||
return fs.fileInfo(), nil
|
||||
default:
|
||||
return nil, errtypes.NotFound("unknown filename")
|
||||
}
|
||||
case fileid:
|
||||
return fs.fileInfo(), nil
|
||||
}
|
||||
|
||||
return nil, errtypes.NotFound("unknown id")
|
||||
}
|
||||
|
||||
// GetPathByID returns the path pointed by the file id
|
||||
func (fs *hellofs) GetPathByID(ctx context.Context, resID *provider.ResourceId) (string, error) {
|
||||
info, err := fs.lookup(ctx, &provider.Reference{ResourceId: resID})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return info.Path, nil
|
||||
}
|
||||
|
||||
// GetMD returns the resuorce info for the referenced resource
|
||||
func (fs *hellofs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
|
||||
return fs.lookup(ctx, ref)
|
||||
}
|
||||
|
||||
// ListFolder returns the resource infos for all children of the referenced resource
|
||||
func (fs *hellofs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
|
||||
info, err := fs.lookup(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER {
|
||||
return nil, errtypes.InternalError("expected a container")
|
||||
}
|
||||
if info.GetId().GetOpaqueId() != rootid {
|
||||
return nil, errtypes.InternalError("unknown folder")
|
||||
}
|
||||
|
||||
return []*provider.ResourceInfo{
|
||||
fs.fileInfo(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Download returns a ReadCloser for the content of the referenced resource
|
||||
func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
|
||||
info, err := fs.lookup(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
|
||||
return nil, errtypes.InternalError("expected a file")
|
||||
}
|
||||
if info.GetId().GetOpaqueId() != fileid {
|
||||
return nil, errtypes.InternalError("unknown file")
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
b.WriteString(content)
|
||||
return io.NopCloser(b), nil
|
||||
}
|
||||
198
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/hello/unimplemented.go
generated
vendored
Normal file
198
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/hello/unimplemented.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2018-2021 CERN
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// In applying this license, CERN does not waive the privileges and immunities
|
||||
// granted to it by virtue of its status as an Intergovernmental Organization
|
||||
// or submit itself to any jurisdiction.
|
||||
|
||||
package hello
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/storage"
|
||||
)
|
||||
|
||||
// hellofs is readonly so these remain unimplemented
|
||||
|
||||
// CreateReference creates a resource of type reference
|
||||
func (fs *hellofs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// CreateStorageSpace creates a storage space
|
||||
func (fs *hellofs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace")
|
||||
}
|
||||
|
||||
// UpdateStorageSpace updates a storage space
|
||||
func (fs *hellofs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
|
||||
return nil, errtypes.NotSupported("update storage space")
|
||||
}
|
||||
|
||||
// DeleteStorageSpace deletes a storage space
|
||||
func (fs *hellofs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error {
|
||||
return errtypes.NotSupported("delete storage space")
|
||||
}
|
||||
|
||||
// CreateDir creates a resource of type container
|
||||
func (fs *hellofs) CreateDir(ctx context.Context, ref *provider.Reference) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// TouchFile sets the mtime of a resource, creating an empty file if it does not exist
|
||||
// FIXME the markprocessing flag is an implementation detail of decomposedfs, remove it from the function
|
||||
// FIXME the mtime should either be a time.Time or a CS3 Timestamp, not a string
|
||||
func (fs *hellofs) TouchFile(ctx context.Context, ref *provider.Reference, _ bool, _ string) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// Delete deletes a resource.
|
||||
// If the storage driver supports a recycle bin it should moves it to the recycle bin
|
||||
func (fs *hellofs) Delete(ctx context.Context, ref *provider.Reference) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// Move changes the path of a resource
|
||||
func (fs *hellofs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// Upload creates or updates a resource of type file with a new revision
|
||||
func (fs *hellofs) Upload(ctx context.Context, req storage.UploadRequest, uff storage.UploadFinishedFunc) (*provider.ResourceInfo, error) {
|
||||
return nil, errtypes.NotSupported("hellofs: upload not supported")
|
||||
}
|
||||
|
||||
// InitiateUpload returns a list of protocols with urls that can be used to append bytes to a new upload session
|
||||
func (fs *hellofs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) {
|
||||
return nil, errtypes.NotSupported("hellofs: initiate upload not supported")
|
||||
}
|
||||
|
||||
// grants
|
||||
|
||||
// DenyGrant marks a resource as denied for a recipient
|
||||
// The resource and its children must be completely hidden for the recipient
|
||||
func (fs *hellofs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error {
|
||||
return errtypes.NotSupported("hellofs: deny grant not supported")
|
||||
}
|
||||
|
||||
// AddGrant adds a grant to a resource
|
||||
func (fs *hellofs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// ListGrants lists all grants on a resource
|
||||
func (fs *hellofs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// RemoveGrant removes a grant from a resource
|
||||
func (fs *hellofs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// UpdateGrant updates a grant on a resource
|
||||
func (fs *hellofs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// arbitrary metadata
|
||||
|
||||
// SetArbitraryMetadata sets arbitraty metadata on a resource
|
||||
func (fs *hellofs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// UnsetArbitraryMetadata removes arbitraty metadata from a resource
|
||||
func (fs *hellofs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// locks
|
||||
|
||||
// GetLock returns an existing lock on the given reference
|
||||
func (fs *hellofs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// SetLock puts a lock on the given reference
|
||||
func (fs *hellofs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// RefreshLock refreshes an existing lock on the given reference
|
||||
func (fs *hellofs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// Unlock removes an existing lock from the given reference
|
||||
func (fs *hellofs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// revisions
|
||||
|
||||
// ListRevisions lists all revisions for the referenced resource
|
||||
func (fs *hellofs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// DownloadRevision downloads a revision
|
||||
func (fs *hellofs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// RestoreRevision restores a revision
|
||||
func (fs *hellofs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// trash
|
||||
|
||||
// PurgeRecycleItem removes a resource from the recycle bin
|
||||
func (fs *hellofs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// EmptyRecycle removes all resource from the recycle bin
|
||||
func (fs *hellofs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// ListRecycle lists the content of the recycle bin
|
||||
func (fs *hellofs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) {
|
||||
return nil, errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// RestoreRecycleItem restores an item from the recyle bin
|
||||
// if restoreRef is nil the resource should be restored at the original path
|
||||
func (fs *hellofs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// CreateHome creates a users home
|
||||
// Deprecated: use CreateStorageSpace with type personal
|
||||
func (fs *hellofs) CreateHome(ctx context.Context) error {
|
||||
return errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
|
||||
// GetHome returns the path to the users home
|
||||
// Deprecated: use ListStorageSpaces with type personal
|
||||
func (fs *hellofs) GetHome(ctx context.Context) (string, error) {
|
||||
return "", errtypes.NotSupported("unimplemented")
|
||||
}
|
||||
1
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go
generated
vendored
1
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go
generated
vendored
@@ -26,6 +26,7 @@ import (
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpc"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpchome"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/eoshome"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/hello"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/local"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/localhome"
|
||||
_ "github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud"
|
||||
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql/upload.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql/upload.go
generated
vendored
@@ -44,7 +44,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
|
||||
var defaultFilePerm = os.FileMode(0664)
|
||||
@@ -388,7 +388,7 @@ func (upload *fileUpload) WriteChunk(ctx context.Context, offset int64, src io.R
|
||||
}
|
||||
|
||||
// GetReader returns an io.Reader for the upload
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (io.Reader, error) {
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (io.ReadCloser, error) {
|
||||
return os.Open(upload.binPath)
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go
generated
vendored
@@ -27,7 +27,7 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
|
||||
142
vendor/github.com/cs3org/reva/v2/pkg/storage/storage.go
generated
vendored
142
vendor/github.com/cs3org/reva/v2/pkg/storage/storage.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
|
||||
@@ -31,46 +31,118 @@ import (
|
||||
|
||||
// FS is the interface to implement access to the storage.
|
||||
type FS interface {
|
||||
GetHome(ctx context.Context) (string, error)
|
||||
CreateHome(ctx context.Context) error
|
||||
CreateDir(ctx context.Context, ref *provider.Reference) error
|
||||
TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error
|
||||
Delete(ctx context.Context, ref *provider.Reference) error
|
||||
Move(ctx context.Context, oldRef, newRef *provider.Reference) error
|
||||
GetMD(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) (*provider.ResourceInfo, error)
|
||||
ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error)
|
||||
InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error)
|
||||
Upload(ctx context.Context, req UploadRequest, uploadFunc UploadFinishedFunc) (*provider.ResourceInfo, error)
|
||||
Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
|
||||
ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
|
||||
DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
|
||||
RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
|
||||
ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error)
|
||||
RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error
|
||||
PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error
|
||||
EmptyRecycle(ctx context.Context, ref *provider.Reference) error
|
||||
GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error)
|
||||
AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error
|
||||
RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
|
||||
GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64 /*RemainingBytes*/, uint64, error)
|
||||
CreateReference(ctx context.Context, path string, targetURI *url.URL) error
|
||||
// Minimal set for a readonly storage driver
|
||||
|
||||
// Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles
|
||||
Shutdown(ctx context.Context) error
|
||||
SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
|
||||
UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
|
||||
SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error
|
||||
GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error)
|
||||
RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error
|
||||
Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error
|
||||
|
||||
// ListStorageSpaces lists the spaces in the storage.
|
||||
// The unrestricted parameter can be used to list other user's spaces when
|
||||
// the user has the necessary permissions.
|
||||
// FIXME The unrestricted parameter is an implementation detail of decomposedfs, remove it from the function?
|
||||
ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error)
|
||||
|
||||
// GetQuota returns the quota on the referenced resource
|
||||
GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64 /*RemainingBytes*/, uint64, error)
|
||||
|
||||
// GetMD returns the resuorce info for the referenced resource
|
||||
GetMD(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) (*provider.ResourceInfo, error)
|
||||
// ListFolder returns the resource infos for all children of the referenced resource
|
||||
ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error)
|
||||
// Download returns a ReadCloser for the content of the referenced resource
|
||||
Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
|
||||
|
||||
// GetPathByID returns the path for the given resource id relative to the space root
|
||||
// It should only reveal the path visible to the current user to not leak the names uf unshared parent resources
|
||||
// FIXME should be deprecated in favor of calls to GetMD and the fieldmask 'path'
|
||||
GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error)
|
||||
|
||||
// Functions for a writeable storage space
|
||||
|
||||
// CreateReference creates a resource of type reference
|
||||
CreateReference(ctx context.Context, path string, targetURI *url.URL) error
|
||||
// CreateDir creates a resource of type container
|
||||
CreateDir(ctx context.Context, ref *provider.Reference) error
|
||||
// TouchFile sets the mtime of a resource, creating an empty file if it does not exist
|
||||
// FIXME the markprocessing flag is an implementation detail of decomposedfs, remove it from the function
|
||||
// FIXME the mtime should either be a time.Time or a CS3 Timestamp, not a string
|
||||
TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error
|
||||
// Delete deletes a resource.
|
||||
// If the storage driver supports a recycle bin it should moves it to the recycle bin
|
||||
Delete(ctx context.Context, ref *provider.Reference) error
|
||||
// Move changes the path of a resource
|
||||
Move(ctx context.Context, oldRef, newRef *provider.Reference) error
|
||||
// InitiateUpload returns a list of protocols with urls that can be used to append bytes to a new upload session
|
||||
InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error)
|
||||
// Upload creates or updates a resource of type file with a new revision
|
||||
Upload(ctx context.Context, req UploadRequest, uploadFunc UploadFinishedFunc) (*provider.ResourceInfo, error)
|
||||
|
||||
// Revisions
|
||||
|
||||
// ListRevisions lists all revisions for the referenced resource
|
||||
ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
|
||||
// DownloadRevision downloads a revision
|
||||
DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
|
||||
// RestoreRevision restores a revision
|
||||
RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
|
||||
|
||||
// Recyce bin
|
||||
|
||||
// ListRecycle lists the content of the recycle bin
|
||||
ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error)
|
||||
// RestoreRecycleItem restores an item from the recyle bin
|
||||
// if restoreRef is nil the resource should be restored at the original path
|
||||
RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error
|
||||
// PurgeRecycleItem removes a resource from the recycle bin
|
||||
PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error
|
||||
// EmptyRecycle removes all resource from the recycle bin
|
||||
EmptyRecycle(ctx context.Context, ref *provider.Reference) error
|
||||
|
||||
// Grants
|
||||
|
||||
// AddGrant adds a grant to a resource
|
||||
AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
// DenyGrant marks a resource as denied for a recipient
|
||||
// The resource and its children must be completely hidden for the recipient
|
||||
DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error
|
||||
// RemoveGrant removes a grant from a resource
|
||||
RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
// UpdateGrant updates a grant on a resource
|
||||
UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
|
||||
// ListGrants lists all grants on a resource
|
||||
ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
|
||||
|
||||
// Arbitrary Metadata
|
||||
|
||||
// SetArbitraryMetadata sets arbitraty metadata on a resource
|
||||
SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
|
||||
// UnsetArbitraryMetadata removes arbitraty metadata from a resource
|
||||
UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
|
||||
|
||||
// Locks
|
||||
|
||||
// GetLock returns an existing lock on the given reference
|
||||
GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error)
|
||||
// SetLock puts a lock on the given reference
|
||||
SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error
|
||||
// RefreshLock refreshes an existing lock on the given reference
|
||||
RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error
|
||||
// Unlock removes an existing lock from the given reference
|
||||
Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error
|
||||
|
||||
// Spaces
|
||||
|
||||
// CreateStorageSpace creates a storage space
|
||||
CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error)
|
||||
// UpdateStorageSpace updates a storage space
|
||||
UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error)
|
||||
// DeleteStorageSpace deletes a storage space
|
||||
DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error
|
||||
|
||||
// CreateHome creates a users home
|
||||
// Deprecated: use CreateStorageSpace with type personal
|
||||
CreateHome(ctx context.Context) error
|
||||
// GetHome returns the path to the users home
|
||||
// Deprecated: use ListStorageSpaces with type personal
|
||||
GetHome(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// UnscopeFunc is a function that unscopes a user
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/uploads.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/uploads.go
generated
vendored
@@ -25,7 +25,7 @@ import (
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
|
||||
// UploadFinishedFunc is a callback function used in storage drivers to indicate that an upload has finished
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go
generated
vendored
@@ -59,7 +59,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
"github.com/pkg/errors"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload.go
generated
vendored
@@ -27,7 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/appctx"
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/session.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/session.go
generated
vendored
@@ -26,7 +26,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go
generated
vendored
@@ -44,7 +44,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
|
||||
var _idRegexp = regexp.MustCompile(".*/([^/]+).info")
|
||||
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/upload.go
generated
vendored
@@ -40,7 +40,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/pkg/errors"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -94,7 +94,7 @@ func (session *OcisSession) GetInfo(_ context.Context) (tusd.FileInfo, error) {
|
||||
}
|
||||
|
||||
// GetReader returns an io.Reader for the upload
|
||||
func (session *OcisSession) GetReader(ctx context.Context) (io.Reader, error) {
|
||||
func (session *OcisSession) GetReader(ctx context.Context) (io.ReadCloser, error) {
|
||||
_, span := tracer.Start(session.Context(ctx), "GetReader")
|
||||
defer span.End()
|
||||
return os.Open(session.binPath())
|
||||
|
||||
4
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/localfs/upload.go
generated
vendored
4
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/localfs/upload.go
generated
vendored
@@ -35,7 +35,7 @@ import (
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
)
|
||||
|
||||
var defaultFilePerm = os.FileMode(0664)
|
||||
@@ -296,7 +296,7 @@ func (upload *fileUpload) GetInfo(ctx context.Context) (tusd.FileInfo, error) {
|
||||
}
|
||||
|
||||
// GetReader returns an io.Reader for the upload
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (io.Reader, error) {
|
||||
func (upload *fileUpload) GetReader(ctx context.Context) (io.ReadCloser, error) {
|
||||
return os.Open(upload.binPath)
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/middleware/middleware.go
generated
vendored
2
vendor/github.com/cs3org/reva/v2/pkg/storage/utils/middleware/middleware.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
tusd "github.com/tus/tusd/pkg/handler"
|
||||
tusd "github.com/tus/tusd/v2/pkg/handler"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/storage"
|
||||
|
||||
53
vendor/github.com/tus/tusd/pkg/handler/body_reader.go
generated
vendored
53
vendor/github.com/tus/tusd/pkg/handler/body_reader.go
generated
vendored
@@ -1,53 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// bodyReader is an io.Reader, which is intended to wrap the request
|
||||
// body reader. If an error occurr during reading the request body, it
|
||||
// will not return this error to the reading entity, but instead store
|
||||
// the error and close the io.Reader, so that the error can be checked
|
||||
// afterwards. This is helpful, so that the stores do not have to handle
|
||||
// the error but this can instead be done in the handler.
|
||||
// In addition, the bodyReader keeps track of how many bytes were read.
|
||||
type bodyReader struct {
|
||||
reader io.Reader
|
||||
err error
|
||||
bytesCounter int64
|
||||
}
|
||||
|
||||
func newBodyReader(r io.Reader) *bodyReader {
|
||||
return &bodyReader{
|
||||
reader: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bodyReader) Read(b []byte) (int, error) {
|
||||
if r.err != nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n, err := r.reader.Read(b)
|
||||
atomic.AddInt64(&r.bytesCounter, int64(n))
|
||||
r.err = err
|
||||
|
||||
if err == io.EOF {
|
||||
return n, io.EOF
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r bodyReader) hasError() error {
|
||||
if r.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *bodyReader) bytesRead() int64 {
|
||||
return atomic.LoadInt64(&r.bytesCounter)
|
||||
}
|
||||
53
vendor/github.com/tus/tusd/pkg/handler/handler.go
generated
vendored
53
vendor/github.com/tus/tusd/pkg/handler/handler.go
generated
vendored
@@ -1,53 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bmizerany/pat"
|
||||
)
|
||||
|
||||
// Handler is a ready to use handler with routing (using pat)
|
||||
type Handler struct {
|
||||
*UnroutedHandler
|
||||
http.Handler
|
||||
}
|
||||
|
||||
// NewHandler creates a routed tus protocol handler. This is the simplest
|
||||
// way to use tusd but may not be as configurable as you require. If you are
|
||||
// integrating this into an existing app you may like to use tusd.NewUnroutedHandler
|
||||
// instead. Using tusd.NewUnroutedHandler allows the tus handlers to be combined into
|
||||
// your existing router (aka mux) directly. It also allows the GET and DELETE
|
||||
// endpoints to be customized. These are not part of the protocol so can be
|
||||
// changed depending on your needs.
|
||||
func NewHandler(config Config) (*Handler, error) {
|
||||
if err := config.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler, err := NewUnroutedHandler(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routedHandler := &Handler{
|
||||
UnroutedHandler: handler,
|
||||
}
|
||||
|
||||
mux := pat.New()
|
||||
|
||||
routedHandler.Handler = handler.Middleware(mux)
|
||||
|
||||
mux.Post("", http.HandlerFunc(handler.PostFile))
|
||||
mux.Head(":id", http.HandlerFunc(handler.HeadFile))
|
||||
mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile))
|
||||
if !config.DisableDownload {
|
||||
mux.Get(":id", http.HandlerFunc(handler.GetFile))
|
||||
}
|
||||
|
||||
// Only attach the DELETE handler if the Terminate() method is provided
|
||||
if config.StoreComposer.UsesTerminater && !config.DisableTermination {
|
||||
mux.Del(":id", http.HandlerFunc(handler.DelFile))
|
||||
}
|
||||
|
||||
return routedHandler, nil
|
||||
}
|
||||
27
vendor/github.com/tus/tusd/pkg/handler/log.go
generated
vendored
27
vendor/github.com/tus/tusd/pkg/handler/log.go
generated
vendored
@@ -1,27 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func (h *UnroutedHandler) log(eventName string, details ...string) {
|
||||
LogEvent(h.logger, eventName, details...)
|
||||
}
|
||||
|
||||
func LogEvent(logger *log.Logger, eventName string, details ...string) {
|
||||
result := make([]byte, 0, 100)
|
||||
|
||||
result = append(result, `event="`...)
|
||||
result = append(result, eventName...)
|
||||
result = append(result, `" `...)
|
||||
|
||||
for i := 0; i < len(details); i += 2 {
|
||||
result = append(result, details[i]...)
|
||||
result = append(result, `="`...)
|
||||
result = append(result, details[i+1]...)
|
||||
result = append(result, `" `...)
|
||||
}
|
||||
|
||||
result = append(result, "\n"...)
|
||||
logger.Output(2, string(result))
|
||||
}
|
||||
0
vendor/github.com/tus/tusd/LICENSE.txt → vendor/github.com/tus/tusd/v2/LICENSE.txt
generated
vendored
0
vendor/github.com/tus/tusd/LICENSE.txt → vendor/github.com/tus/tusd/v2/LICENSE.txt
generated
vendored
132
vendor/github.com/tus/tusd/v2/pkg/handler/body_reader.go
generated
vendored
Normal file
132
vendor/github.com/tus/tusd/v2/pkg/handler/body_reader.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// bodyReader is an io.Reader, which is intended to wrap the request
|
||||
// body reader. If an error occurr during reading the request body, it
|
||||
// will not return this error to the reading entity, but instead store
|
||||
// the error and close the io.Reader, so that the error can be checked
|
||||
// afterwards. This is helpful, so that the stores do not have to handle
|
||||
// the error but this can instead be done in the handler.
|
||||
// In addition, the bodyReader keeps track of how many bytes were read.
|
||||
type bodyReader struct {
|
||||
// bytesCounter is the first field to ensure that it's properly aligned,
|
||||
// otherwise we run into alignment issues on some 32-bit builds.
|
||||
// See https://github.com/tus/tusd/issues/1047
|
||||
// See https://pkg.go.dev/sync/atomic#pkg-note-BUG
|
||||
// TODO: In the future we should move all of these values to the safe
|
||||
// atomic.Uint64 type, which takes care of alignment automatically.
|
||||
bytesCounter int64
|
||||
ctx *httpContext
|
||||
reader io.ReadCloser
|
||||
err error
|
||||
onReadDone func()
|
||||
}
|
||||
|
||||
func newBodyReader(c *httpContext, maxSize int64) *bodyReader {
|
||||
return &bodyReader{
|
||||
ctx: c,
|
||||
reader: http.MaxBytesReader(c.res, c.req.Body, maxSize),
|
||||
onReadDone: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bodyReader) Read(b []byte) (int, error) {
|
||||
if r.err != nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n, err := r.reader.Read(b)
|
||||
atomic.AddInt64(&r.bytesCounter, int64(n))
|
||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
// If the timeout wasn't exceeded (due to SetReadDeadline), invoke
|
||||
// the callback so the deadline can be extended
|
||||
r.onReadDone()
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
// Note: if an error occurs while reading the body, we must set `r.err` (either in here
|
||||
// or somewhere else, such as in closeWithError). Otherwise, the PATCH handler might not know
|
||||
// that an error occurred and assumes that a request was transferred succesfully even though
|
||||
// it was interrupted. This leads to problems with the RUFH draft.
|
||||
|
||||
// io.EOF means that the request body was fully read and does not represent an error.
|
||||
if err == io.EOF {
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// http.ErrBodyReadAfterClose means that the bodyReader closed the request body because the upload is
|
||||
// is stopped or the server shuts down. In this case, the closeWithError method already
|
||||
// set `r.err` and thus we don't overerwrite it here but just return.
|
||||
if err == http.ErrBodyReadAfterClose {
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// All of the following errors can be understood as the input stream ending too soon:
|
||||
// - io.ErrClosedPipe is returned in the package's unit test with io.Pipe()
|
||||
// - io.UnexpectedEOF means that the client aborted the request.
|
||||
if err == io.ErrClosedPipe || err == io.ErrUnexpectedEOF {
|
||||
err = ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// Connection resets are not dropped silently, but responded to the client.
|
||||
// We change the error because otherwise the message would contain the local address,
|
||||
// which is unnecessary to be included in the response.
|
||||
if strings.HasSuffix(err.Error(), "read: connection reset by peer") {
|
||||
err = ErrConnectionReset
|
||||
}
|
||||
|
||||
// For timeouts, we also send a nicer response to the clients.
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
err = ErrReadTimeout
|
||||
}
|
||||
|
||||
// MaxBytesError is returned from http.MaxBytesReader, which we use to limit
|
||||
// the request body size.
|
||||
maxBytesErr := &http.MaxBytesError{}
|
||||
if errors.As(err, &maxBytesErr) {
|
||||
err = ErrSizeExceeded
|
||||
}
|
||||
|
||||
// Other errors are stored for retrival with hasError, but is not returned
|
||||
// to the consumer. We do not overwrite an error if it has been set already.
|
||||
if r.err == nil {
|
||||
r.err = err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r bodyReader) hasError() error {
|
||||
if r.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *bodyReader) bytesRead() int64 {
|
||||
return atomic.LoadInt64(&r.bytesCounter)
|
||||
}
|
||||
|
||||
func (r *bodyReader) closeWithError(err error) {
|
||||
r.err = err
|
||||
|
||||
// SetReadDeadline with the current time causes concurrent reads to the body to time out,
|
||||
// so the body will be closed sooner with less delay.
|
||||
if err := r.ctx.resC.SetReadDeadline(time.Now()); err != nil {
|
||||
r.ctx.log.Warn("NetworkTimeoutError", "error", err)
|
||||
}
|
||||
|
||||
r.reader.Close()
|
||||
}
|
||||
@@ -2,10 +2,11 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// Config provides a way to configure the Handler depending on your needs.
|
||||
@@ -13,7 +14,6 @@ type Config struct {
|
||||
// StoreComposer points to the store composer from which the core data store
|
||||
// and optional dependencies should be taken. May only be nil if DataStore is
|
||||
// set.
|
||||
// TODO: Remove pointer?
|
||||
StoreComposer *StoreComposer
|
||||
// MaxSize defines how many bytes may be stored in one single upload. If its
|
||||
// value is is 0 or smaller no limit will be enforced.
|
||||
@@ -33,12 +33,6 @@ type Config struct {
|
||||
// DisableTermination indicates whether the server will refuse termination
|
||||
// requests of the uploaded file, by not mounting the DELETE handler.
|
||||
DisableTermination bool
|
||||
// Disable cors headers. If set to true, tusd will not send any CORS related header.
|
||||
// This is useful if you have a proxy sitting in front of tusd that handles CORS.
|
||||
//
|
||||
// Deprecated: All CORS-related settings are available in via the Cors field. Use
|
||||
// Cors.Disable instead of DisableCors.
|
||||
DisableCors bool
|
||||
// Cors can be used to customize the handling of Cross-Origin Resource Sharing (CORS).
|
||||
// See the CorsConfig struct for more details.
|
||||
// Defaults to DefaultCorsConfig.
|
||||
@@ -55,21 +49,53 @@ type Config struct {
|
||||
// NotifyCreatedUploads indicates whether sending notifications about
|
||||
// the upload having been created using the CreatedUploads channel should be enabled.
|
||||
NotifyCreatedUploads bool
|
||||
// UploadProgressInterval specifies the interval at which the upload progress
|
||||
// notifications are sent to the UploadProgress channel, if enabled.
|
||||
// Defaults to 1s.
|
||||
UploadProgressInterval time.Duration
|
||||
// Logger is the logger to use internally, mostly for printing requests.
|
||||
Logger *log.Logger
|
||||
Logger *slog.Logger
|
||||
// Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers
|
||||
// potentially set by proxies when generating an absolute URL in the
|
||||
// response to POST requests.
|
||||
RespectForwardedHeaders bool
|
||||
// PreUploadCreateCallback will be invoked before a new upload is created, if the
|
||||
// property is supplied. If the callback returns nil, the upload will be created.
|
||||
// Otherwise the HTTP request will be aborted. This can be used to implement
|
||||
// validation of upload metadata etc.
|
||||
PreUploadCreateCallback func(hook HookEvent) error
|
||||
// property is supplied. If the callback returns no error, the upload will be created
|
||||
// and optional values from HTTPResponse will be contained in the HTTP response.
|
||||
// If the error is non-nil, the upload will not be created. This can be used to implement
|
||||
// validation of upload metadata etc. Furthermore, HTTPResponse will be ignored and
|
||||
// the error value can contain values for the HTTP response.
|
||||
// If the error is nil, FileInfoChanges can be filled out to specify individual properties
|
||||
// that should be overwriten before the upload is create. See its type definition for
|
||||
// more details on its behavior. If you do not want to make any changes, return an empty struct.
|
||||
PreUploadCreateCallback func(hook HookEvent) (HTTPResponse, FileInfoChanges, error)
|
||||
// PreFinishResponseCallback will be invoked after an upload is completed but before
|
||||
// a response is returned to the client. Error responses from the callback will be passed
|
||||
// back to the client. This can be used to implement post-processing validation.
|
||||
PreFinishResponseCallback func(hook HookEvent) error
|
||||
// a response is returned to the client. This can be used to implement post-processing validation.
|
||||
// If the callback returns no error, optional values from HTTPResponse will be contained in the HTTP response.
|
||||
// If the error is non-nil, the error will be forwarded to the client. Furthermore,
|
||||
// HTTPResponse will be ignored and the error value can contain values for the HTTP response.
|
||||
PreFinishResponseCallback func(hook HookEvent) (HTTPResponse, error)
|
||||
// GracefulRequestCompletionTimeout is the timeout for operations to complete after an HTTP
|
||||
// request has ended (successfully or by error). For example, if an HTTP request is interrupted,
|
||||
// instead of stopping immediately, the handler and data store will be given some additional
|
||||
// time to wrap up their operations and save any uploaded data. GracefulRequestCompletionTimeout
|
||||
// controls this time.
|
||||
// See HookEvent.Context for more details.
|
||||
// Defaults to 10s.
|
||||
GracefulRequestCompletionTimeout time.Duration
|
||||
// AcquireLockTimeout is the duration that a request handler will wait to acquire a lock for
|
||||
// an upload. If the timeout is reached, it will stop waiting and send an error response to the
|
||||
// client.
|
||||
// Defaults to 20s.
|
||||
AcquireLockTimeout time.Duration
|
||||
// NetworkTimeout is the timeout for individual read operations on the request body. If the
|
||||
// read operation succeeds in this time window, the handler will continue consuming the body.
|
||||
// If a read operation times out, the handler will stop reading and close the request.
|
||||
// This ensures that an upload is consumed while data is being transmitted, while also closing
|
||||
// dead connections.
|
||||
// Under the hood, this is passed to ResponseController.SetReadDeadline
|
||||
// Defaults to 60s
|
||||
NetworkTimeout time.Duration
|
||||
}
|
||||
|
||||
// CorsConfig provides a way to customize the the handling of Cross-Origin Resource Sharing (CORS).
|
||||
@@ -109,14 +135,14 @@ var DefaultCorsConfig = CorsConfig{
|
||||
AllowOrigin: regexp.MustCompile(".*"),
|
||||
AllowCredentials: false,
|
||||
AllowMethods: "POST, HEAD, PATCH, OPTIONS, GET, DELETE",
|
||||
AllowHeaders: "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version",
|
||||
AllowHeaders: "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Complete, Upload-Draft-Interop-Version",
|
||||
MaxAge: "86400",
|
||||
ExposeHeaders: "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version",
|
||||
ExposeHeaders: "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Complete, Upload-Draft-Interop-Version",
|
||||
}
|
||||
|
||||
func (config *Config) validate() error {
|
||||
if config.Logger == nil {
|
||||
config.Logger = log.New(os.Stdout, "[tusd] ", log.Ldate|log.Lmicroseconds)
|
||||
config.Logger = slog.Default()
|
||||
}
|
||||
|
||||
base := config.BasePath
|
||||
@@ -145,14 +171,25 @@ func (config *Config) validate() error {
|
||||
return errors.New("tusd: StoreComposer in Config needs to contain a non-nil core")
|
||||
}
|
||||
|
||||
if config.UploadProgressInterval <= 0 {
|
||||
config.UploadProgressInterval = 1 * time.Second
|
||||
}
|
||||
|
||||
if config.GracefulRequestCompletionTimeout <= 0 {
|
||||
config.GracefulRequestCompletionTimeout = 10 * time.Second
|
||||
}
|
||||
|
||||
if config.AcquireLockTimeout <= 0 {
|
||||
config.AcquireLockTimeout = 20 * time.Second
|
||||
}
|
||||
|
||||
if config.NetworkTimeout <= 0 {
|
||||
config.NetworkTimeout = 60 * time.Second
|
||||
}
|
||||
|
||||
if config.Cors == nil {
|
||||
config.Cors = &DefaultCorsConfig
|
||||
}
|
||||
|
||||
// Support previous settings for disabling CORS.
|
||||
if config.DisableCors {
|
||||
config.Cors.Disable = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
102
vendor/github.com/tus/tusd/v2/pkg/handler/context.go
generated
vendored
Normal file
102
vendor/github.com/tus/tusd/v2/pkg/handler/context.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// httpContext is wrapper around context.Context that also carries the
|
||||
// corresponding HTTP request and response writer, as well as an
|
||||
// optional body reader
|
||||
type httpContext struct {
|
||||
context.Context
|
||||
|
||||
// res and req are the native request and response instances
|
||||
res http.ResponseWriter
|
||||
resC *http.ResponseController
|
||||
req *http.Request
|
||||
|
||||
// body is nil by default and set by the user if the request body is consumed.
|
||||
body *bodyReader
|
||||
|
||||
// cancel allows a user to cancel the internal request context, causing
|
||||
// the request body to be closed.
|
||||
cancel context.CancelCauseFunc
|
||||
|
||||
// log is the logger for this request. It gets extended with more properties as the
|
||||
// request progresses and is identified.
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// newContext constructs a new httpContext for the given request. This should only be done once
|
||||
// per request and the context should be stored in the request, so it can be fetched with getContext.
|
||||
func (h UnroutedHandler) newContext(w http.ResponseWriter, r *http.Request) *httpContext {
|
||||
// requestCtx is the context from the native request instance. It gets cancelled
|
||||
// if the connection closes, the request is cancelled (HTTP/2), ServeHTTP returns
|
||||
// or the server's base context is cancelled.
|
||||
requestCtx := r.Context()
|
||||
// On top of requestCtx, we construct a context that we can cancel, for example when
|
||||
// the post-receive hook stops an upload or if another uploads requests a lock to be released.
|
||||
cancellableCtx, cancelHandling := context.WithCancelCause(requestCtx)
|
||||
// On top of cancellableCtx, we construct a new context which gets cancelled with a delay.
|
||||
// See HookEvent.Context for more details, but the gist is that we want to give data stores
|
||||
// some more time to finish their buisness.
|
||||
delayedCtx := newDelayedContext(cancellableCtx, h.config.GracefulRequestCompletionTimeout)
|
||||
|
||||
ctx := &httpContext{
|
||||
Context: delayedCtx,
|
||||
res: w,
|
||||
resC: http.NewResponseController(w),
|
||||
req: r,
|
||||
body: nil, // body can be filled later for PATCH requests
|
||||
cancel: cancelHandling,
|
||||
log: h.logger.With("method", r.Method, "path", r.URL.Path, "requestId", getRequestId(r)),
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-cancellableCtx.Done()
|
||||
|
||||
// If the cause is one of our own errors, close a potential body and relay the error.
|
||||
cause := context.Cause(cancellableCtx)
|
||||
if (errors.Is(cause, ErrServerShutdown) || errors.Is(cause, ErrUploadInterrupted) || errors.Is(cause, ErrUploadStoppedByServer)) && ctx.body != nil {
|
||||
ctx.body.closeWithError(cause)
|
||||
}
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// getContext tries to retrieve a httpContext from the request or constructs a new one.
|
||||
func (h UnroutedHandler) getContext(w http.ResponseWriter, r *http.Request) *httpContext {
|
||||
c, ok := r.Context().(*httpContext)
|
||||
if !ok {
|
||||
c = h.newContext(w, r)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c httpContext) Value(key any) any {
|
||||
// We overwrite the Value function to ensure that the values from the request
|
||||
// context are returned because c.Context does not contain any values.
|
||||
return c.req.Context().Value(key)
|
||||
}
|
||||
|
||||
// newDelayedContext returns a context that is cancelled with a delay. If the parent context
|
||||
// is done, the new context will also be cancelled but only after waiting the specified delay.
|
||||
// Note: The parent context MUST be cancelled or otherwise this will leak resources. In the
|
||||
// case of http.Request.Context, the net/http package ensures that the context is always cancelled.
|
||||
func newDelayedContext(parent context.Context, delay time.Duration) context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
<-parent.Done()
|
||||
<-time.After(delay)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
|
||||
type MetaData map[string]string
|
||||
|
||||
// FileInfo contains information about a single upload resource.
|
||||
type FileInfo struct {
|
||||
// ID is the unique identifier of the upload resource.
|
||||
ID string
|
||||
// Total file size in bytes specified in the NewUpload call
|
||||
Size int64
|
||||
@@ -31,53 +33,79 @@ type FileInfo struct {
|
||||
// store is used. This map may also be nil.
|
||||
Storage map[string]string
|
||||
|
||||
// stopUpload is the cancel function for the upload's context.Context. When
|
||||
// invoked it will interrupt the writes to DataStore#WriteChunk.
|
||||
stopUpload context.CancelFunc
|
||||
// stopUpload is a callback for communicating that an upload should by stopped
|
||||
// and interrupt the writes to DataStore#WriteChunk.
|
||||
stopUpload func(HTTPResponse)
|
||||
}
|
||||
|
||||
// StopUpload interrupts an running upload from the server-side. This means that
|
||||
// StopUpload interrupts a running upload from the server-side. This means that
|
||||
// the current request body is closed, so that the data store does not get any
|
||||
// more data. Furthermore, a response is sent to notify the client of the
|
||||
// interrupting and the upload is terminated (if supported by the data store),
|
||||
// so the upload cannot be resumed anymore.
|
||||
func (f FileInfo) StopUpload() {
|
||||
// so the upload cannot be resumed anymore. The response to the client can be
|
||||
// optionally modified by providing values in the HTTPResponse struct.
|
||||
func (f FileInfo) StopUpload(response HTTPResponse) {
|
||||
if f.stopUpload != nil {
|
||||
f.stopUpload()
|
||||
f.stopUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
// FileInfoChanges collects changes the should be made to a FileInfo struct. This
|
||||
// can be done using the PreUploadCreateCallback to modify certain properties before
|
||||
// an upload is created. Properties which should not be modified (e.g. Size or Offset)
|
||||
// are intentionally left out here.
|
||||
type FileInfoChanges struct {
|
||||
// If ID is not empty, it will be passed to the data store, allowing
|
||||
// hooks to influence the upload ID. Be aware that a data store is not required to
|
||||
// respect a pre-defined upload ID and might overwrite or modify it. However,
|
||||
// all data stores in the github.com/tus/tusd package do respect pre-defined IDs.
|
||||
ID string
|
||||
|
||||
// If MetaData is not nil, it replaces the entire user-defined meta data from
|
||||
// the upload creation request. You can add custom meta data fields this way
|
||||
// or ensure that only certain fields from the user-defined meta data are saved.
|
||||
// If you want to retain only specific entries from the user-defined meta data, you must
|
||||
// manually copy them into this MetaData field.
|
||||
// If you do not want to store any meta data, set this field to an empty map (`MetaData{}`).
|
||||
// If you want to keep the entire user-defined meta data, set this field to nil.
|
||||
MetaData MetaData
|
||||
|
||||
// If Storage is not nil, it is passed to the data store to allow for minor adjustments
|
||||
// to the upload storage (e.g. destination file name). The details are specific for each
|
||||
// data store and should be looked up in their respective documentation.
|
||||
// Please be aware that this behavior is currently not supported by any data store in
|
||||
// the github.com/tus/tusd package.
|
||||
Storage map[string]string
|
||||
}
|
||||
|
||||
type Upload interface {
|
||||
// Write the chunk read from src into the file specified by the id at the
|
||||
// given offset. The handler will take care of validating the offset and
|
||||
// limiting the size of the src to not overflow the file's size. It may
|
||||
// return an os.ErrNotExist which will be interpreted as a 404 Not Found.
|
||||
// It will also lock resources while they are written to ensure only one
|
||||
// limiting the size of the src to not overflow the file's size.
|
||||
// The handler will also lock resources while they are written to ensure only one
|
||||
// write happens per time.
|
||||
// The function call must return the number of bytes written.
|
||||
WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error)
|
||||
// Read the fileinformation used to validate the offset and respond to HEAD
|
||||
// requests. It may return an os.ErrNotExist which will be interpreted as a
|
||||
// 404 Not Found.
|
||||
// requests.
|
||||
GetInfo(ctx context.Context) (FileInfo, error)
|
||||
// GetReader returns a reader which allows iterating of the content of an
|
||||
// upload specified by its ID. It should attempt to provide a reader even if
|
||||
// the upload has not been finished yet but it's not required.
|
||||
// If the returned reader also implements the io.Closer interface, the
|
||||
// Close() method will be invoked once everything has been read.
|
||||
// If the given upload could not be found, the error tusd.ErrNotFound should
|
||||
// be returned.
|
||||
GetReader(ctx context.Context) (io.Reader, error)
|
||||
// GetReader returns an io.ReadCloser which allows iterating of the content of an
|
||||
// upload. It should attempt to provide a reader even if the upload has not
|
||||
// been finished yet but it's not required.
|
||||
GetReader(ctx context.Context) (io.ReadCloser, error)
|
||||
// FinisherDataStore is the interface which can be implemented by DataStores
|
||||
// which need to do additional operations once an entire upload has been
|
||||
// completed. These tasks may include but are not limited to freeing unused
|
||||
// resources or notifying other services. For example, S3Store uses this
|
||||
// interface for removing a temporary object.
|
||||
// FinishUpload executes additional operations for the finished upload which
|
||||
// is specified by its ID.
|
||||
FinishUpload(ctx context.Context) error
|
||||
}
|
||||
|
||||
// DataStore is the base interface for storages to implement. It provides functions
|
||||
// to create new uploads and fetch existing ones.
|
||||
//
|
||||
// Note: the context values passed to all functions is not the request's context,
|
||||
// but a similar context. See HookEvent.Context for more details.
|
||||
type DataStore interface {
|
||||
// Create a new upload using the size as the file's length. The method must
|
||||
// return an unique id which is used to identify the upload. If no backend
|
||||
@@ -85,12 +113,14 @@ type DataStore interface {
|
||||
// generate one. The properties Size and MetaData will be filled.
|
||||
NewUpload(ctx context.Context, info FileInfo) (upload Upload, err error)
|
||||
|
||||
// GetUpload fetches the upload with a given ID. If no such upload can be found,
|
||||
// ErrNotFound must be returned.
|
||||
GetUpload(ctx context.Context, id string) (upload Upload, err error)
|
||||
}
|
||||
|
||||
type TerminatableUpload interface {
|
||||
// Terminate an upload so any further requests to the resource, both reading
|
||||
// and writing, must return os.ErrNotExist or similar.
|
||||
// Terminate an upload so any further requests to the upload resource will
|
||||
// return the ErrNotFound error.
|
||||
Terminate(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -146,11 +176,15 @@ type Locker interface {
|
||||
type Lock interface {
|
||||
// Lock attempts to obtain an exclusive lock for the upload specified
|
||||
// by its id.
|
||||
// If this operation fails because the resource is already locked, the
|
||||
// tusd.ErrFileLocked must be returned. If no error is returned, the attempt
|
||||
// is consider to be successful and the upload to be locked until UnlockUpload
|
||||
// is invoked for the same upload.
|
||||
Lock() error
|
||||
// If the lock can be acquired, it will return without error. The requestUnlock
|
||||
// callback is invoked when another caller attempts to create a lock. In this
|
||||
// case, the holder of the lock should attempt to release the lock as soon
|
||||
// as possible
|
||||
// If the lock is already held, the holder's requestUnlock function will be
|
||||
// invoked to request the lock to be released. If the context is cancelled before
|
||||
// the lock can be acquired, ErrLockTimeout will be returned without acquiring
|
||||
// the lock.
|
||||
Lock(ctx context.Context, requestUnlock func()) error
|
||||
// Unlock releases an existing lock for the given upload.
|
||||
Unlock() error
|
||||
}
|
||||
@@ -7,7 +7,7 @@ re-uploading the previous data again. An interruption may happen willingly, if
|
||||
the user wants to pause, or by accident in case of an network issue or server
|
||||
outage (http://tus.io).
|
||||
|
||||
The basics of tusd
|
||||
# The basics of tusd
|
||||
|
||||
tusd was designed in way which allows an flexible and customizable usage. We
|
||||
wanted to avoid binding this package to a specific storage system – particularly
|
||||
@@ -22,7 +22,7 @@ current state. Therefore it is the only part of the system which communicates
|
||||
directly with the underlying storage system, whether it be the local disk, a
|
||||
remote FTP server or cloud providers such as AWS S3.
|
||||
|
||||
Using a store composer
|
||||
# Using a store composer
|
||||
|
||||
The only hard requirements for a data store can be found in the DataStore
|
||||
interface. It contains methods for creating uploads (NewUpload), writing to
|
||||
@@ -35,10 +35,10 @@ TerminaterDataStore which allows uploads to be terminated.
|
||||
The store composer offers a way to combine the basic data store - the core -
|
||||
implementation and these additional extensions:
|
||||
|
||||
composer := tusd.NewStoreComposer()
|
||||
composer.UseCore(dataStore) // Implements DataStore
|
||||
composer.UseTerminater(terminater) // Implements TerminaterDataStore
|
||||
composer.UseLocker(locker) // Implements LockerDataStore
|
||||
composer := tusd.NewStoreComposer()
|
||||
composer.UseCore(dataStore) // Implements DataStore
|
||||
composer.UseTerminater(terminater) // Implements TerminaterDataStore
|
||||
composer.UseLocker(locker) // Implements LockerDataStore
|
||||
|
||||
The corresponding methods for adding an extension to the composer are prefixed
|
||||
with Use* followed by the name of the corresponding interface. However, most
|
||||
@@ -47,23 +47,23 @@ tedious and error-prone. Therefore, all data store distributed with tusd provide
|
||||
an UseIn() method which does this job automatically. For example, this is the
|
||||
S3 store in action (see S3Store.UseIn):
|
||||
|
||||
store := s3store.New(…)
|
||||
locker := memorylocker.New()
|
||||
composer := tusd.NewStoreComposer()
|
||||
store.UseIn(composer)
|
||||
locker.UseIn(composer)
|
||||
store := s3store.New(…)
|
||||
locker := memorylocker.New()
|
||||
composer := tusd.NewStoreComposer()
|
||||
store.UseIn(composer)
|
||||
locker.UseIn(composer)
|
||||
|
||||
Finally, once you are done with composing your data store, you can pass it
|
||||
inside the Config struct in order to create create a new tusd HTTP handler:
|
||||
|
||||
config := tusd.Config{
|
||||
StoreComposer: composer,
|
||||
BasePath: "/files/",
|
||||
}
|
||||
handler, err := tusd.NewHandler(config)
|
||||
config := tusd.Config{
|
||||
StoreComposer: composer,
|
||||
BasePath: "/files/",
|
||||
}
|
||||
handler, err := tusd.NewHandler(config)
|
||||
|
||||
This handler can then be mounted to a specific path, e.g. /files:
|
||||
|
||||
http.Handle("/files/", http.StripPrefix("/files/", handler))
|
||||
http.Handle("/files/", http.StripPrefix("/files/", handler))
|
||||
*/
|
||||
package handler
|
||||
37
vendor/github.com/tus/tusd/v2/pkg/handler/error.go
generated
vendored
Normal file
37
vendor/github.com/tus/tusd/v2/pkg/handler/error.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package handler
|
||||
|
||||
// Error represents an error with the intent to be sent in the HTTP
|
||||
// response to the client. Therefore, it also contains a HTTPResponse,
|
||||
// next to an error code and error message.
|
||||
type Error struct {
|
||||
ErrorCode string
|
||||
Message string
|
||||
HTTPResponse HTTPResponse
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.ErrorCode + ": " + e.Message
|
||||
}
|
||||
|
||||
func (e1 Error) Is(target error) bool {
|
||||
e2, ok := target.(Error)
|
||||
return ok && e1.ErrorCode == e2.ErrorCode
|
||||
}
|
||||
|
||||
// NewError constructs a new Error object with the given error code and message.
|
||||
// The corresponding HTTP response will have the provided status code
|
||||
// and a body consisting of the error details.
|
||||
// responses. See the net/http package for standardized status codes.
|
||||
func NewError(errCode string, message string, statusCode int) Error {
|
||||
return Error{
|
||||
ErrorCode: errCode,
|
||||
Message: message,
|
||||
HTTPResponse: HTTPResponse{
|
||||
StatusCode: statusCode,
|
||||
Body: errCode + ": " + message + "\n",
|
||||
Header: HTTPHeader{
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
77
vendor/github.com/tus/tusd/v2/pkg/handler/handler.go
generated
vendored
Normal file
77
vendor/github.com/tus/tusd/v2/pkg/handler/handler.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Handler is a ready to use handler with routing
|
||||
type Handler struct {
|
||||
*UnroutedHandler
|
||||
http.Handler
|
||||
}
|
||||
|
||||
// NewHandler creates a routed tus protocol handler. This is the simplest
|
||||
// way to use tusd but may not be as configurable as you require. If you are
|
||||
// integrating this into an existing app you may like to use tusd.NewUnroutedHandler
|
||||
// instead. Using tusd.NewUnroutedHandler allows the tus handlers to be combined into
|
||||
// your existing router (aka mux) directly. It also allows the GET and DELETE
|
||||
// endpoints to be customized. These are not part of the protocol so can be
|
||||
// changed depending on your needs.
|
||||
func NewHandler(config Config) (*Handler, error) {
|
||||
if err := config.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler, err := NewUnroutedHandler(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routedHandler := &Handler{
|
||||
UnroutedHandler: handler,
|
||||
}
|
||||
|
||||
mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
method := r.Method
|
||||
path := strings.Trim(r.URL.Path, "/")
|
||||
|
||||
switch path {
|
||||
case "":
|
||||
// Root endpoint for upload creation
|
||||
switch method {
|
||||
case "POST":
|
||||
handler.PostFile(w, r)
|
||||
default:
|
||||
w.Header().Add("Allow", "POST")
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.Write([]byte(`method not allowed`))
|
||||
}
|
||||
default:
|
||||
// URL points to an upload resource
|
||||
switch {
|
||||
case method == "HEAD" && r.URL.Path != "":
|
||||
// Offset retrieval
|
||||
handler.HeadFile(w, r)
|
||||
case method == "PATCH" && r.URL.Path != "":
|
||||
// Upload apppending
|
||||
handler.PatchFile(w, r)
|
||||
case method == "GET" && r.URL.Path != "" && !config.DisableDownload:
|
||||
// Upload download
|
||||
handler.GetFile(w, r)
|
||||
case method == "DELETE" && r.URL.Path != "" && config.StoreComposer.UsesTerminater && !config.DisableTermination:
|
||||
// Upload termination
|
||||
handler.DelFile(w, r)
|
||||
default:
|
||||
// TODO: Only add GET and DELETE if they are supported
|
||||
w.Header().Add("Allow", "GET, HEAD, PATCH, DELETE")
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.Write([]byte(`method not allowed`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
routedHandler.Handler = handler.Middleware(mux)
|
||||
|
||||
return routedHandler, nil
|
||||
}
|
||||
48
vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go
generated
vendored
Normal file
48
vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// HookEvent represents an event from tusd which can be handled by the application.
|
||||
type HookEvent struct {
|
||||
// Context provides access to the context from the HTTP request. This context is
|
||||
// not the exact value as the request context from http.Request.Context() but
|
||||
// a similar context that retains the same values as the request context. In
|
||||
// addition, Context will be cancelled after a short delay when the request context
|
||||
// is done. This delay is controlled by Config.GracefulRequestCompletionTimeout.
|
||||
//
|
||||
// The reason is that we want stores to be able to continue processing a request after
|
||||
// its context has been cancelled. For example, assume a PATCH request is incoming. If
|
||||
// the end-user pauses the upload, the connection is closed causing the request context
|
||||
// to be cancelled immediately. However, we want the store to be able to save the last
|
||||
// few bytes that were transmitted before the request was aborted. To allow this, we
|
||||
// copy the request context but cancel it with a brief delay to give the data store
|
||||
// time to finish its operations.
|
||||
Context context.Context `json:"-"`
|
||||
// Upload contains information about the upload that caused this hook
|
||||
// to be fired.
|
||||
Upload FileInfo
|
||||
// HTTPRequest contains details about the HTTP request that reached
|
||||
// tusd.
|
||||
HTTPRequest HTTPRequest
|
||||
}
|
||||
|
||||
func newHookEvent(c *httpContext, info FileInfo) HookEvent {
|
||||
// The Host header field is not present in the header map, see https://pkg.go.dev/net/http#Request:
|
||||
// > For incoming requests, the Host header is promoted to the
|
||||
// > Request.Host field and removed from the Header map.
|
||||
// That's why we add it back manually.
|
||||
c.req.Header.Set("Host", c.req.Host)
|
||||
|
||||
return HookEvent{
|
||||
Context: c,
|
||||
Upload: info,
|
||||
HTTPRequest: HTTPRequest{
|
||||
Method: c.req.Method,
|
||||
URI: c.req.RequestURI,
|
||||
RemoteAddr: c.req.RemoteAddr,
|
||||
Header: c.req.Header,
|
||||
},
|
||||
}
|
||||
}
|
||||
79
vendor/github.com/tus/tusd/v2/pkg/handler/http.go
generated
vendored
Normal file
79
vendor/github.com/tus/tusd/v2/pkg/handler/http.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// HTTPRequest contains basic details of an incoming HTTP request.
|
||||
type HTTPRequest struct {
|
||||
// Method is the HTTP method, e.g. POST or PATCH.
|
||||
Method string
|
||||
// URI is the full HTTP request URI, e.g. /files/fooo.
|
||||
URI string
|
||||
// RemoteAddr contains the network address that sent the request.
|
||||
RemoteAddr string
|
||||
// Header contains all HTTP headers as present in the HTTP request.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
type HTTPHeader map[string]string
|
||||
|
||||
// HTTPResponse contains basic details of an outgoing HTTP response.
|
||||
type HTTPResponse struct {
|
||||
// StatusCode is status code, e.g. 200 or 400.
|
||||
StatusCode int
|
||||
// Body is the response body.
|
||||
Body string
|
||||
// Header contains additional HTTP headers for the response.
|
||||
Header HTTPHeader
|
||||
}
|
||||
|
||||
// writeTo writes the HTTP response into w, as specified by the fields in resp.
|
||||
func (resp HTTPResponse) writeTo(w http.ResponseWriter) {
|
||||
headers := w.Header()
|
||||
for key, value := range resp.Header {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
|
||||
if len(resp.Body) > 0 {
|
||||
headers.Set("Content-Length", strconv.Itoa(len(resp.Body)))
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
if len(resp.Body) > 0 {
|
||||
w.Write([]byte(resp.Body))
|
||||
}
|
||||
}
|
||||
|
||||
// MergeWith returns a copy of resp1, where non-default values from resp2 overwrite
|
||||
// values from resp1.
|
||||
func (resp1 HTTPResponse) MergeWith(resp2 HTTPResponse) HTTPResponse {
|
||||
// Clone the response 1 and use it as a basis
|
||||
newResp := resp1
|
||||
|
||||
// Take the status code and body from response 2 to
|
||||
// overwrite values from response 1.
|
||||
if resp2.StatusCode != 0 {
|
||||
newResp.StatusCode = resp2.StatusCode
|
||||
}
|
||||
|
||||
if len(resp2.Body) > 0 {
|
||||
newResp.Body = resp2.Body
|
||||
}
|
||||
|
||||
// For the headers, me must make a new map to avoid writing
|
||||
// into the header map from response 1.
|
||||
newResp.Header = make(HTTPHeader, len(resp1.Header)+len(resp2.Header))
|
||||
|
||||
for key, value := range resp1.Header {
|
||||
newResp.Header[key] = value
|
||||
}
|
||||
|
||||
for key, value := range resp2.Header {
|
||||
newResp.Header[key] = value
|
||||
}
|
||||
|
||||
return newResp
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
@@ -31,7 +30,7 @@ func (m Metrics) incRequestsTotal(method string) {
|
||||
}
|
||||
|
||||
// incErrorsTotal increases the counter for this error atomically by one.
|
||||
func (m Metrics) incErrorsTotal(err HTTPError) {
|
||||
func (m Metrics) incErrorsTotal(err Error) {
|
||||
ptr := m.ErrorsTotal.retrievePointerFor(err)
|
||||
atomic.AddUint64(ptr, 1)
|
||||
}
|
||||
@@ -78,23 +77,16 @@ func newMetrics() Metrics {
|
||||
// ErrorsTotalMap stores the counters for the different HTTP errors.
|
||||
type ErrorsTotalMap struct {
|
||||
lock sync.RWMutex
|
||||
counter map[simpleHTTPError]*uint64
|
||||
counter map[ErrorsTotalMapEntry]*uint64
|
||||
}
|
||||
|
||||
type simpleHTTPError struct {
|
||||
Message string
|
||||
type ErrorsTotalMapEntry struct {
|
||||
ErrorCode string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func simplifyHTTPError(err HTTPError) simpleHTTPError {
|
||||
return simpleHTTPError{
|
||||
Message: err.Error(),
|
||||
StatusCode: err.StatusCode(),
|
||||
}
|
||||
}
|
||||
|
||||
func newErrorsTotalMap() *ErrorsTotalMap {
|
||||
m := make(map[simpleHTTPError]*uint64, 20)
|
||||
m := make(map[ErrorsTotalMapEntry]*uint64, 20)
|
||||
return &ErrorsTotalMap{
|
||||
counter: m,
|
||||
}
|
||||
@@ -102,8 +94,12 @@ func newErrorsTotalMap() *ErrorsTotalMap {
|
||||
|
||||
// retrievePointerFor returns (after creating it if necessary) the pointer to
|
||||
// the counter for the error.
|
||||
func (e *ErrorsTotalMap) retrievePointerFor(err HTTPError) *uint64 {
|
||||
serr := simplifyHTTPError(err)
|
||||
func (e *ErrorsTotalMap) retrievePointerFor(err Error) *uint64 {
|
||||
serr := ErrorsTotalMapEntry{
|
||||
ErrorCode: err.ErrorCode,
|
||||
StatusCode: err.HTTPResponse.StatusCode,
|
||||
}
|
||||
|
||||
e.lock.RLock()
|
||||
ptr, ok := e.counter[serr]
|
||||
e.lock.RUnlock()
|
||||
@@ -124,12 +120,11 @@ func (e *ErrorsTotalMap) retrievePointerFor(err HTTPError) *uint64 {
|
||||
}
|
||||
|
||||
// Load retrieves the map of the counter pointers atomically
|
||||
func (e *ErrorsTotalMap) Load() map[HTTPError]*uint64 {
|
||||
m := make(map[HTTPError]*uint64, len(e.counter))
|
||||
func (e *ErrorsTotalMap) Load() map[ErrorsTotalMapEntry]*uint64 {
|
||||
m := make(map[ErrorsTotalMapEntry]*uint64, len(e.counter))
|
||||
e.lock.RLock()
|
||||
for err, ptr := range e.counter {
|
||||
httpErr := NewHTTPError(errors.New(err.Message), err.StatusCode)
|
||||
m[httpErr] = ptr
|
||||
m[err] = ptr
|
||||
}
|
||||
e.lock.RUnlock()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
102
vendor/golang.org/x/exp/slog/attr.go
generated
vendored
Normal file
102
vendor/golang.org/x/exp/slog/attr.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Attr is a key-value pair.
|
||||
type Attr struct {
|
||||
Key string
|
||||
Value Value
|
||||
}
|
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr {
|
||||
return Attr{key, StringValue(value)}
|
||||
}
|
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr {
|
||||
return Attr{key, Int64Value(value)}
|
||||
}
|
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr {
|
||||
return Int64(key, int64(value))
|
||||
}
|
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr {
|
||||
return Attr{key, Uint64Value(v)}
|
||||
}
|
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr {
|
||||
return Attr{key, Float64Value(v)}
|
||||
}
|
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr {
|
||||
return Attr{key, BoolValue(v)}
|
||||
}
|
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr {
|
||||
return Attr{key, TimeValue(v)}
|
||||
}
|
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr {
|
||||
return Attr{key, DurationValue(v)}
|
||||
}
|
||||
|
||||
// Group returns an Attr for a Group Value.
|
||||
// The first argument is the key; the remaining arguments
|
||||
// are converted to Attrs as in [Logger.Log].
|
||||
//
|
||||
// Use Group to collect several key-value pairs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, args ...any) Attr {
|
||||
return Attr{key, GroupValue(argsToAttrSlice(args)...)}
|
||||
}
|
||||
|
||||
func argsToAttrSlice(args []any) []Attr {
|
||||
var (
|
||||
attr Attr
|
||||
attrs []Attr
|
||||
)
|
||||
for len(args) > 0 {
|
||||
attr, args = argsToAttr(args)
|
||||
attrs = append(attrs, attr)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [Value.AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr {
|
||||
return Attr{key, AnyValue(value)}
|
||||
}
|
||||
|
||||
// Equal reports whether a and b have equal keys and values.
|
||||
func (a Attr) Equal(b Attr) bool {
|
||||
return a.Key == b.Key && a.Value.Equal(b.Value)
|
||||
}
|
||||
|
||||
func (a Attr) String() string {
|
||||
return fmt.Sprintf("%s=%s", a.Key, a.Value)
|
||||
}
|
||||
|
||||
// isEmpty reports whether a has an empty key and a nil value.
|
||||
// That can be written as Attr{} or Any("", nil).
|
||||
func (a Attr) isEmpty() bool {
|
||||
return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
|
||||
}
|
||||
316
vendor/golang.org/x/exp/slog/doc.go
generated
vendored
Normal file
316
vendor/golang.org/x/exp/slog/doc.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package slog provides structured logging,
|
||||
in which log records include a message,
|
||||
a severity level, and various other attributes
|
||||
expressed as key-value pairs.
|
||||
|
||||
It defines a type, [Logger],
|
||||
which provides several methods (such as [Logger.Info] and [Logger.Error])
|
||||
for reporting events of interest.
|
||||
|
||||
Each Logger is associated with a [Handler].
|
||||
A Logger output method creates a [Record] from the method arguments
|
||||
and passes it to the Handler, which decides how to handle it.
|
||||
There is a default Logger accessible through top-level functions
|
||||
(such as [Info] and [Error]) that call the corresponding Logger methods.
|
||||
|
||||
A log record consists of a time, a level, a message, and a set of key-value
|
||||
pairs, where the keys are strings and the values may be of any type.
|
||||
As an example,
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
creates a record containing the time of the call,
|
||||
a level of Info, the message "hello", and a single
|
||||
pair with key "count" and value 3.
|
||||
|
||||
The [Info] top-level function calls the [Logger.Info] method on the default Logger.
|
||||
In addition to [Logger.Info], there are methods for Debug, Warn and Error levels.
|
||||
Besides these convenience methods for common levels,
|
||||
there is also a [Logger.Log] method which takes the level as an argument.
|
||||
Each of these methods has a corresponding top-level function that uses the
|
||||
default logger.
|
||||
|
||||
The default handler formats the log record's message, time, level, and attributes
|
||||
as a string and passes it to the [log] package.
|
||||
|
||||
2022/11/08 15:28:26 INFO hello count=3
|
||||
|
||||
For more control over the output format, create a logger with a different handler.
|
||||
This statement uses [New] to create a new logger with a TextHandler
|
||||
that writes structured records in text form to standard error:
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||
|
||||
[TextHandler] output is a sequence of key=value pairs, easily and unambiguously
|
||||
parsed by machine. This statement:
|
||||
|
||||
logger.Info("hello", "count", 3)
|
||||
|
||||
produces this output:
|
||||
|
||||
time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3
|
||||
|
||||
The package also provides [JSONHandler], whose output is line-delimited JSON:
|
||||
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
logger.Info("hello", "count", 3)
|
||||
|
||||
produces this output:
|
||||
|
||||
{"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}
|
||||
|
||||
Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions].
|
||||
There are options for setting the minimum level (see Levels, below),
|
||||
displaying the source file and line of the log call, and
|
||||
modifying attributes before they are logged.
|
||||
|
||||
Setting a logger as the default with
|
||||
|
||||
slog.SetDefault(logger)
|
||||
|
||||
will cause the top-level functions like [Info] to use it.
|
||||
[SetDefault] also updates the default logger used by the [log] package,
|
||||
so that existing applications that use [log.Printf] and related functions
|
||||
will send log records to the logger's handler without needing to be rewritten.
|
||||
|
||||
Some attributes are common to many log calls.
|
||||
For example, you may wish to include the URL or trace identifier of a server request
|
||||
with all log events arising from the request.
|
||||
Rather than repeat the attribute with every log call, you can use [Logger.With]
|
||||
to construct a new Logger containing the attributes:
|
||||
|
||||
logger2 := logger.With("url", r.URL)
|
||||
|
||||
The arguments to With are the same key-value pairs used in [Logger.Info].
|
||||
The result is a new Logger with the same handler as the original, but additional
|
||||
attributes that will appear in the output of every call.
|
||||
|
||||
# Levels
|
||||
|
||||
A [Level] is an integer representing the importance or severity of a log event.
|
||||
The higher the level, the more severe the event.
|
||||
This package defines constants for the most common levels,
|
||||
but any int can be used as a level.
|
||||
|
||||
In an application, you may wish to log messages only at a certain level or greater.
|
||||
One common configuration is to log messages at Info or higher levels,
|
||||
suppressing debug logging until it is needed.
|
||||
The built-in handlers can be configured with the minimum level to output by
|
||||
setting [HandlerOptions.Level].
|
||||
The program's `main` function typically does this.
|
||||
The default value is LevelInfo.
|
||||
|
||||
Setting the [HandlerOptions.Level] field to a [Level] value
|
||||
fixes the handler's minimum level throughout its lifetime.
|
||||
Setting it to a [LevelVar] allows the level to be varied dynamically.
|
||||
A LevelVar holds a Level and is safe to read or write from multiple
|
||||
goroutines.
|
||||
To vary the level dynamically for an entire program, first initialize
|
||||
a global LevelVar:
|
||||
|
||||
var programLevel = new(slog.LevelVar) // Info by default
|
||||
|
||||
Then use the LevelVar to construct a handler, and make it the default:
|
||||
|
||||
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
|
||||
slog.SetDefault(slog.New(h))
|
||||
|
||||
Now the program can change its logging level with a single statement:
|
||||
|
||||
programLevel.Set(slog.LevelDebug)
|
||||
|
||||
# Groups
|
||||
|
||||
Attributes can be collected into groups.
|
||||
A group has a name that is used to qualify the names of its attributes.
|
||||
How this qualification is displayed depends on the handler.
|
||||
[TextHandler] separates the group and attribute names with a dot.
|
||||
[JSONHandler] treats each group as a separate JSON object, with the group name as the key.
|
||||
|
||||
Use [Group] to create a Group attribute from a name and a list of key-value pairs:
|
||||
|
||||
slog.Group("request",
|
||||
"method", r.Method,
|
||||
"url", r.URL)
|
||||
|
||||
TextHandler would display this group as
|
||||
|
||||
request.method=GET request.url=http://example.com
|
||||
|
||||
JSONHandler would display it as
|
||||
|
||||
"request":{"method":"GET","url":"http://example.com"}
|
||||
|
||||
Use [Logger.WithGroup] to qualify all of a Logger's output
|
||||
with a group name. Calling WithGroup on a Logger results in a
|
||||
new Logger with the same Handler as the original, but with all
|
||||
its attributes qualified by the group name.
|
||||
|
||||
This can help prevent duplicate attribute keys in large systems,
|
||||
where subsystems might use the same keys.
|
||||
Pass each subsystem a different Logger with its own group name so that
|
||||
potential duplicates are qualified:
|
||||
|
||||
logger := slog.Default().With("id", systemID)
|
||||
parserLogger := logger.WithGroup("parser")
|
||||
parseInput(input, parserLogger)
|
||||
|
||||
When parseInput logs with parserLogger, its keys will be qualified with "parser",
|
||||
so even if it uses the common key "id", the log line will have distinct keys.
|
||||
|
||||
# Contexts
|
||||
|
||||
Some handlers may wish to include information from the [context.Context] that is
|
||||
available at the call site. One example of such information
|
||||
is the identifier for the current span when tracing is enabled.
|
||||
|
||||
The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first
|
||||
argument, as do their corresponding top-level functions.
|
||||
|
||||
Although the convenience methods on Logger (Info and so on) and the
|
||||
corresponding top-level functions do not take a context, the alternatives ending
|
||||
in "Context" do. For example,
|
||||
|
||||
slog.InfoContext(ctx, "message")
|
||||
|
||||
It is recommended to pass a context to an output method if one is available.
|
||||
|
||||
# Attrs and Values
|
||||
|
||||
An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as
|
||||
alternating keys and values. The statement
|
||||
|
||||
slog.Info("hello", slog.Int("count", 3))
|
||||
|
||||
behaves the same as
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
There are convenience constructors for [Attr] such as [Int], [String], and [Bool]
|
||||
for common types, as well as the function [Any] for constructing Attrs of any
|
||||
type.
|
||||
|
||||
The value part of an Attr is a type called [Value].
|
||||
Like an [any], a Value can hold any Go value,
|
||||
but it can represent typical values, including all numbers and strings,
|
||||
without an allocation.
|
||||
|
||||
For the most efficient log output, use [Logger.LogAttrs].
|
||||
It is similar to [Logger.Log] but accepts only Attrs, not alternating
|
||||
keys and values; this allows it, too, to avoid allocation.
|
||||
|
||||
The call
|
||||
|
||||
logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3))
|
||||
|
||||
is the most efficient way to achieve the same output as
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
# Customizing a type's logging behavior
|
||||
|
||||
If a type implements the [LogValuer] interface, the [Value] returned from its LogValue
|
||||
method is used for logging. You can use this to control how values of the type
|
||||
appear in logs. For example, you can redact secret information like passwords,
|
||||
or gather a struct's fields in a Group. See the examples under [LogValuer] for
|
||||
details.
|
||||
|
||||
A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve]
|
||||
method handles these cases carefully, avoiding infinite loops and unbounded recursion.
|
||||
Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly.
|
||||
|
||||
# Wrapping output methods
|
||||
|
||||
The logger functions use reflection over the call stack to find the file name
|
||||
and line number of the logging call within the application. This can produce
|
||||
incorrect source information for functions that wrap slog. For instance, if you
|
||||
define this function in file mylog.go:
|
||||
|
||||
func Infof(format string, args ...any) {
|
||||
slog.Default().Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
and you call it like this in main.go:
|
||||
|
||||
Infof(slog.Default(), "hello, %s", "world")
|
||||
|
||||
then slog will report the source file as mylog.go, not main.go.
|
||||
|
||||
A correct implementation of Infof will obtain the source location
|
||||
(pc) and pass it to NewRecord.
|
||||
The Infof function in the package-level example called "wrapping"
|
||||
demonstrates how to do this.
|
||||
|
||||
# Working with Records
|
||||
|
||||
Sometimes a Handler will need to modify a Record
|
||||
before passing it on to another Handler or backend.
|
||||
A Record contains a mixture of simple public fields (e.g. Time, Level, Message)
|
||||
and hidden fields that refer to state (such as attributes) indirectly. This
|
||||
means that modifying a simple copy of a Record (e.g. by calling
|
||||
[Record.Add] or [Record.AddAttrs] to add attributes)
|
||||
may have unexpected effects on the original.
|
||||
Before modifying a Record, use [Clone] to
|
||||
create a copy that shares no state with the original,
|
||||
or create a new Record with [NewRecord]
|
||||
and build up its Attrs by traversing the old ones with [Record.Attrs].
|
||||
|
||||
# Performance considerations
|
||||
|
||||
If profiling your application demonstrates that logging is taking significant time,
|
||||
the following suggestions may help.
|
||||
|
||||
If many log lines have a common attribute, use [Logger.With] to create a Logger with
|
||||
that attribute. The built-in handlers will format that attribute only once, at the
|
||||
call to [Logger.With]. The [Handler] interface is designed to allow that optimization,
|
||||
and a well-written Handler should take advantage of it.
|
||||
|
||||
The arguments to a log call are always evaluated, even if the log event is discarded.
|
||||
If possible, defer computation so that it happens only if the value is actually logged.
|
||||
For example, consider the call
|
||||
|
||||
slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily
|
||||
|
||||
The URL.String method will be called even if the logger discards Info-level events.
|
||||
Instead, pass the URL directly:
|
||||
|
||||
slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed
|
||||
|
||||
The built-in [TextHandler] will call its String method, but only
|
||||
if the log event is enabled.
|
||||
Avoiding the call to String also preserves the structure of the underlying value.
|
||||
For example [JSONHandler] emits the components of the parsed URL as a JSON object.
|
||||
If you want to avoid eagerly paying the cost of the String call
|
||||
without causing the handler to potentially inspect the structure of the value,
|
||||
wrap the value in a fmt.Stringer implementation that hides its Marshal methods.
|
||||
|
||||
You can also use the [LogValuer] interface to avoid unnecessary work in disabled log
|
||||
calls. Say you need to log some expensive value:
|
||||
|
||||
slog.Debug("frobbing", "value", computeExpensiveValue(arg))
|
||||
|
||||
Even if this line is disabled, computeExpensiveValue will be called.
|
||||
To avoid that, define a type implementing LogValuer:
|
||||
|
||||
type expensive struct { arg int }
|
||||
|
||||
func (e expensive) LogValue() slog.Value {
|
||||
return slog.AnyValue(computeExpensiveValue(e.arg))
|
||||
}
|
||||
|
||||
Then use a value of that type in log calls:
|
||||
|
||||
slog.Debug("frobbing", "value", expensive{arg})
|
||||
|
||||
Now computeExpensiveValue will only be called when the line is enabled.
|
||||
|
||||
The built-in handlers acquire a lock before calling [io.Writer.Write]
|
||||
to ensure that each record is written in one piece. User-defined
|
||||
handlers are responsible for their own locking.
|
||||
*/
|
||||
package slog
|
||||
577
vendor/golang.org/x/exp/slog/handler.go
generated
vendored
Normal file
577
vendor/golang.org/x/exp/slog/handler.go
generated
vendored
Normal file
@@ -0,0 +1,577 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/exp/slog/internal/buffer"
|
||||
)
|
||||
|
||||
// A Handler handles log records produced by a Logger..
|
||||
//
|
||||
// A typical handler may print log records to standard error,
|
||||
// or write them to a file or database, or perhaps augment them
|
||||
// with additional attributes and pass them on to another handler.
|
||||
//
|
||||
// Any of the Handler's methods may be called concurrently with itself
|
||||
// or with other methods. It is the responsibility of the Handler to
|
||||
// manage this concurrency.
|
||||
//
|
||||
// Users of the slog package should not invoke Handler methods directly.
|
||||
// They should use the methods of [Logger] instead.
|
||||
type Handler interface {
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
// It is called early, before any arguments are processed,
|
||||
// to save effort if the log event should be discarded.
|
||||
// If called from a Logger method, the first argument is the context
|
||||
// passed to that method, or context.Background() if nil was passed
|
||||
// or the method does not take a context.
|
||||
// The context is passed so Enabled can use its values
|
||||
// to make a decision.
|
||||
Enabled(context.Context, Level) bool
|
||||
|
||||
// Handle handles the Record.
|
||||
// It will only be called when Enabled returns true.
|
||||
// The Context argument is as for Enabled.
|
||||
// It is present solely to provide Handlers access to the context's values.
|
||||
// Canceling the context should not affect record processing.
|
||||
// (Among other things, log messages may be necessary to debug a
|
||||
// cancellation-related problem.)
|
||||
//
|
||||
// Handle methods that produce output should observe the following rules:
|
||||
// - If r.Time is the zero time, ignore the time.
|
||||
// - If r.PC is zero, ignore it.
|
||||
// - Attr's values should be resolved.
|
||||
// - If an Attr's key and value are both the zero value, ignore the Attr.
|
||||
// This can be tested with attr.Equal(Attr{}).
|
||||
// - If a group's key is empty, inline the group's Attrs.
|
||||
// - If a group has no Attrs (even if it has a non-empty key),
|
||||
// ignore it.
|
||||
Handle(context.Context, Record) error
|
||||
|
||||
// WithAttrs returns a new Handler whose attributes consist of
|
||||
// both the receiver's attributes and the arguments.
|
||||
// The Handler owns the slice: it may retain, modify or discard it.
|
||||
WithAttrs(attrs []Attr) Handler
|
||||
|
||||
// WithGroup returns a new Handler with the given group appended to
|
||||
// the receiver's existing groups.
|
||||
// The keys of all subsequent attributes, whether added by With or in a
|
||||
// Record, should be qualified by the sequence of group names.
|
||||
//
|
||||
// How this qualification happens is up to the Handler, so long as
|
||||
// this Handler's attribute keys differ from those of another Handler
|
||||
// with a different sequence of group names.
|
||||
//
|
||||
// A Handler should treat WithGroup as starting a Group of Attrs that ends
|
||||
// at the end of the log event. That is,
|
||||
//
|
||||
// logger.WithGroup("s").LogAttrs(level, msg, slog.Int("a", 1), slog.Int("b", 2))
|
||||
//
|
||||
// should behave like
|
||||
//
|
||||
// logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
|
||||
//
|
||||
// If the name is empty, WithGroup returns the receiver.
|
||||
WithGroup(name string) Handler
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
ch *commonHandler
|
||||
// log.Output, except for testing
|
||||
output func(calldepth int, message string) error
|
||||
}
|
||||
|
||||
func newDefaultHandler(output func(int, string) error) *defaultHandler {
|
||||
return &defaultHandler{
|
||||
ch: &commonHandler{json: false},
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
func (*defaultHandler) Enabled(_ context.Context, l Level) bool {
|
||||
return l >= LevelInfo
|
||||
}
|
||||
|
||||
// Collect the level, attributes and message in a string and
|
||||
// write it with the default log.Logger.
|
||||
// Let the log.Logger handle time and file/line.
|
||||
func (h *defaultHandler) Handle(ctx context.Context, r Record) error {
|
||||
buf := buffer.New()
|
||||
buf.WriteString(r.Level.String())
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(r.Message)
|
||||
state := h.ch.newHandleState(buf, true, " ", nil)
|
||||
defer state.free()
|
||||
state.appendNonBuiltIns(r)
|
||||
|
||||
// skip [h.output, defaultHandler.Handle, handlerWriter.Write, log.Output]
|
||||
return h.output(4, buf.String())
|
||||
}
|
||||
|
||||
func (h *defaultHandler) WithAttrs(as []Attr) Handler {
|
||||
return &defaultHandler{h.ch.withAttrs(as), h.output}
|
||||
}
|
||||
|
||||
func (h *defaultHandler) WithGroup(name string) Handler {
|
||||
return &defaultHandler{h.ch.withGroup(name), h.output}
|
||||
}
|
||||
|
||||
// HandlerOptions are options for a TextHandler or JSONHandler.
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions struct {
|
||||
// AddSource causes the handler to compute the source code position
|
||||
// of the log statement and add a SourceKey attribute to the output.
|
||||
AddSource bool
|
||||
|
||||
// Level reports the minimum record level that will be logged.
|
||||
// The handler discards records with lower levels.
|
||||
// If Level is nil, the handler assumes LevelInfo.
|
||||
// The handler calls Level.Level for each record processed;
|
||||
// to adjust the minimum level dynamically, use a LevelVar.
|
||||
Level Leveler
|
||||
|
||||
// ReplaceAttr is called to rewrite each non-group attribute before it is logged.
|
||||
// The attribute's value has been resolved (see [Value.Resolve]).
|
||||
// If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
|
||||
//
|
||||
// The built-in attributes with keys "time", "level", "source", and "msg"
|
||||
// are passed to this function, except that time is omitted
|
||||
// if zero, and source is omitted if AddSource is false.
|
||||
//
|
||||
// The first argument is a list of currently open groups that contain the
|
||||
// Attr. It must not be retained or modified. ReplaceAttr is never called
|
||||
// for Group attributes, only their contents. For example, the attribute
|
||||
// list
|
||||
//
|
||||
// Int("a", 1), Group("g", Int("b", 2)), Int("c", 3)
|
||||
//
|
||||
// results in consecutive calls to ReplaceAttr with the following arguments:
|
||||
//
|
||||
// nil, Int("a", 1)
|
||||
// []string{"g"}, Int("b", 2)
|
||||
// nil, Int("c", 3)
|
||||
//
|
||||
// ReplaceAttr can be used to change the default keys of the built-in
|
||||
// attributes, convert types (for example, to replace a `time.Time` with the
|
||||
// integer seconds since the Unix epoch), sanitize personal information, or
|
||||
// remove attributes from the output.
|
||||
ReplaceAttr func(groups []string, a Attr) Attr
|
||||
}
|
||||
|
||||
// Keys for "built-in" attributes.
|
||||
const (
|
||||
// TimeKey is the key used by the built-in handlers for the time
|
||||
// when the log method is called. The associated Value is a [time.Time].
|
||||
TimeKey = "time"
|
||||
// LevelKey is the key used by the built-in handlers for the level
|
||||
// of the log call. The associated value is a [Level].
|
||||
LevelKey = "level"
|
||||
// MessageKey is the key used by the built-in handlers for the
|
||||
// message of the log call. The associated value is a string.
|
||||
MessageKey = "msg"
|
||||
// SourceKey is the key used by the built-in handlers for the source file
|
||||
// and line of the log call. The associated value is a string.
|
||||
SourceKey = "source"
|
||||
)
|
||||
|
||||
type commonHandler struct {
|
||||
json bool // true => output JSON; false => output text
|
||||
opts HandlerOptions
|
||||
preformattedAttrs []byte
|
||||
groupPrefix string // for text: prefix of groups opened in preformatting
|
||||
groups []string // all groups started from WithGroup
|
||||
nOpenGroups int // the number of groups opened in preformattedAttrs
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (h *commonHandler) clone() *commonHandler {
|
||||
// We can't use assignment because we can't copy the mutex.
|
||||
return &commonHandler{
|
||||
json: h.json,
|
||||
opts: h.opts,
|
||||
preformattedAttrs: slices.Clip(h.preformattedAttrs),
|
||||
groupPrefix: h.groupPrefix,
|
||||
groups: slices.Clip(h.groups),
|
||||
nOpenGroups: h.nOpenGroups,
|
||||
w: h.w,
|
||||
}
|
||||
}
|
||||
|
||||
// enabled reports whether l is greater than or equal to the
|
||||
// minimum level.
|
||||
func (h *commonHandler) enabled(l Level) bool {
|
||||
minLevel := LevelInfo
|
||||
if h.opts.Level != nil {
|
||||
minLevel = h.opts.Level.Level()
|
||||
}
|
||||
return l >= minLevel
|
||||
}
|
||||
|
||||
func (h *commonHandler) withAttrs(as []Attr) *commonHandler {
|
||||
h2 := h.clone()
|
||||
// Pre-format the attributes as an optimization.
|
||||
prefix := buffer.New()
|
||||
defer prefix.Free()
|
||||
prefix.WriteString(h.groupPrefix)
|
||||
state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "", prefix)
|
||||
defer state.free()
|
||||
if len(h2.preformattedAttrs) > 0 {
|
||||
state.sep = h.attrSep()
|
||||
}
|
||||
state.openGroups()
|
||||
for _, a := range as {
|
||||
state.appendAttr(a)
|
||||
}
|
||||
// Remember the new prefix for later keys.
|
||||
h2.groupPrefix = state.prefix.String()
|
||||
// Remember how many opened groups are in preformattedAttrs,
|
||||
// so we don't open them again when we handle a Record.
|
||||
h2.nOpenGroups = len(h2.groups)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *commonHandler) withGroup(name string) *commonHandler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groups = append(h2.groups, name)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *commonHandler) handle(r Record) error {
|
||||
state := h.newHandleState(buffer.New(), true, "", nil)
|
||||
defer state.free()
|
||||
if h.json {
|
||||
state.buf.WriteByte('{')
|
||||
}
|
||||
// Built-in attributes. They are not in a group.
|
||||
stateGroups := state.groups
|
||||
state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups.
|
||||
rep := h.opts.ReplaceAttr
|
||||
// time
|
||||
if !r.Time.IsZero() {
|
||||
key := TimeKey
|
||||
val := r.Time.Round(0) // strip monotonic to match Attr behavior
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendTime(val)
|
||||
} else {
|
||||
state.appendAttr(Time(key, val))
|
||||
}
|
||||
}
|
||||
// level
|
||||
key := LevelKey
|
||||
val := r.Level
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendString(val.String())
|
||||
} else {
|
||||
state.appendAttr(Any(key, val))
|
||||
}
|
||||
// source
|
||||
if h.opts.AddSource {
|
||||
state.appendAttr(Any(SourceKey, r.source()))
|
||||
}
|
||||
key = MessageKey
|
||||
msg := r.Message
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendString(msg)
|
||||
} else {
|
||||
state.appendAttr(String(key, msg))
|
||||
}
|
||||
state.groups = stateGroups // Restore groups passed to ReplaceAttrs.
|
||||
state.appendNonBuiltIns(r)
|
||||
state.buf.WriteByte('\n')
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
_, err := h.w.Write(*state.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *handleState) appendNonBuiltIns(r Record) {
|
||||
// preformatted Attrs
|
||||
if len(s.h.preformattedAttrs) > 0 {
|
||||
s.buf.WriteString(s.sep)
|
||||
s.buf.Write(s.h.preformattedAttrs)
|
||||
s.sep = s.h.attrSep()
|
||||
}
|
||||
// Attrs in Record -- unlike the built-in ones, they are in groups started
|
||||
// from WithGroup.
|
||||
s.prefix = buffer.New()
|
||||
defer s.prefix.Free()
|
||||
s.prefix.WriteString(s.h.groupPrefix)
|
||||
s.openGroups()
|
||||
r.Attrs(func(a Attr) bool {
|
||||
s.appendAttr(a)
|
||||
return true
|
||||
})
|
||||
if s.h.json {
|
||||
// Close all open groups.
|
||||
for range s.h.groups {
|
||||
s.buf.WriteByte('}')
|
||||
}
|
||||
// Close the top-level object.
|
||||
s.buf.WriteByte('}')
|
||||
}
|
||||
}
|
||||
|
||||
// attrSep returns the separator between attributes.
|
||||
func (h *commonHandler) attrSep() string {
|
||||
if h.json {
|
||||
return ","
|
||||
}
|
||||
return " "
|
||||
}
|
||||
|
||||
// handleState holds state for a single call to commonHandler.handle.
|
||||
// The initial value of sep determines whether to emit a separator
|
||||
// before the next key, after which it stays true.
|
||||
type handleState struct {
|
||||
h *commonHandler
|
||||
buf *buffer.Buffer
|
||||
freeBuf bool // should buf be freed?
|
||||
sep string // separator to write before next key
|
||||
prefix *buffer.Buffer // for text: key prefix
|
||||
groups *[]string // pool-allocated slice of active groups, for ReplaceAttr
|
||||
}
|
||||
|
||||
var groupPool = sync.Pool{New: func() any {
|
||||
s := make([]string, 0, 10)
|
||||
return &s
|
||||
}}
|
||||
|
||||
func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string, prefix *buffer.Buffer) handleState {
|
||||
s := handleState{
|
||||
h: h,
|
||||
buf: buf,
|
||||
freeBuf: freeBuf,
|
||||
sep: sep,
|
||||
prefix: prefix,
|
||||
}
|
||||
if h.opts.ReplaceAttr != nil {
|
||||
s.groups = groupPool.Get().(*[]string)
|
||||
*s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *handleState) free() {
|
||||
if s.freeBuf {
|
||||
s.buf.Free()
|
||||
}
|
||||
if gs := s.groups; gs != nil {
|
||||
*gs = (*gs)[:0]
|
||||
groupPool.Put(gs)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) openGroups() {
|
||||
for _, n := range s.h.groups[s.h.nOpenGroups:] {
|
||||
s.openGroup(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Separator for group names and keys.
|
||||
const keyComponentSep = '.'
|
||||
|
||||
// openGroup starts a new group of attributes
|
||||
// with the given name.
|
||||
func (s *handleState) openGroup(name string) {
|
||||
if s.h.json {
|
||||
s.appendKey(name)
|
||||
s.buf.WriteByte('{')
|
||||
s.sep = ""
|
||||
} else {
|
||||
s.prefix.WriteString(name)
|
||||
s.prefix.WriteByte(keyComponentSep)
|
||||
}
|
||||
// Collect group names for ReplaceAttr.
|
||||
if s.groups != nil {
|
||||
*s.groups = append(*s.groups, name)
|
||||
}
|
||||
}
|
||||
|
||||
// closeGroup ends the group with the given name.
|
||||
func (s *handleState) closeGroup(name string) {
|
||||
if s.h.json {
|
||||
s.buf.WriteByte('}')
|
||||
} else {
|
||||
(*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */]
|
||||
}
|
||||
s.sep = s.h.attrSep()
|
||||
if s.groups != nil {
|
||||
*s.groups = (*s.groups)[:len(*s.groups)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// appendAttr appends the Attr's key and value using app.
|
||||
// It handles replacement and checking for an empty key.
|
||||
// after replacement).
|
||||
func (s *handleState) appendAttr(a Attr) {
|
||||
if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup {
|
||||
var gs []string
|
||||
if s.groups != nil {
|
||||
gs = *s.groups
|
||||
}
|
||||
// Resolve before calling ReplaceAttr, so the user doesn't have to.
|
||||
a.Value = a.Value.Resolve()
|
||||
a = rep(gs, a)
|
||||
}
|
||||
a.Value = a.Value.Resolve()
|
||||
// Elide empty Attrs.
|
||||
if a.isEmpty() {
|
||||
return
|
||||
}
|
||||
// Special case: Source.
|
||||
if v := a.Value; v.Kind() == KindAny {
|
||||
if src, ok := v.Any().(*Source); ok {
|
||||
if s.h.json {
|
||||
a.Value = src.group()
|
||||
} else {
|
||||
a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line))
|
||||
}
|
||||
}
|
||||
}
|
||||
if a.Value.Kind() == KindGroup {
|
||||
attrs := a.Value.Group()
|
||||
// Output only non-empty groups.
|
||||
if len(attrs) > 0 {
|
||||
// Inline a group with an empty key.
|
||||
if a.Key != "" {
|
||||
s.openGroup(a.Key)
|
||||
}
|
||||
for _, aa := range attrs {
|
||||
s.appendAttr(aa)
|
||||
}
|
||||
if a.Key != "" {
|
||||
s.closeGroup(a.Key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.appendKey(a.Key)
|
||||
s.appendValue(a.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendError(err error) {
|
||||
s.appendString(fmt.Sprintf("!ERROR:%v", err))
|
||||
}
|
||||
|
||||
func (s *handleState) appendKey(key string) {
|
||||
s.buf.WriteString(s.sep)
|
||||
if s.prefix != nil {
|
||||
// TODO: optimize by avoiding allocation.
|
||||
s.appendString(string(*s.prefix) + key)
|
||||
} else {
|
||||
s.appendString(key)
|
||||
}
|
||||
if s.h.json {
|
||||
s.buf.WriteByte(':')
|
||||
} else {
|
||||
s.buf.WriteByte('=')
|
||||
}
|
||||
s.sep = s.h.attrSep()
|
||||
}
|
||||
|
||||
func (s *handleState) appendString(str string) {
|
||||
if s.h.json {
|
||||
s.buf.WriteByte('"')
|
||||
*s.buf = appendEscapedJSONString(*s.buf, str)
|
||||
s.buf.WriteByte('"')
|
||||
} else {
|
||||
// text
|
||||
if needsQuoting(str) {
|
||||
*s.buf = strconv.AppendQuote(*s.buf, str)
|
||||
} else {
|
||||
s.buf.WriteString(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendValue(v Value) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// If it panics with a nil pointer, the most likely cases are
|
||||
// an encoding.TextMarshaler or error fails to guard against nil,
|
||||
// in which case "<nil>" seems to be the feasible choice.
|
||||
//
|
||||
// Adapted from the code in fmt/print.go.
|
||||
if v := reflect.ValueOf(v.any); v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
s.appendString("<nil>")
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise just print the original panic message.
|
||||
s.appendString(fmt.Sprintf("!PANIC: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
if s.h.json {
|
||||
err = appendJSONValue(s, v)
|
||||
} else {
|
||||
err = appendTextValue(s, v)
|
||||
}
|
||||
if err != nil {
|
||||
s.appendError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendTime(t time.Time) {
|
||||
if s.h.json {
|
||||
appendJSONTime(s, t)
|
||||
} else {
|
||||
writeTimeRFC3339Millis(s.buf, t)
|
||||
}
|
||||
}
|
||||
|
||||
// This takes half the time of Time.AppendFormat.
|
||||
func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) {
|
||||
year, month, day := t.Date()
|
||||
buf.WritePosIntWidth(year, 4)
|
||||
buf.WriteByte('-')
|
||||
buf.WritePosIntWidth(int(month), 2)
|
||||
buf.WriteByte('-')
|
||||
buf.WritePosIntWidth(day, 2)
|
||||
buf.WriteByte('T')
|
||||
hour, min, sec := t.Clock()
|
||||
buf.WritePosIntWidth(hour, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(min, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(sec, 2)
|
||||
ns := t.Nanosecond()
|
||||
buf.WriteByte('.')
|
||||
buf.WritePosIntWidth(ns/1e6, 3)
|
||||
_, offsetSeconds := t.Zone()
|
||||
if offsetSeconds == 0 {
|
||||
buf.WriteByte('Z')
|
||||
} else {
|
||||
offsetMinutes := offsetSeconds / 60
|
||||
if offsetMinutes < 0 {
|
||||
buf.WriteByte('-')
|
||||
offsetMinutes = -offsetMinutes
|
||||
} else {
|
||||
buf.WriteByte('+')
|
||||
}
|
||||
buf.WritePosIntWidth(offsetMinutes/60, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(offsetMinutes%60, 2)
|
||||
}
|
||||
}
|
||||
84
vendor/golang.org/x/exp/slog/internal/buffer/buffer.go
generated
vendored
Normal file
84
vendor/golang.org/x/exp/slog/internal/buffer/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package buffer provides a pool-allocated byte buffer.
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Buffer adapted from go/src/fmt/print.go
|
||||
type Buffer []byte
|
||||
|
||||
// Having an initial size gives a dramatic speedup.
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
b := make([]byte, 0, 1024)
|
||||
return (*Buffer)(&b)
|
||||
},
|
||||
}
|
||||
|
||||
func New() *Buffer {
|
||||
return bufPool.Get().(*Buffer)
|
||||
}
|
||||
|
||||
func (b *Buffer) Free() {
|
||||
// To reduce peak allocation, return only smaller buffers to the pool.
|
||||
const maxBufferSize = 16 << 10
|
||||
if cap(*b) <= maxBufferSize {
|
||||
*b = (*b)[:0]
|
||||
bufPool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
*b = (*b)[:0]
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (int, error) {
|
||||
*b = append(*b, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteString(s string) {
|
||||
*b = append(*b, s...)
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteByte(c byte) {
|
||||
*b = append(*b, c)
|
||||
}
|
||||
|
||||
func (b *Buffer) WritePosInt(i int) {
|
||||
b.WritePosIntWidth(i, 0)
|
||||
}
|
||||
|
||||
// WritePosIntWidth writes non-negative integer i to the buffer, padded on the left
|
||||
// by zeroes to the given width. Use a width of 0 to omit padding.
|
||||
func (b *Buffer) WritePosIntWidth(i, width int) {
|
||||
// Cheap integer to fixed-width decimal ASCII.
|
||||
// Copied from log/log.go.
|
||||
|
||||
if i < 0 {
|
||||
panic("negative int")
|
||||
}
|
||||
|
||||
// Assemble decimal in reverse order.
|
||||
var bb [20]byte
|
||||
bp := len(bb) - 1
|
||||
for i >= 10 || width > 1 {
|
||||
width--
|
||||
q := i / 10
|
||||
bb[bp] = byte('0' + i - q*10)
|
||||
bp--
|
||||
i = q
|
||||
}
|
||||
// i < 10
|
||||
bb[bp] = byte('0' + i)
|
||||
b.Write(bb[bp:])
|
||||
}
|
||||
|
||||
func (b *Buffer) String() string {
|
||||
return string(*b)
|
||||
}
|
||||
9
vendor/golang.org/x/exp/slog/internal/ignorepc.go
generated
vendored
Normal file
9
vendor/golang.org/x/exp/slog/internal/ignorepc.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// If IgnorePC is true, do not invoke runtime.Callers to get the pc.
|
||||
// This is solely for benchmarking the slowdown from runtime.Callers.
|
||||
var IgnorePC = false
|
||||
336
vendor/golang.org/x/exp/slog/json_handler.go
generated
vendored
Normal file
336
vendor/golang.org/x/exp/slog/json_handler.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/exp/slog/internal/buffer"
|
||||
)
|
||||
|
||||
// JSONHandler is a Handler that writes Records to an io.Writer as
|
||||
// line-delimited JSON objects.
|
||||
type JSONHandler struct {
|
||||
*commonHandler
|
||||
}
|
||||
|
||||
// NewJSONHandler creates a JSONHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &JSONHandler{
|
||||
&commonHandler{
|
||||
json: true,
|
||||
w: w,
|
||||
opts: *opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
func (h *JSONHandler) Enabled(_ context.Context, level Level) bool {
|
||||
return h.commonHandler.enabled(level)
|
||||
}
|
||||
|
||||
// WithAttrs returns a new JSONHandler whose attributes consists
|
||||
// of h's attributes followed by attrs.
|
||||
func (h *JSONHandler) WithAttrs(attrs []Attr) Handler {
|
||||
return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
|
||||
}
|
||||
|
||||
func (h *JSONHandler) WithGroup(name string) Handler {
|
||||
return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)}
|
||||
}
|
||||
|
||||
// Handle formats its argument Record as a JSON object on a single line.
|
||||
//
|
||||
// If the Record's time is zero, the time is omitted.
|
||||
// Otherwise, the key is "time"
|
||||
// and the value is output as with json.Marshal.
|
||||
//
|
||||
// If the Record's level is zero, the level is omitted.
|
||||
// Otherwise, the key is "level"
|
||||
// and the value of [Level.String] is output.
|
||||
//
|
||||
// If the AddSource option is set and source information is available,
|
||||
// the key is "source"
|
||||
// and the value is output as "FILE:LINE".
|
||||
//
|
||||
// The message's key is "msg".
|
||||
//
|
||||
// To modify these or other attributes, or remove them from the output, use
|
||||
// [HandlerOptions.ReplaceAttr].
|
||||
//
|
||||
// Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false),
|
||||
// with two exceptions.
|
||||
//
|
||||
// First, an Attr whose Value is of type error is formatted as a string, by
|
||||
// calling its Error method. Only errors in Attrs receive this special treatment,
|
||||
// not errors embedded in structs, slices, maps or other data structures that
|
||||
// are processed by the encoding/json package.
|
||||
//
|
||||
// Second, an encoding failure does not cause Handle to return an error.
|
||||
// Instead, the error message is formatted as a string.
|
||||
//
|
||||
// Each call to Handle results in a single serialized call to io.Writer.Write.
|
||||
func (h *JSONHandler) Handle(_ context.Context, r Record) error {
|
||||
return h.commonHandler.handle(r)
|
||||
}
|
||||
|
||||
// Adapted from time.Time.MarshalJSON to avoid allocation.
|
||||
func appendJSONTime(s *handleState, t time.Time) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
// RFC 3339 is clear that years are 4 digits exactly.
|
||||
// See golang.org/issue/4556#c15 for more discussion.
|
||||
s.appendError(errors.New("time.Time year outside of range [0,9999]"))
|
||||
}
|
||||
s.buf.WriteByte('"')
|
||||
*s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano)
|
||||
s.buf.WriteByte('"')
|
||||
}
|
||||
|
||||
func appendJSONValue(s *handleState, v Value) error {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
s.appendString(v.str())
|
||||
case KindInt64:
|
||||
*s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10)
|
||||
case KindUint64:
|
||||
*s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10)
|
||||
case KindFloat64:
|
||||
// json.Marshal is funny about floats; it doesn't
|
||||
// always match strconv.AppendFloat. So just call it.
|
||||
// That's expensive, but floats are rare.
|
||||
if err := appendJSONMarshal(s.buf, v.Float64()); err != nil {
|
||||
return err
|
||||
}
|
||||
case KindBool:
|
||||
*s.buf = strconv.AppendBool(*s.buf, v.Bool())
|
||||
case KindDuration:
|
||||
// Do what json.Marshal does.
|
||||
*s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10)
|
||||
case KindTime:
|
||||
s.appendTime(v.Time())
|
||||
case KindAny:
|
||||
a := v.Any()
|
||||
_, jm := a.(json.Marshaler)
|
||||
if err, ok := a.(error); ok && !jm {
|
||||
s.appendString(err.Error())
|
||||
} else {
|
||||
return appendJSONMarshal(s.buf, a)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendJSONMarshal(buf *buffer.Buffer, v any) error {
|
||||
// Use a json.Encoder to avoid escaping HTML.
|
||||
var bb bytes.Buffer
|
||||
enc := json.NewEncoder(&bb)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
bs := bb.Bytes()
|
||||
buf.Write(bs[:len(bs)-1]) // remove final newline
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendEscapedJSONString escapes s for JSON and appends it to buf.
|
||||
// It does not surround the string in quotation marks.
|
||||
//
|
||||
// Modified from encoding/json/encode.go:encodeState.string,
|
||||
// with escapeHTML set to false.
|
||||
func appendEscapedJSONString(buf []byte, s string) []byte {
|
||||
char := func(b byte) { buf = append(buf, b) }
|
||||
str := func(s string) { buf = append(buf, s...) }
|
||||
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if safeSet[b] {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
char('\\')
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
char(b)
|
||||
case '\n':
|
||||
char('n')
|
||||
case '\r':
|
||||
char('r')
|
||||
case '\t':
|
||||
char('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||
str(`u00`)
|
||||
char(hex[b>>4])
|
||||
char(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
str(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
// U+2028 is LINE SEPARATOR.
|
||||
// U+2029 is PARAGRAPH SEPARATOR.
|
||||
// They are both technically valid characters in JSON strings,
|
||||
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||
// and can lead to security holes there. It is valid JSON to
|
||||
// escape them, so we do so unconditionally.
|
||||
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||
if c == '\u2028' || c == '\u2029' {
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
str(`\u202`)
|
||||
char(hex[c&0xF])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
str(s[start:])
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
var hex = "0123456789abcdef"
|
||||
|
||||
// Copied from encoding/json/tables.go.
|
||||
//
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
201
vendor/golang.org/x/exp/slog/level.go
generated
vendored
Normal file
201
vendor/golang.org/x/exp/slog/level.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A Level is the importance or severity of a log event.
|
||||
// The higher the level, the more important or severe the event.
|
||||
type Level int
|
||||
|
||||
// Level numbers are inherently arbitrary,
|
||||
// but we picked them to satisfy three constraints.
|
||||
// Any system can map them to another numbering scheme if it wishes.
|
||||
//
|
||||
// First, we wanted the default level to be Info, Since Levels are ints, Info is
|
||||
// the default value for int, zero.
|
||||
//
|
||||
|
||||
// Second, we wanted to make it easy to use levels to specify logger verbosity.
|
||||
// Since a larger level means a more severe event, a logger that accepts events
|
||||
// with smaller (or more negative) level means a more verbose logger. Logger
|
||||
// verbosity is thus the negation of event severity, and the default verbosity
|
||||
// of 0 accepts all events at least as severe as INFO.
|
||||
//
|
||||
// Third, we wanted some room between levels to accommodate schemes with named
|
||||
// levels between ours. For example, Google Cloud Logging defines a Notice level
|
||||
// between Info and Warn. Since there are only a few of these intermediate
|
||||
// levels, the gap between the numbers need not be large. Our gap of 4 matches
|
||||
// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
|
||||
// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
|
||||
// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
|
||||
// does not. But those OpenTelemetry levels can still be represented as slog
|
||||
// Levels by using the appropriate integers.
|
||||
//
|
||||
// Names for common levels.
|
||||
const (
|
||||
LevelDebug Level = -4
|
||||
LevelInfo Level = 0
|
||||
LevelWarn Level = 4
|
||||
LevelError Level = 8
|
||||
)
|
||||
|
||||
// String returns a name for the level.
|
||||
// If the level has a name, then that name
|
||||
// in uppercase is returned.
|
||||
// If the level is between named values, then
|
||||
// an integer is appended to the uppercased name.
|
||||
// Examples:
|
||||
//
|
||||
// LevelWarn.String() => "WARN"
|
||||
// (LevelInfo+2).String() => "INFO+2"
|
||||
func (l Level) String() string {
|
||||
str := func(base string, val Level) string {
|
||||
if val == 0 {
|
||||
return base
|
||||
}
|
||||
return fmt.Sprintf("%s%+d", base, val)
|
||||
}
|
||||
|
||||
switch {
|
||||
case l < LevelInfo:
|
||||
return str("DEBUG", l-LevelDebug)
|
||||
case l < LevelWarn:
|
||||
return str("INFO", l-LevelInfo)
|
||||
case l < LevelError:
|
||||
return str("WARN", l-LevelWarn)
|
||||
default:
|
||||
return str("ERROR", l-LevelError)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements [encoding/json.Marshaler]
|
||||
// by quoting the output of [Level.String].
|
||||
func (l Level) MarshalJSON() ([]byte, error) {
|
||||
// AppendQuote is sufficient for JSON-encoding all Level strings.
|
||||
// They don't contain any runes that would produce invalid JSON
|
||||
// when escaped.
|
||||
return strconv.AppendQuote(nil, l.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [encoding/json.Unmarshaler]
|
||||
// It accepts any string produced by [Level.MarshalJSON],
|
||||
// ignoring case.
|
||||
// It also accepts numeric offsets that would result in a different string on
|
||||
// output. For example, "Error-8" would marshal as "INFO".
|
||||
func (l *Level) UnmarshalJSON(data []byte) error {
|
||||
s, err := strconv.Unquote(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.parse(s)
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler]
|
||||
// by calling [Level.String].
|
||||
func (l Level) MarshalText() ([]byte, error) {
|
||||
return []byte(l.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It accepts any string produced by [Level.MarshalText],
|
||||
// ignoring case.
|
||||
// It also accepts numeric offsets that would result in a different string on
|
||||
// output. For example, "Error-8" would marshal as "INFO".
|
||||
func (l *Level) UnmarshalText(data []byte) error {
|
||||
return l.parse(string(data))
|
||||
}
|
||||
|
||||
func (l *Level) parse(s string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("slog: level string %q: %w", s, err)
|
||||
}
|
||||
}()
|
||||
|
||||
name := s
|
||||
offset := 0
|
||||
if i := strings.IndexAny(s, "+-"); i >= 0 {
|
||||
name = s[:i]
|
||||
offset, err = strconv.Atoi(s[i:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch strings.ToUpper(name) {
|
||||
case "DEBUG":
|
||||
*l = LevelDebug
|
||||
case "INFO":
|
||||
*l = LevelInfo
|
||||
case "WARN":
|
||||
*l = LevelWarn
|
||||
case "ERROR":
|
||||
*l = LevelError
|
||||
default:
|
||||
return errors.New("unknown name")
|
||||
}
|
||||
*l += Level(offset)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Level returns the receiver.
|
||||
// It implements Leveler.
|
||||
func (l Level) Level() Level { return l }
|
||||
|
||||
// A LevelVar is a Level variable, to allow a Handler level to change
|
||||
// dynamically.
|
||||
// It implements Leveler as well as a Set method,
|
||||
// and it is safe for use by multiple goroutines.
|
||||
// The zero LevelVar corresponds to LevelInfo.
|
||||
type LevelVar struct {
|
||||
val atomic.Int64
|
||||
}
|
||||
|
||||
// Level returns v's level.
|
||||
func (v *LevelVar) Level() Level {
|
||||
return Level(int(v.val.Load()))
|
||||
}
|
||||
|
||||
// Set sets v's level to l.
|
||||
func (v *LevelVar) Set(l Level) {
|
||||
v.val.Store(int64(l))
|
||||
}
|
||||
|
||||
func (v *LevelVar) String() string {
|
||||
return fmt.Sprintf("LevelVar(%s)", v.Level())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler]
|
||||
// by calling [Level.MarshalText].
|
||||
func (v *LevelVar) MarshalText() ([]byte, error) {
|
||||
return v.Level().MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler]
|
||||
// by calling [Level.UnmarshalText].
|
||||
func (v *LevelVar) UnmarshalText(data []byte) error {
|
||||
var l Level
|
||||
if err := l.UnmarshalText(data); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(l)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Leveler provides a Level value.
|
||||
//
|
||||
// As Level itself implements Leveler, clients typically supply
|
||||
// a Level value wherever a Leveler is needed, such as in HandlerOptions.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation such as *LevelVar.
|
||||
type Leveler interface {
|
||||
Level() Level
|
||||
}
|
||||
343
vendor/golang.org/x/exp/slog/logger.go
generated
vendored
Normal file
343
vendor/golang.org/x/exp/slog/logger.go
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog/internal"
|
||||
)
|
||||
|
||||
var defaultLogger atomic.Value
|
||||
|
||||
func init() {
|
||||
defaultLogger.Store(New(newDefaultHandler(log.Output)))
|
||||
}
|
||||
|
||||
// Default returns the default Logger.
|
||||
func Default() *Logger { return defaultLogger.Load().(*Logger) }
|
||||
|
||||
// SetDefault makes l the default Logger.
|
||||
// After this call, output from the log package's default Logger
|
||||
// (as with [log.Print], etc.) will be logged at LevelInfo using l's Handler.
|
||||
func SetDefault(l *Logger) {
|
||||
defaultLogger.Store(l)
|
||||
// If the default's handler is a defaultHandler, then don't use a handleWriter,
|
||||
// or we'll deadlock as they both try to acquire the log default mutex.
|
||||
// The defaultHandler will use whatever the log default writer is currently
|
||||
// set to, which is correct.
|
||||
// This can occur with SetDefault(Default()).
|
||||
// See TestSetDefault.
|
||||
if _, ok := l.Handler().(*defaultHandler); !ok {
|
||||
capturePC := log.Flags()&(log.Lshortfile|log.Llongfile) != 0
|
||||
log.SetOutput(&handlerWriter{l.Handler(), LevelInfo, capturePC})
|
||||
log.SetFlags(0) // we want just the log message, no time or location
|
||||
}
|
||||
}
|
||||
|
||||
// handlerWriter is an io.Writer that calls a Handler.
|
||||
// It is used to link the default log.Logger to the default slog.Logger.
|
||||
type handlerWriter struct {
|
||||
h Handler
|
||||
level Level
|
||||
capturePC bool
|
||||
}
|
||||
|
||||
func (w *handlerWriter) Write(buf []byte) (int, error) {
|
||||
if !w.h.Enabled(context.Background(), w.level) {
|
||||
return 0, nil
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC && w.capturePC {
|
||||
// skip [runtime.Callers, w.Write, Logger.Output, log.Print]
|
||||
var pcs [1]uintptr
|
||||
runtime.Callers(4, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
|
||||
// Remove final newline.
|
||||
origLen := len(buf) // Report that the entire buf was written.
|
||||
if len(buf) > 0 && buf[len(buf)-1] == '\n' {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
r := NewRecord(time.Now(), w.level, string(buf), pc)
|
||||
return origLen, w.h.Handle(context.Background(), r)
|
||||
}
|
||||
|
||||
// A Logger records structured information about each call to its
|
||||
// Log, Debug, Info, Warn, and Error methods.
|
||||
// For each call, it creates a Record and passes it to a Handler.
|
||||
//
|
||||
// To create a new Logger, call [New] or a Logger method
|
||||
// that begins "With".
|
||||
type Logger struct {
|
||||
handler Handler // for structured logging
|
||||
}
|
||||
|
||||
func (l *Logger) clone() *Logger {
|
||||
c := *l
|
||||
return &c
|
||||
}
|
||||
|
||||
// Handler returns l's Handler.
|
||||
func (l *Logger) Handler() Handler { return l.handler }
|
||||
|
||||
// With returns a new Logger that includes the given arguments, converted to
|
||||
// Attrs as in [Logger.Log].
|
||||
// The Attrs will be added to each output from the Logger.
|
||||
// The new Logger shares the old Logger's context.
|
||||
// The new Logger's handler is the result of calling WithAttrs on the receiver's
|
||||
// handler.
|
||||
func (l *Logger) With(args ...any) *Logger {
|
||||
c := l.clone()
|
||||
c.handler = l.handler.WithAttrs(argsToAttrSlice(args))
|
||||
return c
|
||||
}
|
||||
|
||||
// WithGroup returns a new Logger that starts a group. The keys of all
|
||||
// attributes added to the Logger will be qualified by the given name.
|
||||
// (How that qualification happens depends on the [Handler.WithGroup]
|
||||
// method of the Logger's Handler.)
|
||||
// The new Logger shares the old Logger's context.
|
||||
//
|
||||
// The new Logger's handler is the result of calling WithGroup on the receiver's
|
||||
// handler.
|
||||
func (l *Logger) WithGroup(name string) *Logger {
|
||||
c := l.clone()
|
||||
c.handler = l.handler.WithGroup(name)
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
// New creates a new Logger with the given non-nil Handler and a nil context.
|
||||
func New(h Handler) *Logger {
|
||||
if h == nil {
|
||||
panic("nil Handler")
|
||||
}
|
||||
return &Logger{handler: h}
|
||||
}
|
||||
|
||||
// With calls Logger.With on the default logger.
|
||||
func With(args ...any) *Logger {
|
||||
return Default().With(args...)
|
||||
}
|
||||
|
||||
// Enabled reports whether l emits log records at the given context and level.
|
||||
func (l *Logger) Enabled(ctx context.Context, level Level) bool {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return l.Handler().Enabled(ctx, level)
|
||||
}
|
||||
|
||||
// NewLogLogger returns a new log.Logger such that each call to its Output method
|
||||
// dispatches a Record to the specified handler. The logger acts as a bridge from
|
||||
// the older log API to newer structured logging handlers.
|
||||
func NewLogLogger(h Handler, level Level) *log.Logger {
|
||||
return log.New(&handlerWriter{h, level, true}, "", 0)
|
||||
}
|
||||
|
||||
// Log emits a log record with the current time and the given level and message.
|
||||
// The Record's Attrs consist of the Logger's attributes followed by
|
||||
// the Attrs specified by args.
|
||||
//
|
||||
// The attribute arguments are processed as follows:
|
||||
// - If an argument is an Attr, it is used as is.
|
||||
// - If an argument is a string and this is not the last argument,
|
||||
// the following argument is treated as the value and the two are combined
|
||||
// into an Attr.
|
||||
// - Otherwise, the argument is treated as a value with key "!BADKEY".
|
||||
func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
l.log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs.
|
||||
func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
l.logAttrs(ctx, level, msg, attrs...)
|
||||
}
|
||||
|
||||
// Debug logs at LevelDebug.
|
||||
func (l *Logger) Debug(msg string, args ...any) {
|
||||
l.log(nil, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// DebugContext logs at LevelDebug with the given context.
|
||||
func (l *Logger) DebugContext(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// DebugCtx logs at LevelDebug with the given context.
|
||||
// Deprecated: Use Logger.DebugContext.
|
||||
func (l *Logger) DebugCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// Info logs at LevelInfo.
|
||||
func (l *Logger) Info(msg string, args ...any) {
|
||||
l.log(nil, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// InfoContext logs at LevelInfo with the given context.
|
||||
func (l *Logger) InfoContext(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// InfoCtx logs at LevelInfo with the given context.
|
||||
// Deprecated: Use Logger.InfoContext.
|
||||
func (l *Logger) InfoCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// Warn logs at LevelWarn.
|
||||
func (l *Logger) Warn(msg string, args ...any) {
|
||||
l.log(nil, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// WarnContext logs at LevelWarn with the given context.
|
||||
func (l *Logger) WarnContext(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// WarnCtx logs at LevelWarn with the given context.
|
||||
// Deprecated: Use Logger.WarnContext.
|
||||
func (l *Logger) WarnCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// Error logs at LevelError.
|
||||
func (l *Logger) Error(msg string, args ...any) {
|
||||
l.log(nil, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorContext logs at LevelError with the given context.
|
||||
func (l *Logger) ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorCtx logs at LevelError with the given context.
|
||||
// Deprecated: Use Logger.ErrorContext.
|
||||
func (l *Logger) ErrorCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// log is the low-level logging method for methods that take ...any.
|
||||
// It must always be called directly by an exported logging method
|
||||
// or function, because it uses a fixed call depth to obtain the pc.
|
||||
func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
if !l.Enabled(ctx, level) {
|
||||
return
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC {
|
||||
var pcs [1]uintptr
|
||||
// skip [runtime.Callers, this function, this function's caller]
|
||||
runtime.Callers(3, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
r := NewRecord(time.Now(), level, msg, pc)
|
||||
r.Add(args...)
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
_ = l.Handler().Handle(ctx, r)
|
||||
}
|
||||
|
||||
// logAttrs is like [Logger.log], but for methods that take ...Attr.
|
||||
func (l *Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
if !l.Enabled(ctx, level) {
|
||||
return
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC {
|
||||
var pcs [1]uintptr
|
||||
// skip [runtime.Callers, this function, this function's caller]
|
||||
runtime.Callers(3, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
r := NewRecord(time.Now(), level, msg, pc)
|
||||
r.AddAttrs(attrs...)
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
_ = l.Handler().Handle(ctx, r)
|
||||
}
|
||||
|
||||
// Debug calls Logger.Debug on the default logger.
|
||||
func Debug(msg string, args ...any) {
|
||||
Default().log(nil, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// DebugContext calls Logger.DebugContext on the default logger.
|
||||
func DebugContext(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// Info calls Logger.Info on the default logger.
|
||||
func Info(msg string, args ...any) {
|
||||
Default().log(nil, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// InfoContext calls Logger.InfoContext on the default logger.
|
||||
func InfoContext(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// Warn calls Logger.Warn on the default logger.
|
||||
func Warn(msg string, args ...any) {
|
||||
Default().log(nil, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// WarnContext calls Logger.WarnContext on the default logger.
|
||||
func WarnContext(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// Error calls Logger.Error on the default logger.
|
||||
func Error(msg string, args ...any) {
|
||||
Default().log(nil, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorContext calls Logger.ErrorContext on the default logger.
|
||||
func ErrorContext(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// DebugCtx calls Logger.DebugContext on the default logger.
|
||||
// Deprecated: call DebugContext.
|
||||
func DebugCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// InfoCtx calls Logger.InfoContext on the default logger.
|
||||
// Deprecated: call InfoContext.
|
||||
func InfoCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// WarnCtx calls Logger.WarnContext on the default logger.
|
||||
// Deprecated: call WarnContext.
|
||||
func WarnCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorCtx calls Logger.ErrorContext on the default logger.
|
||||
// Deprecated: call ErrorContext.
|
||||
func ErrorCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// Log calls Logger.Log on the default logger.
|
||||
func Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
Default().log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs calls Logger.LogAttrs on the default logger.
|
||||
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
Default().logAttrs(ctx, level, msg, attrs...)
|
||||
}
|
||||
36
vendor/golang.org/x/exp/slog/noplog.bench
generated
vendored
Normal file
36
vendor/golang.org/x/exp/slog/noplog.bench
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: golang.org/x/exp/slog
|
||||
cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
|
||||
BenchmarkNopLog/attrs-8 1000000 1090 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1097 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1078 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1095 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1096 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4007268 308.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4016138 299.7 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4020529 305.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 3977829 303.4 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 3225438 318.5 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1179256 994.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1002 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1216710 993.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1013 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1016 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 989066 1163 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 994116 1163 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 1000000 1152 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 991675 1165 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 965268 1166 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3955503 303.3 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3861188 307.8 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3967752 303.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3955203 302.7 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3948278 301.1 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 940622 1247 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 936381 1257 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 959730 1266 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 943473 1290 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 919414 1259 ns/op 0 B/op 0 allocs/op
|
||||
PASS
|
||||
ok golang.org/x/exp/slog 40.566s
|
||||
207
vendor/golang.org/x/exp/slog/record.go
generated
vendored
Normal file
207
vendor/golang.org/x/exp/slog/record.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const nAttrsInline = 5
|
||||
|
||||
// A Record holds information about a log event.
|
||||
// Copies of a Record share state.
|
||||
// Do not modify a Record after handing out a copy to it.
|
||||
// Use [Record.Clone] to create a copy with no shared state.
|
||||
type Record struct {
|
||||
// The time at which the output method (Log, Info, etc.) was called.
|
||||
Time time.Time
|
||||
|
||||
// The log message.
|
||||
Message string
|
||||
|
||||
// The level of the event.
|
||||
Level Level
|
||||
|
||||
// The program counter at the time the record was constructed, as determined
|
||||
// by runtime.Callers. If zero, no program counter is available.
|
||||
//
|
||||
// The only valid use for this value is as an argument to
|
||||
// [runtime.CallersFrames]. In particular, it must not be passed to
|
||||
// [runtime.FuncForPC].
|
||||
PC uintptr
|
||||
|
||||
// Allocation optimization: an inline array sized to hold
|
||||
// the majority of log calls (based on examination of open-source
|
||||
// code). It holds the start of the list of Attrs.
|
||||
front [nAttrsInline]Attr
|
||||
|
||||
// The number of Attrs in front.
|
||||
nFront int
|
||||
|
||||
// The list of Attrs except for those in front.
|
||||
// Invariants:
|
||||
// - len(back) > 0 iff nFront == len(front)
|
||||
// - Unused array elements are zero. Used to detect mistakes.
|
||||
back []Attr
|
||||
}
|
||||
|
||||
// NewRecord creates a Record from the given arguments.
|
||||
// Use [Record.AddAttrs] to add attributes to the Record.
|
||||
//
|
||||
// NewRecord is intended for logging APIs that want to support a [Handler] as
|
||||
// a backend.
|
||||
func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
|
||||
return Record{
|
||||
Time: t,
|
||||
Message: msg,
|
||||
Level: level,
|
||||
PC: pc,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a copy of the record with no shared state.
|
||||
// The original record and the clone can both be modified
|
||||
// without interfering with each other.
|
||||
func (r Record) Clone() Record {
|
||||
r.back = slices.Clip(r.back) // prevent append from mutating shared array
|
||||
return r
|
||||
}
|
||||
|
||||
// NumAttrs returns the number of attributes in the Record.
|
||||
func (r Record) NumAttrs() int {
|
||||
return r.nFront + len(r.back)
|
||||
}
|
||||
|
||||
// Attrs calls f on each Attr in the Record.
|
||||
// Iteration stops if f returns false.
|
||||
func (r Record) Attrs(f func(Attr) bool) {
|
||||
for i := 0; i < r.nFront; i++ {
|
||||
if !f(r.front[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, a := range r.back {
|
||||
if !f(a) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddAttrs appends the given Attrs to the Record's list of Attrs.
|
||||
func (r *Record) AddAttrs(attrs ...Attr) {
|
||||
n := copy(r.front[r.nFront:], attrs)
|
||||
r.nFront += n
|
||||
// Check if a copy was modified by slicing past the end
|
||||
// and seeing if the Attr there is non-zero.
|
||||
if cap(r.back) > len(r.back) {
|
||||
end := r.back[:len(r.back)+1][len(r.back)]
|
||||
if !end.isEmpty() {
|
||||
panic("copies of a slog.Record were both modified")
|
||||
}
|
||||
}
|
||||
r.back = append(r.back, attrs[n:]...)
|
||||
}
|
||||
|
||||
// Add converts the args to Attrs as described in [Logger.Log],
|
||||
// then appends the Attrs to the Record's list of Attrs.
|
||||
func (r *Record) Add(args ...any) {
|
||||
var a Attr
|
||||
for len(args) > 0 {
|
||||
a, args = argsToAttr(args)
|
||||
if r.nFront < len(r.front) {
|
||||
r.front[r.nFront] = a
|
||||
r.nFront++
|
||||
} else {
|
||||
if r.back == nil {
|
||||
r.back = make([]Attr, 0, countAttrs(args))
|
||||
}
|
||||
r.back = append(r.back, a)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// countAttrs returns the number of Attrs that would be created from args.
|
||||
func countAttrs(args []any) int {
|
||||
n := 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
n++
|
||||
if _, ok := args[i].(string); ok {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
const badKey = "!BADKEY"
|
||||
|
||||
// argsToAttr turns a prefix of the nonempty args slice into an Attr
|
||||
// and returns the unconsumed portion of the slice.
|
||||
// If args[0] is an Attr, it returns it.
|
||||
// If args[0] is a string, it treats the first two elements as
|
||||
// a key-value pair.
|
||||
// Otherwise, it treats args[0] as a value with a missing key.
|
||||
func argsToAttr(args []any) (Attr, []any) {
|
||||
switch x := args[0].(type) {
|
||||
case string:
|
||||
if len(args) == 1 {
|
||||
return String(badKey, x), nil
|
||||
}
|
||||
return Any(x, args[1]), args[2:]
|
||||
|
||||
case Attr:
|
||||
return x, args[1:]
|
||||
|
||||
default:
|
||||
return Any(badKey, x), args[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Source describes the location of a line of source code.
|
||||
type Source struct {
|
||||
// Function is the package path-qualified function name containing the
|
||||
// source line. If non-empty, this string uniquely identifies a single
|
||||
// function in the program. This may be the empty string if not known.
|
||||
Function string `json:"function"`
|
||||
// File and Line are the file name and line number (1-based) of the source
|
||||
// line. These may be the empty string and zero, respectively, if not known.
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
}
|
||||
|
||||
// attrs returns the non-zero fields of s as a slice of attrs.
|
||||
// It is similar to a LogValue method, but we don't want Source
|
||||
// to implement LogValuer because it would be resolved before
|
||||
// the ReplaceAttr function was called.
|
||||
func (s *Source) group() Value {
|
||||
var as []Attr
|
||||
if s.Function != "" {
|
||||
as = append(as, String("function", s.Function))
|
||||
}
|
||||
if s.File != "" {
|
||||
as = append(as, String("file", s.File))
|
||||
}
|
||||
if s.Line != 0 {
|
||||
as = append(as, Int("line", s.Line))
|
||||
}
|
||||
return GroupValue(as...)
|
||||
}
|
||||
|
||||
// source returns a Source for the log event.
|
||||
// If the Record was created without the necessary information,
|
||||
// or if the location is unavailable, it returns a non-nil *Source
|
||||
// with zero fields.
|
||||
func (r Record) source() *Source {
|
||||
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||
f, _ := fs.Next()
|
||||
return &Source{
|
||||
Function: f.Function,
|
||||
File: f.File,
|
||||
Line: f.Line,
|
||||
}
|
||||
}
|
||||
161
vendor/golang.org/x/exp/slog/text_handler.go
generated
vendored
Normal file
161
vendor/golang.org/x/exp/slog/text_handler.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// TextHandler is a Handler that writes Records to an io.Writer as a
|
||||
// sequence of key=value pairs separated by spaces and followed by a newline.
|
||||
type TextHandler struct {
|
||||
*commonHandler
|
||||
}
|
||||
|
||||
// NewTextHandler creates a TextHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &TextHandler{
|
||||
&commonHandler{
|
||||
json: false,
|
||||
w: w,
|
||||
opts: *opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
|
||||
return h.commonHandler.enabled(level)
|
||||
}
|
||||
|
||||
// WithAttrs returns a new TextHandler whose attributes consists
|
||||
// of h's attributes followed by attrs.
|
||||
func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
|
||||
return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
|
||||
}
|
||||
|
||||
func (h *TextHandler) WithGroup(name string) Handler {
|
||||
return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
|
||||
}
|
||||
|
||||
// Handle formats its argument Record as a single line of space-separated
|
||||
// key=value items.
|
||||
//
|
||||
// If the Record's time is zero, the time is omitted.
|
||||
// Otherwise, the key is "time"
|
||||
// and the value is output in RFC3339 format with millisecond precision.
|
||||
//
|
||||
// If the Record's level is zero, the level is omitted.
|
||||
// Otherwise, the key is "level"
|
||||
// and the value of [Level.String] is output.
|
||||
//
|
||||
// If the AddSource option is set and source information is available,
|
||||
// the key is "source" and the value is output as FILE:LINE.
|
||||
//
|
||||
// The message's key is "msg".
|
||||
//
|
||||
// To modify these or other attributes, or remove them from the output, use
|
||||
// [HandlerOptions.ReplaceAttr].
|
||||
//
|
||||
// If a value implements [encoding.TextMarshaler], the result of MarshalText is
|
||||
// written. Otherwise, the result of fmt.Sprint is written.
|
||||
//
|
||||
// Keys and values are quoted with [strconv.Quote] if they contain Unicode space
|
||||
// characters, non-printing characters, '"' or '='.
|
||||
//
|
||||
// Keys inside groups consist of components (keys or group names) separated by
|
||||
// dots. No further escaping is performed.
|
||||
// Thus there is no way to determine from the key "a.b.c" whether there
|
||||
// are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
|
||||
// or single group "a" and a key "b.c".
|
||||
// If it is necessary to reconstruct the group structure of a key
|
||||
// even in the presence of dots inside components, use
|
||||
// [HandlerOptions.ReplaceAttr] to encode that information in the key.
|
||||
//
|
||||
// Each call to Handle results in a single serialized call to
|
||||
// io.Writer.Write.
|
||||
func (h *TextHandler) Handle(_ context.Context, r Record) error {
|
||||
return h.commonHandler.handle(r)
|
||||
}
|
||||
|
||||
func appendTextValue(s *handleState, v Value) error {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
s.appendString(v.str())
|
||||
case KindTime:
|
||||
s.appendTime(v.time())
|
||||
case KindAny:
|
||||
if tm, ok := v.any.(encoding.TextMarshaler); ok {
|
||||
data, err := tm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: avoid the conversion to string.
|
||||
s.appendString(string(data))
|
||||
return nil
|
||||
}
|
||||
if bs, ok := byteSlice(v.any); ok {
|
||||
// As of Go 1.19, this only allocates for strings longer than 32 bytes.
|
||||
s.buf.WriteString(strconv.Quote(string(bs)))
|
||||
return nil
|
||||
}
|
||||
s.appendString(fmt.Sprintf("%+v", v.Any()))
|
||||
default:
|
||||
*s.buf = v.append(*s.buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// byteSlice returns its argument as a []byte if the argument's
|
||||
// underlying type is []byte, along with a second return value of true.
|
||||
// Otherwise it returns nil, false.
|
||||
func byteSlice(a any) ([]byte, bool) {
|
||||
if bs, ok := a.([]byte); ok {
|
||||
return bs, true
|
||||
}
|
||||
// Like Printf's %s, we allow both the slice type and the byte element type to be named.
|
||||
t := reflect.TypeOf(a)
|
||||
if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||||
return reflect.ValueOf(a).Bytes(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func needsQuoting(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
b := s[i]
|
||||
if b < utf8.RuneSelf {
|
||||
// Quote anything except a backslash that would need quoting in a
|
||||
// JSON string, as well as space and '='
|
||||
if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
|
||||
return true
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
||||
return true
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return false
|
||||
}
|
||||
456
vendor/golang.org/x/exp/slog/value.go
generated
vendored
Normal file
456
vendor/golang.org/x/exp/slog/value.go
generated
vendored
Normal file
@@ -0,0 +1,456 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// A Value can represent any Go value, but unlike type any,
|
||||
// it can represent most small values without an allocation.
|
||||
// The zero Value corresponds to nil.
|
||||
type Value struct {
|
||||
_ [0]func() // disallow ==
|
||||
// num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
|
||||
// the string length for KindString, and nanoseconds since the epoch for KindTime.
|
||||
num uint64
|
||||
// If any is of type Kind, then the value is in num as described above.
|
||||
// If any is of type *time.Location, then the Kind is Time and time.Time value
|
||||
// can be constructed from the Unix nanos in num and the location (monotonic time
|
||||
// is not preserved).
|
||||
// If any is of type stringptr, then the Kind is String and the string value
|
||||
// consists of the length in num and the pointer in any.
|
||||
// Otherwise, the Kind is Any and any is the value.
|
||||
// (This implies that Attrs cannot store values of type Kind, *time.Location
|
||||
// or stringptr.)
|
||||
any any
|
||||
}
|
||||
|
||||
// Kind is the kind of a Value.
|
||||
type Kind int
|
||||
|
||||
// The following list is sorted alphabetically, but it's also important that
|
||||
// KindAny is 0 so that a zero Value represents nil.
|
||||
|
||||
const (
|
||||
KindAny Kind = iota
|
||||
KindBool
|
||||
KindDuration
|
||||
KindFloat64
|
||||
KindInt64
|
||||
KindString
|
||||
KindTime
|
||||
KindUint64
|
||||
KindGroup
|
||||
KindLogValuer
|
||||
)
|
||||
|
||||
var kindStrings = []string{
|
||||
"Any",
|
||||
"Bool",
|
||||
"Duration",
|
||||
"Float64",
|
||||
"Int64",
|
||||
"String",
|
||||
"Time",
|
||||
"Uint64",
|
||||
"Group",
|
||||
"LogValuer",
|
||||
}
|
||||
|
||||
func (k Kind) String() string {
|
||||
if k >= 0 && int(k) < len(kindStrings) {
|
||||
return kindStrings[k]
|
||||
}
|
||||
return "<unknown slog.Kind>"
|
||||
}
|
||||
|
||||
// Unexported version of Kind, just so we can store Kinds in Values.
|
||||
// (No user-provided value has this type.)
|
||||
type kind Kind
|
||||
|
||||
// Kind returns v's Kind.
|
||||
func (v Value) Kind() Kind {
|
||||
switch x := v.any.(type) {
|
||||
case Kind:
|
||||
return x
|
||||
case stringptr:
|
||||
return KindString
|
||||
case timeLocation:
|
||||
return KindTime
|
||||
case groupptr:
|
||||
return KindGroup
|
||||
case LogValuer:
|
||||
return KindLogValuer
|
||||
case kind: // a kind is just a wrapper for a Kind
|
||||
return KindAny
|
||||
default:
|
||||
return KindAny
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Constructors
|
||||
|
||||
// IntValue returns a Value for an int.
|
||||
func IntValue(v int) Value {
|
||||
return Int64Value(int64(v))
|
||||
}
|
||||
|
||||
// Int64Value returns a Value for an int64.
|
||||
func Int64Value(v int64) Value {
|
||||
return Value{num: uint64(v), any: KindInt64}
|
||||
}
|
||||
|
||||
// Uint64Value returns a Value for a uint64.
|
||||
func Uint64Value(v uint64) Value {
|
||||
return Value{num: v, any: KindUint64}
|
||||
}
|
||||
|
||||
// Float64Value returns a Value for a floating-point number.
|
||||
func Float64Value(v float64) Value {
|
||||
return Value{num: math.Float64bits(v), any: KindFloat64}
|
||||
}
|
||||
|
||||
// BoolValue returns a Value for a bool.
|
||||
func BoolValue(v bool) Value {
|
||||
u := uint64(0)
|
||||
if v {
|
||||
u = 1
|
||||
}
|
||||
return Value{num: u, any: KindBool}
|
||||
}
|
||||
|
||||
// Unexported version of *time.Location, just so we can store *time.Locations in
|
||||
// Values. (No user-provided value has this type.)
|
||||
type timeLocation *time.Location
|
||||
|
||||
// TimeValue returns a Value for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func TimeValue(v time.Time) Value {
|
||||
if v.IsZero() {
|
||||
// UnixNano on the zero time is undefined, so represent the zero time
|
||||
// with a nil *time.Location instead. time.Time.Location method never
|
||||
// returns nil, so a Value with any == timeLocation(nil) cannot be
|
||||
// mistaken for any other Value, time.Time or otherwise.
|
||||
return Value{any: timeLocation(nil)}
|
||||
}
|
||||
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
|
||||
}
|
||||
|
||||
// DurationValue returns a Value for a time.Duration.
|
||||
func DurationValue(v time.Duration) Value {
|
||||
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
|
||||
}
|
||||
|
||||
// AnyValue returns a Value for the supplied value.
|
||||
//
|
||||
// If the supplied value is of type Value, it is returned
|
||||
// unmodified.
|
||||
//
|
||||
// Given a value of one of Go's predeclared string, bool, or
|
||||
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||
// original numeric type is not preserved.
|
||||
//
|
||||
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||
//
|
||||
// For nil, or values of all other types, including named types whose
|
||||
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||
func AnyValue(v any) Value {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return StringValue(v)
|
||||
case int:
|
||||
return Int64Value(int64(v))
|
||||
case uint:
|
||||
return Uint64Value(uint64(v))
|
||||
case int64:
|
||||
return Int64Value(v)
|
||||
case uint64:
|
||||
return Uint64Value(v)
|
||||
case bool:
|
||||
return BoolValue(v)
|
||||
case time.Duration:
|
||||
return DurationValue(v)
|
||||
case time.Time:
|
||||
return TimeValue(v)
|
||||
case uint8:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint16:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint32:
|
||||
return Uint64Value(uint64(v))
|
||||
case uintptr:
|
||||
return Uint64Value(uint64(v))
|
||||
case int8:
|
||||
return Int64Value(int64(v))
|
||||
case int16:
|
||||
return Int64Value(int64(v))
|
||||
case int32:
|
||||
return Int64Value(int64(v))
|
||||
case float64:
|
||||
return Float64Value(v)
|
||||
case float32:
|
||||
return Float64Value(float64(v))
|
||||
case []Attr:
|
||||
return GroupValue(v...)
|
||||
case Kind:
|
||||
return Value{any: kind(v)}
|
||||
case Value:
|
||||
return v
|
||||
default:
|
||||
return Value{any: v}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Accessors
|
||||
|
||||
// Any returns v's value as an any.
|
||||
func (v Value) Any() any {
|
||||
switch v.Kind() {
|
||||
case KindAny:
|
||||
if k, ok := v.any.(kind); ok {
|
||||
return Kind(k)
|
||||
}
|
||||
return v.any
|
||||
case KindLogValuer:
|
||||
return v.any
|
||||
case KindGroup:
|
||||
return v.group()
|
||||
case KindInt64:
|
||||
return int64(v.num)
|
||||
case KindUint64:
|
||||
return v.num
|
||||
case KindFloat64:
|
||||
return v.float()
|
||||
case KindString:
|
||||
return v.str()
|
||||
case KindBool:
|
||||
return v.bool()
|
||||
case KindDuration:
|
||||
return v.duration()
|
||||
case KindTime:
|
||||
return v.time()
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// Int64 returns v's value as an int64. It panics
|
||||
// if v is not a signed integer.
|
||||
func (v Value) Int64() int64 {
|
||||
if g, w := v.Kind(), KindInt64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return int64(v.num)
|
||||
}
|
||||
|
||||
// Uint64 returns v's value as a uint64. It panics
|
||||
// if v is not an unsigned integer.
|
||||
func (v Value) Uint64() uint64 {
|
||||
if g, w := v.Kind(), KindUint64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.num
|
||||
}
|
||||
|
||||
// Bool returns v's value as a bool. It panics
|
||||
// if v is not a bool.
|
||||
func (v Value) Bool() bool {
|
||||
if g, w := v.Kind(), KindBool; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.bool()
|
||||
}
|
||||
|
||||
func (v Value) bool() bool {
|
||||
return v.num == 1
|
||||
}
|
||||
|
||||
// Duration returns v's value as a time.Duration. It panics
|
||||
// if v is not a time.Duration.
|
||||
func (v Value) Duration() time.Duration {
|
||||
if g, w := v.Kind(), KindDuration; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return v.duration()
|
||||
}
|
||||
|
||||
func (v Value) duration() time.Duration {
|
||||
return time.Duration(int64(v.num))
|
||||
}
|
||||
|
||||
// Float64 returns v's value as a float64. It panics
|
||||
// if v is not a float64.
|
||||
func (v Value) Float64() float64 {
|
||||
if g, w := v.Kind(), KindFloat64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return v.float()
|
||||
}
|
||||
|
||||
func (v Value) float() float64 {
|
||||
return math.Float64frombits(v.num)
|
||||
}
|
||||
|
||||
// Time returns v's value as a time.Time. It panics
|
||||
// if v is not a time.Time.
|
||||
func (v Value) Time() time.Time {
|
||||
if g, w := v.Kind(), KindTime; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.time()
|
||||
}
|
||||
|
||||
func (v Value) time() time.Time {
|
||||
loc := v.any.(timeLocation)
|
||||
if loc == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(0, int64(v.num)).In(loc)
|
||||
}
|
||||
|
||||
// LogValuer returns v's value as a LogValuer. It panics
|
||||
// if v is not a LogValuer.
|
||||
func (v Value) LogValuer() LogValuer {
|
||||
return v.any.(LogValuer)
|
||||
}
|
||||
|
||||
// Group returns v's value as a []Attr.
|
||||
// It panics if v's Kind is not KindGroup.
|
||||
func (v Value) Group() []Attr {
|
||||
if sp, ok := v.any.(groupptr); ok {
|
||||
return unsafe.Slice((*Attr)(sp), v.num)
|
||||
}
|
||||
panic("Group: bad kind")
|
||||
}
|
||||
|
||||
func (v Value) group() []Attr {
|
||||
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
|
||||
}
|
||||
|
||||
//////////////// Other
|
||||
|
||||
// Equal reports whether v and w represent the same Go value.
|
||||
func (v Value) Equal(w Value) bool {
|
||||
k1 := v.Kind()
|
||||
k2 := w.Kind()
|
||||
if k1 != k2 {
|
||||
return false
|
||||
}
|
||||
switch k1 {
|
||||
case KindInt64, KindUint64, KindBool, KindDuration:
|
||||
return v.num == w.num
|
||||
case KindString:
|
||||
return v.str() == w.str()
|
||||
case KindFloat64:
|
||||
return v.float() == w.float()
|
||||
case KindTime:
|
||||
return v.time().Equal(w.time())
|
||||
case KindAny, KindLogValuer:
|
||||
return v.any == w.any // may panic if non-comparable
|
||||
case KindGroup:
|
||||
return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", k1))
|
||||
}
|
||||
}
|
||||
|
||||
// append appends a text representation of v to dst.
|
||||
// v is formatted as with fmt.Sprint.
|
||||
func (v Value) append(dst []byte) []byte {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
return append(dst, v.str()...)
|
||||
case KindInt64:
|
||||
return strconv.AppendInt(dst, int64(v.num), 10)
|
||||
case KindUint64:
|
||||
return strconv.AppendUint(dst, v.num, 10)
|
||||
case KindFloat64:
|
||||
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
|
||||
case KindBool:
|
||||
return strconv.AppendBool(dst, v.bool())
|
||||
case KindDuration:
|
||||
return append(dst, v.duration().String()...)
|
||||
case KindTime:
|
||||
return append(dst, v.time().String()...)
|
||||
case KindGroup:
|
||||
return fmt.Append(dst, v.group())
|
||||
case KindAny, KindLogValuer:
|
||||
return fmt.Append(dst, v.any)
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// A LogValuer is any Go value that can convert itself into a Value for logging.
|
||||
//
|
||||
// This mechanism may be used to defer expensive operations until they are
|
||||
// needed, or to expand a single value into a sequence of components.
|
||||
type LogValuer interface {
|
||||
LogValue() Value
|
||||
}
|
||||
|
||||
const maxLogValues = 100
|
||||
|
||||
// Resolve repeatedly calls LogValue on v while it implements LogValuer,
|
||||
// and returns the result.
|
||||
// If v resolves to a group, the group's attributes' values are not recursively
|
||||
// resolved.
|
||||
// If the number of LogValue calls exceeds a threshold, a Value containing an
|
||||
// error is returned.
|
||||
// Resolve's return value is guaranteed not to be of Kind KindLogValuer.
|
||||
func (v Value) Resolve() (rv Value) {
|
||||
orig := v
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rv = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5)))
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < maxLogValues; i++ {
|
||||
if v.Kind() != KindLogValuer {
|
||||
return v
|
||||
}
|
||||
v = v.LogValuer().LogValue()
|
||||
}
|
||||
err := fmt.Errorf("LogValue called too many times on Value of type %T", orig.Any())
|
||||
return AnyValue(err)
|
||||
}
|
||||
|
||||
func stack(skip, nFrames int) string {
|
||||
pcs := make([]uintptr, nFrames+1)
|
||||
n := runtime.Callers(skip+1, pcs)
|
||||
if n == 0 {
|
||||
return "(no stack)"
|
||||
}
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
var b strings.Builder
|
||||
i := 0
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
fmt.Fprintf(&b, "called from %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
i++
|
||||
if i >= nFrames {
|
||||
fmt.Fprintf(&b, "(rest of stack elided)\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
53
vendor/golang.org/x/exp/slog/value_119.go
generated
vendored
Normal file
53
vendor/golang.org/x/exp/slog/value_119.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.19 && !go1.20
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
stringptr unsafe.Pointer // used in Value.any when the Value is a string
|
||||
groupptr unsafe.Pointer // used in Value.any when the Value is a []Attr
|
||||
)
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&value))
|
||||
return Value{num: uint64(hdr.Len), any: stringptr(hdr.Data)}
|
||||
}
|
||||
|
||||
func (v Value) str() string {
|
||||
var s string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = uintptr(v.any.(stringptr))
|
||||
hdr.Len = int(v.num)
|
||||
return s
|
||||
}
|
||||
|
||||
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||
// wrong kind, String never panics.
|
||||
func (v Value) String() string {
|
||||
if sp, ok := v.any.(stringptr); ok {
|
||||
// Inlining this code makes a huge difference.
|
||||
var s string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = uintptr(sp)
|
||||
hdr.Len = int(v.num)
|
||||
return s
|
||||
}
|
||||
return string(v.append(nil))
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&as))
|
||||
return Value{num: uint64(hdr.Len), any: groupptr(hdr.Data)}
|
||||
}
|
||||
39
vendor/golang.org/x/exp/slog/value_120.go
generated
vendored
Normal file
39
vendor/golang.org/x/exp/slog/value_120.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package slog
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type (
|
||||
stringptr *byte // used in Value.any when the Value is a string
|
||||
groupptr *Attr // used in Value.any when the Value is a []Attr
|
||||
)
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
|
||||
}
|
||||
|
||||
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||
// wrong kind, String never panics.
|
||||
func (v Value) String() string {
|
||||
if sp, ok := v.any.(stringptr); ok {
|
||||
return unsafe.String(sp, v.num)
|
||||
}
|
||||
return string(v.append(nil))
|
||||
}
|
||||
|
||||
func (v Value) str() string {
|
||||
return unsafe.String(v.any.(stringptr), v.num)
|
||||
}
|
||||
17
vendor/modules.txt
vendored
17
vendor/modules.txt
vendored
@@ -261,9 +261,6 @@ github.com/blevesearch/zapx/v16
|
||||
# github.com/bluele/gcache v0.0.2
|
||||
## explicit; go 1.15
|
||||
github.com/bluele/gcache
|
||||
# github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f
|
||||
## explicit
|
||||
github.com/bmizerany/pat
|
||||
# github.com/bombsimon/logrusr/v3 v3.1.0
|
||||
## explicit; go 1.17
|
||||
github.com/bombsimon/logrusr/v3
|
||||
@@ -370,7 +367,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
|
||||
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
|
||||
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
|
||||
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
|
||||
# github.com/cs3org/reva/v2 v2.22.1-0.20240730105121-548644c31544
|
||||
# github.com/cs3org/reva/v2 v2.22.1-0.20240806075425-8bcdd93dfa20
|
||||
## explicit; go 1.21
|
||||
github.com/cs3org/reva/v2/cmd/revad/internal/grace
|
||||
github.com/cs3org/reva/v2/cmd/revad/runtime
|
||||
@@ -659,6 +656,7 @@ github.com/cs3org/reva/v2/pkg/storage/fs/eos
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpc
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpchome
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/eoshome
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/hello
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/loader
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/local
|
||||
github.com/cs3org/reva/v2/pkg/storage/fs/localhome
|
||||
@@ -1099,6 +1097,8 @@ github.com/golang/snappy
|
||||
# github.com/gomodule/redigo v1.8.9
|
||||
## explicit; go 1.16
|
||||
github.com/gomodule/redigo/redis
|
||||
# github.com/google/flatbuffers v2.0.8+incompatible
|
||||
## explicit
|
||||
# github.com/google/go-cmp v0.6.0
|
||||
## explicit; go 1.13
|
||||
github.com/google/go-cmp/cmp
|
||||
@@ -1881,9 +1881,9 @@ github.com/trustelem/zxcvbn/internal/mathutils
|
||||
github.com/trustelem/zxcvbn/match
|
||||
github.com/trustelem/zxcvbn/matching
|
||||
github.com/trustelem/zxcvbn/scoring
|
||||
# github.com/tus/tusd v1.13.0
|
||||
## explicit; go 1.16
|
||||
github.com/tus/tusd/pkg/handler
|
||||
# github.com/tus/tusd/v2 v2.4.0
|
||||
## explicit; go 1.20
|
||||
github.com/tus/tusd/v2/pkg/handler
|
||||
# github.com/unrolled/secure v1.14.0 => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c
|
||||
## explicit; go 1.13
|
||||
github.com/unrolled/secure
|
||||
@@ -2156,6 +2156,9 @@ golang.org/x/crypto/ssh/knownhosts
|
||||
golang.org/x/exp/constraints
|
||||
golang.org/x/exp/maps
|
||||
golang.org/x/exp/slices
|
||||
golang.org/x/exp/slog
|
||||
golang.org/x/exp/slog/internal
|
||||
golang.org/x/exp/slog/internal/buffer
|
||||
# golang.org/x/image v0.18.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/image/bmp
|
||||
|
||||
Reference in New Issue
Block a user