Merge pull request #9714 from kobergj/BumpTusdPkg

[full-ci] Bump Reva & Tusd
This commit is contained in:
kobergj
2024-08-07 09:38:17 +02:00
committed by GitHub
62 changed files with 4737 additions and 2467 deletions

View File

@@ -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

View 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
View File

@@ -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

1346
go.sum
View File

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -1,3 +0,0 @@
*.prof
*.out
example/example

View File

@@ -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.

View File

@@ -1,82 +0,0 @@
# pat (formerly pat.go) - A Sinatra style pattern muxer for Go's net/http library
[![GoDoc](https://godoc.org/github.com/bmizerany/pat?status.svg)](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.

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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"
)

View File

@@ -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())
}

View 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
}

View File

@@ -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...)

View File

@@ -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 }

View File

@@ -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)

View 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
}

View 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")
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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")

View File

@@ -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())

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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))
}

View 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()
}

View File

@@ -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
View 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
}

View File

@@ -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
}

View File

@@ -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
View 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
View 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
View 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
View 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
}

View File

@@ -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()

102
vendor/golang.org/x/exp/slog/attr.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -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