initial commit
This commit is contained in:
commit
3053f89410
39 changed files with 4567 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/cachefs
|
||||
56
Makefile
Normal file
56
Makefile
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
CC=go
|
||||
BUILD=build -v -trimpath
|
||||
VERSION=$(shell cat VERSION)
|
||||
GCFLAGS=-gcflags '-e'
|
||||
LDFLAGS=-ldflags '-buildid=$(VERSION) -X main.version=$(VERSION) -s -w'
|
||||
PROGRAM=cachefs
|
||||
CMD=-o $(PROGRAM) cmd/$(PROGRAM)/main.go
|
||||
ENV=CGO_ENABLED=0 GO111MODULE=on
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
setup:
|
||||
$(CC) install github.com/client9/misspell/cmd/misspell@latest
|
||||
$(CC) install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
|
||||
release: clean gofmt codeqa
|
||||
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD)
|
||||
|
||||
release-vendor: clean gofmt codeqa
|
||||
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD)
|
||||
|
||||
vendor: clean gofmt
|
||||
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD)
|
||||
|
||||
$(PROGRAM): clean gofmt
|
||||
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD)
|
||||
|
||||
clean:
|
||||
$(CC) clean -x
|
||||
|
||||
gofmt:
|
||||
$(CC) fmt ./...
|
||||
|
||||
codeqa: govet misspell staticcheck test
|
||||
|
||||
govet:
|
||||
$(CC) vet ./...
|
||||
|
||||
misspell:
|
||||
$(GOPATH)/bin/misspell cmd/* pkg/* Makefile README.md sri.sh
|
||||
|
||||
staticcheck:
|
||||
$(GOPATH)/bin/staticcheck ./...
|
||||
|
||||
test:
|
||||
ifeq ($(shell go env GOARCH), $(shell go env GOHOSTARCH))
|
||||
ifeq ($(shell go env GOOS), $(shell go env GOHOSTOS))
|
||||
$(CC) test ./...
|
||||
else
|
||||
$(info skipping tests of other platforms)
|
||||
endif
|
||||
else
|
||||
$(info skipping tests of other platforms)
|
||||
endif
|
||||
|
||||
.PHONY: setup release release-vendor vendor build clean codeqa gofmt govet misspell staticcheck test
|
||||
2
TODO.txt
Normal file
2
TODO.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
* add disk quota
|
||||
* add LRU cache
|
||||
1
VERSION
Normal file
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.0
|
||||
64
cmd/cachefs/main.go
Normal file
64
cmd/cachefs/main.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"cachefs/pkg/fs"
|
||||
"cachefs/pkg/srv"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
|
||||
src string
|
||||
dst string
|
||||
metadata string
|
||||
listen string
|
||||
|
||||
log logr.Logger
|
||||
)
|
||||
|
||||
func main() {
|
||||
klog.InitFlags(nil)
|
||||
flag.StringVar(&src, "src", "", "root directory (NFS share)")
|
||||
flag.StringVar(&dst, "dst", "", "local directory to cache files")
|
||||
flag.StringVar(&metadata, "data", "", "path to metadata file")
|
||||
flag.StringVar(&listen, "listen", "127.0.0.1:8080", "listen addr:port")
|
||||
flag.Parse()
|
||||
|
||||
log = klogr.New().WithName("main")
|
||||
log.Info("starting cachefs", "version", version)
|
||||
|
||||
fs, err := fs.NewFS(src, dst, metadata, klogr.New().WithName("fs"))
|
||||
if err != nil {
|
||||
klog.Fatalf("init failed: %s", err)
|
||||
}
|
||||
s := &http.Server{Addr: listen, Handler: srv.NewFileServer(fs, klogr.New().WithName("server"))}
|
||||
go func() {
|
||||
err := s.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
klog.Fatalf("init failed: %s", err)
|
||||
}
|
||||
}()
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
log.Info("cachefs shutdown initiated")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
s.Shutdown(ctx)
|
||||
fs.Close()
|
||||
cancel()
|
||||
log.Info("cachefs shutdown completed")
|
||||
}
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module cachefs
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.2
|
||||
k8s.io/klog/v2 v2.40.1
|
||||
)
|
||||
5
go.sum
Normal file
5
go.sum
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
|
||||
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
52
pkg/fs/chunk.go
Normal file
52
pkg/fs/chunk.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package fs
|
||||
|
||||
import "sort"
|
||||
|
||||
type Chunk [2]int64
|
||||
|
||||
type Chunks []Chunk
|
||||
|
||||
func (cs Chunks) Len() int { return len(cs) }
|
||||
func (cs Chunks) Less(i, j int) bool { return cs[i][0] < cs[j][0] }
|
||||
func (cs Chunks) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
|
||||
|
||||
func (cs Chunks) Exists(off int64, n int, size int64) bool {
|
||||
end := off + int64(n)
|
||||
if end > size {
|
||||
end = size
|
||||
}
|
||||
for _, c := range cs {
|
||||
if c[0] <= off && c[1] > off && c[1] >= end {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cs *Chunks) Add(off int64, n int) {
|
||||
*cs = append(*cs, Chunk{off, off + int64(n)})
|
||||
cs.merge()
|
||||
}
|
||||
|
||||
func (cs *Chunks) merge() {
|
||||
c := *cs
|
||||
if len(c) < 2 {
|
||||
return
|
||||
}
|
||||
sort.Sort(*cs)
|
||||
for i := 0; i < len(c); i++ {
|
||||
if i+1 == len(c) {
|
||||
break
|
||||
}
|
||||
if c[i][1] >= c[i+1][0] {
|
||||
if c[i+1][1] > c[i][1] {
|
||||
c[i][1] = c[i+1][1]
|
||||
}
|
||||
c = append(c[:i+1], c[i+2:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
*cs = c
|
||||
}
|
||||
106
pkg/fs/file.go
Normal file
106
pkg/fs/file.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
log logr.Logger
|
||||
f *os.File
|
||||
md *Metadata
|
||||
offset int64
|
||||
}
|
||||
|
||||
type preload struct {
|
||||
f *File
|
||||
skipped int64
|
||||
written int64
|
||||
}
|
||||
|
||||
func (p *preload) Read(data []byte) (n int, err error) {
|
||||
if p.f.offset >= p.f.size() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = len(data)
|
||||
if p.f.hasChunk(n) {
|
||||
p.skipped++
|
||||
_, err = p.f.Seek(int64(n), io.SeekCurrent)
|
||||
return
|
||||
}
|
||||
n, err = p.f.readWithoutCache(data)
|
||||
p.written++
|
||||
return
|
||||
}
|
||||
|
||||
func (f *File) Preload(unlock func()) {
|
||||
f.log.V(2).Info("preload started")
|
||||
p := &preload{f: f}
|
||||
_, err := io.Copy(io.Discard, p)
|
||||
if err != nil && err != io.EOF {
|
||||
f.log.Error(err, "error preloading file")
|
||||
}
|
||||
f.log.V(2).Info("preload finished", "skipped", p.skipped, "written", p.written)
|
||||
unlock()
|
||||
}
|
||||
|
||||
func (f *File) Read(p []byte) (n int, err error) {
|
||||
if f.hasChunk(len(p)) {
|
||||
n, err = f.md.ReadAt(p, f.offset)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f.Seek(int64(n), io.SeekCurrent)
|
||||
return
|
||||
}
|
||||
return f.readWithoutCache(p)
|
||||
}
|
||||
|
||||
func (f *File) size() int64 {
|
||||
return f.md.Size
|
||||
}
|
||||
|
||||
func (f *File) hasChunk(n int) bool {
|
||||
return f.md.HasChunk(f.offset, n)
|
||||
}
|
||||
|
||||
func (f *File) readWithoutCache(p []byte) (n int, err error) {
|
||||
n, err = f.f.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.md.WriteAt(p, f.offset)
|
||||
f.md.AddChunk(f.offset, n)
|
||||
f.offset += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *File) Seek(offset int64, whence int) (n int64, err error) {
|
||||
n, err = f.f.Seek(offset, whence)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
f.offset = offset
|
||||
case io.SeekCurrent:
|
||||
f.offset += offset
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||
return f.f.Readdir(count)
|
||||
}
|
||||
|
||||
func (f *File) Stat() (os.FileInfo, error) {
|
||||
return f.f.Stat()
|
||||
}
|
||||
|
||||
func (f *File) Close() error {
|
||||
return f.f.Close()
|
||||
}
|
||||
225
pkg/fs/fs.go
Normal file
225
pkg/fs/fs.go
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
stdfs "io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
type FS struct {
|
||||
mu sync.RWMutex
|
||||
log logr.Logger
|
||||
NoCache http.Handler
|
||||
src string
|
||||
dst string
|
||||
mdf *os.File
|
||||
jenc *json.Encoder
|
||||
mm map[string]*Metadata
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) {
|
||||
if !filepath.IsAbs(src) {
|
||||
return nil, errors.New("src path is not absolute")
|
||||
}
|
||||
if !filepath.IsAbs(dst) {
|
||||
return nil, errors.New("dst path is not absolute")
|
||||
}
|
||||
if !filepath.IsAbs(metadata) {
|
||||
return nil, errors.New("metadata path is not absolute")
|
||||
}
|
||||
if src == dst {
|
||||
return nil, errors.New("src and dst path can not be equal")
|
||||
}
|
||||
mdf, err := os.OpenFile(metadata, os.O_RDWR|os.O_CREATE, 0o640)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fs = &FS{
|
||||
log: log,
|
||||
NoCache: http.FileServer(http.Dir(src)),
|
||||
src: src,
|
||||
dst: dst,
|
||||
mdf: mdf,
|
||||
mm: make(map[string]*Metadata),
|
||||
jenc: json.NewEncoder(mdf),
|
||||
}
|
||||
err = json.NewDecoder(mdf).Decode(&fs.mm)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
for k := range fs.mm {
|
||||
_, err = stat(fs.dst, k)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
delete(fs.mm, k)
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
fs.cancel = cancel
|
||||
go fs.flusher(ctx)
|
||||
return fs, err
|
||||
}
|
||||
|
||||
func (fs *FS) Stat(name string) (stdfs.FileInfo, error) {
|
||||
return stat(fs.src, name)
|
||||
}
|
||||
|
||||
func (fs *FS) Open(name string) (http.File, error) {
|
||||
return fs.open(name)
|
||||
}
|
||||
|
||||
func (fs *FS) Preload(name string) {
|
||||
f, err := fs.open(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if f.md.Preload() {
|
||||
fs.log.WithValues("file", name).Error(errors.New("preload is running"), "error starting preload")
|
||||
return
|
||||
}
|
||||
unlock := func() { f.md.UnlockPreload() }
|
||||
go f.Preload(unlock)
|
||||
}
|
||||
|
||||
func skipLog(name string) bool {
|
||||
return strings.HasSuffix(name, "index.html")
|
||||
}
|
||||
|
||||
func (fs *FS) open(name string) (f *File, err error) {
|
||||
log := fs.log.WithValues("file", name)
|
||||
rp := filepath.Join(fs.src, filepath.FromSlash(path.Clean("/"+name)))
|
||||
rf, err := os.Open(rp)
|
||||
if err != nil {
|
||||
if !skipLog(name) {
|
||||
log.Error(err, "error opening source file")
|
||||
}
|
||||
return
|
||||
}
|
||||
rfi, err := rf.Stat()
|
||||
if err != nil {
|
||||
log.Error(err, "error stat source file")
|
||||
return
|
||||
}
|
||||
if rfi.IsDir() {
|
||||
return &File{f: rf}, nil
|
||||
}
|
||||
mp := filepath.Join(fs.dst, filepath.FromSlash(path.Clean("/"+name)))
|
||||
var mf *os.File
|
||||
i := strings.LastIndex(mp, "/")
|
||||
if i > 0 {
|
||||
dir := mp[:i]
|
||||
_, err = os.Stat(dir)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = os.MkdirAll(dir, 0o755)
|
||||
if err != nil {
|
||||
log.Error(err, "error creating cache dir", "dir", mp)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = os.Stat(mp)
|
||||
truncate := errors.Is(err, os.ErrNotExist)
|
||||
if !fs.isOpen(name) {
|
||||
mf, err = os.OpenFile(mp, os.O_RDWR|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
log.Error(err, "error opening cache file")
|
||||
return nil, err
|
||||
}
|
||||
if truncate {
|
||||
err = mf.Truncate(rfi.Size())
|
||||
if err != nil {
|
||||
log.Error(err, "error truncating cache file")
|
||||
return nil, err
|
||||
}
|
||||
err = mf.Sync()
|
||||
if err != nil {
|
||||
log.Error(err, "error syncing cache file")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return &File{
|
||||
log: log,
|
||||
f: rf,
|
||||
md: fs.metadata(name, rfi.Size(), mf),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *FS) isOpen(name string) bool {
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
md, ok := fs.mm[name]
|
||||
return ok && md.f != nil
|
||||
}
|
||||
|
||||
func (fs *FS) metadata(name string, size int64, f *os.File) (md *Metadata) {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
if md, ok := fs.mm[name]; ok {
|
||||
if md.f == nil {
|
||||
md.f = f
|
||||
}
|
||||
return md
|
||||
}
|
||||
md = &Metadata{Size: size, f: f}
|
||||
fs.mm[name] = md
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *FS) flusher(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(time.Minute * 5):
|
||||
}
|
||||
fs.flushMetadata()
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) flushMetadata() {
|
||||
log := fs.log
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
_, err := fs.mdf.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
log.Error(err, "failure seeking metadata file")
|
||||
return
|
||||
}
|
||||
err = fs.mdf.Truncate(0)
|
||||
if err != nil {
|
||||
log.Error(err, "failure truncating metadata file")
|
||||
return
|
||||
}
|
||||
err = fs.jenc.Encode(fs.mm)
|
||||
if err != nil {
|
||||
log.Error(err, "failure flushing metadata file")
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) Close() {
|
||||
fs.cancel()
|
||||
for _, md := range fs.mm {
|
||||
md.f.Sync()
|
||||
md.f.Close()
|
||||
}
|
||||
fs.flushMetadata()
|
||||
}
|
||||
|
||||
func stat(prefix, name string) (stdfs.FileInfo, error) {
|
||||
p := filepath.Join(prefix, filepath.FromSlash(path.Clean("/"+name)))
|
||||
return os.Stat(p)
|
||||
}
|
||||
52
pkg/fs/metadata.go
Normal file
52
pkg/fs/metadata.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
mu sync.RWMutex `json:"-"`
|
||||
f *os.File `json:"-"`
|
||||
Size int64 `json:"s"`
|
||||
Chunks Chunks `json:"c"`
|
||||
preload bool `json:"-"`
|
||||
}
|
||||
|
||||
func (md *Metadata) HasChunk(off int64, n int) bool {
|
||||
md.mu.RLock()
|
||||
defer md.mu.RUnlock()
|
||||
return md.Chunks.Exists(off, n, md.Size)
|
||||
}
|
||||
|
||||
func (md *Metadata) AddChunk(off int64, n int) {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
md.Chunks.Add(off, n)
|
||||
}
|
||||
|
||||
func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) {
|
||||
return md.f.ReadAt(p, pos)
|
||||
}
|
||||
|
||||
func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) {
|
||||
return md.f.WriteAt(data, pos)
|
||||
}
|
||||
|
||||
func (md *Metadata) Preload() bool {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
if md.preload {
|
||||
return true
|
||||
}
|
||||
md.preload = true
|
||||
return false
|
||||
}
|
||||
|
||||
func (md *Metadata) UnlockPreload() {
|
||||
md.mu.Lock()
|
||||
defer md.mu.Unlock()
|
||||
md.preload = false
|
||||
}
|
||||
76
pkg/srv/interceptor.go
Normal file
76
pkg/srv/interceptor.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package srv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type dirContents struct {
|
||||
AllPaths []string `xml:"a"`
|
||||
Dirs []string `xml:"-"`
|
||||
Files []string `xml:"-"`
|
||||
}
|
||||
|
||||
type statusInterceptor struct {
|
||||
w http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (s *statusInterceptor) Header() http.Header {
|
||||
return s.w.Header()
|
||||
}
|
||||
func (s *statusInterceptor) Write(p []byte) (int, error) {
|
||||
return s.w.Write(p)
|
||||
}
|
||||
func (s *statusInterceptor) WriteHeader(statusCode int) {
|
||||
s.w.WriteHeader(statusCode)
|
||||
s.status = statusCode
|
||||
}
|
||||
func (s *statusInterceptor) Status() int {
|
||||
if s.status == 0 {
|
||||
return http.StatusOK
|
||||
}
|
||||
return s.status
|
||||
}
|
||||
|
||||
type responseInterceptor struct {
|
||||
buf bytes.Buffer
|
||||
w http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (r *responseInterceptor) Header() http.Header {
|
||||
return r.w.Header()
|
||||
}
|
||||
func (r *responseInterceptor) Write(p []byte) (int, error) {
|
||||
return r.buf.Write(p)
|
||||
}
|
||||
func (r *responseInterceptor) WriteHeader(statusCode int) {
|
||||
r.status = statusCode
|
||||
}
|
||||
func (r *responseInterceptor) Status() int {
|
||||
if r.status == 0 {
|
||||
return http.StatusOK
|
||||
}
|
||||
return r.status
|
||||
}
|
||||
func (r *responseInterceptor) GetPaths() (dir dirContents, err error) {
|
||||
err = xml.Unmarshal(r.buf.Bytes(), &dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range dir.AllPaths {
|
||||
if p[len(p)-1] == '/' {
|
||||
dir.Dirs = append(dir.Dirs, p)
|
||||
} else {
|
||||
dir.Files = append(dir.Files, p)
|
||||
}
|
||||
}
|
||||
sort.Strings(dir.Dirs)
|
||||
sort.Strings(dir.Files)
|
||||
return
|
||||
}
|
||||
114
pkg/srv/srv.go
Normal file
114
pkg/srv/srv.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package srv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
stdfs "io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"cachefs/pkg/fs"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
type FileServer struct {
|
||||
log logr.Logger
|
||||
fs *fs.FS
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func NewFileServer(fs *fs.FS, log logr.Logger) http.Handler {
|
||||
return &FileServer{log, fs, http.FileServer(fs)}
|
||||
}
|
||||
|
||||
func skipLog(path string) bool {
|
||||
return strings.HasSuffix(path, "favicon.ico")
|
||||
}
|
||||
|
||||
func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s := &statusInterceptor{w: w}
|
||||
w = s
|
||||
defer func() {
|
||||
if skipLog(r.RequestURI) {
|
||||
return
|
||||
}
|
||||
fs.log.Info("access",
|
||||
"uri", r.RequestURI,
|
||||
"method", r.Method,
|
||||
"status", s.Status(),
|
||||
)
|
||||
}()
|
||||
upath := r.URL.Path
|
||||
if !strings.HasPrefix(upath, "/") {
|
||||
upath = "/" + upath
|
||||
r.URL.Path = upath
|
||||
}
|
||||
p := path.Clean(upath)
|
||||
d, err := fs.fs.Stat(p)
|
||||
if err != nil {
|
||||
msg, code := toHTTPError(err)
|
||||
http.Error(w, msg, code)
|
||||
return
|
||||
}
|
||||
if r.FormValue("v") == "t" {
|
||||
err = video.Execute(w, r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
if r.FormValue("p") == "t" {
|
||||
fs.fs.Preload(p)
|
||||
idx := strings.LastIndex(p, "/")
|
||||
if idx > 0 {
|
||||
p = p[:idx]
|
||||
} else {
|
||||
p = "/"
|
||||
}
|
||||
http.Redirect(w, r, p, http.StatusFound)
|
||||
return
|
||||
}
|
||||
if r.FormValue("n") == "t" {
|
||||
fs.fs.NoCache.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
fs.h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
i := &responseInterceptor{w: w}
|
||||
fs.h.ServeHTTP(i, r)
|
||||
paths, err := i.GetPaths()
|
||||
if err == io.EOF {
|
||||
w.WriteHeader(i.Status())
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(i.Status())
|
||||
err = index.Execute(w, paths)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func toHTTPError(err error) (string, int) {
|
||||
if errors.Is(err, stdfs.ErrNotExist) {
|
||||
return "404 page not found", http.StatusNotFound
|
||||
}
|
||||
if errors.Is(err, stdfs.ErrPermission) {
|
||||
return "403 Forbidden", http.StatusForbidden
|
||||
}
|
||||
// Default:
|
||||
return "500 Internal Server Error", http.StatusInternalServerError
|
||||
}
|
||||
48
pkg/srv/templates.go
Normal file
48
pkg/srv/templates.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
|
||||
package srv
|
||||
|
||||
import "html/template"
|
||||
|
||||
var (
|
||||
index = template.Must(template.New("index").Parse(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { text-align: center; padding: 0 150px 0 150px; font: 20px Helvetica, sans-serif; color: #333; }
|
||||
article { display: block; text-align: left; width: 650px; margin: 0 auto; }
|
||||
table { text-align: left; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<pre>
|
||||
[v]: show video
|
||||
[n]: skip file caching
|
||||
[p]: preload file
|
||||
</pre>
|
||||
<table>
|
||||
<tr><th>Path</th><th>Options</th></tr>
|
||||
<tr><td><a href="../">../</a></td><td></td></tr>
|
||||
{{range $s := .Dirs -}}
|
||||
<tr><td><a href="{{$s}}">{{$s}}</a></td><td></td></tr>
|
||||
{{end -}}
|
||||
{{range $s := .Files -}}
|
||||
<tr><td><a href="{{$s}}">{{$s}}</a></td>
|
||||
<td><a href="{{$s}}?v=t">[v]</a> <a href="{{$s}}?n=t">[n]</a> <a href="{{$s}}?p=t">[p]</a></td></tr>
|
||||
{{end -}}
|
||||
</table>
|
||||
</article>
|
||||
</body>
|
||||
</html>`))
|
||||
video = template.Must(template.New("video").Parse(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
</head>
|
||||
<body>
|
||||
<video id="video" style="width: 100%; height: 100%;" src="{{.}}" controls=""></video>
|
||||
<script>document.getElementById("video").volume = 0.5;</script>
|
||||
</body>
|
||||
</html>`))
|
||||
)
|
||||
7
run.sh
Executable file
7
run.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
./cachefs \
|
||||
-src /home/marius/Desktop \
|
||||
-dst /home/marius/mirrortmp \
|
||||
-data /home/marius/mirrortmp/md.json \
|
||||
-listen :8080 \
|
||||
-v 2
|
||||
63
scripts/install_openbsd.sh
Normal file
63
scripts/install_openbsd.sh
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
groupadd _cachefs
|
||||
useradd -d /var/empty -g _cachefs _cachefs
|
||||
|
||||
cat <<"EOF">/etc/rc.d/cachefs
|
||||
#!/bin/sh
|
||||
|
||||
daemon="/usr/local/sbin/cachefs"
|
||||
daemon_flags="-src /mnt/nfs -dst /mnt/cache -data /mnt/cache/md.json -listen :9090 -v 2"
|
||||
daemon_logger="daemon.info"
|
||||
daemon_user="_cachefs"
|
||||
|
||||
. /etc/rc.d/rc.subr
|
||||
|
||||
pexp="${daemon}.*"
|
||||
rc_bg=YES
|
||||
rc_reload=NO
|
||||
|
||||
rc_pre() {
|
||||
if ! mount | grep -q /mnt/cache; then
|
||||
bioctl -c C -p /root/sd1_key -l sd1a softraid0 && { sleep 1; fsck /dev/sd2a && mount /mnt/cache; }
|
||||
fi
|
||||
}
|
||||
|
||||
rc_cmd $1
|
||||
EOF
|
||||
chmod 555 /etc/rc.d/cachefs
|
||||
|
||||
mkdir -p /mnt/cache
|
||||
|
||||
fdisk -iy sd1
|
||||
|
||||
disklabel -E sd1 <<EOF
|
||||
a a
|
||||
|
||||
|
||||
RAID
|
||||
w
|
||||
q
|
||||
EOF
|
||||
dd if=/dev/urandom bs=1 count=256 2>/dev/null|sha256 >/root/sd1_key
|
||||
chmod 600 /root/sd1_key
|
||||
bioctl -c C -p /root/sd1_key -l sd1a softraid0
|
||||
|
||||
fdisk -iy sd2
|
||||
disklabel -E sd2 <<EOF
|
||||
a a
|
||||
|
||||
|
||||
|
||||
w
|
||||
q
|
||||
EOF
|
||||
newfs sd2a
|
||||
|
||||
echo "/dev/sd2a /mnt/cache ffs rw,nodev,nosuid,softdep,noauto 1 0" >>/etc/fstab
|
||||
echo "sync; umount /mnt/cache && bioctl -d sd2" >>/etc/rc.shutdown
|
||||
chmod 700 /etc/rc.shutdown
|
||||
|
||||
mount /mnt/cache
|
||||
chown _cachefs:_cachefs /mnt/cache
|
||||
|
||||
rcctl enable cachefs
|
||||
rcctl start cachefs
|
||||
29
vendor/github.com/go-logr/logr/.golangci.yaml
generated
vendored
Normal file
29
vendor/github.com/go-logr/logr/.golangci.yaml
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
run:
|
||||
timeout: 1m
|
||||
tests: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- deadcode
|
||||
- errcheck
|
||||
- forcetypeassert
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- revive
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 10
|
||||
6
vendor/github.com/go-logr/logr/CHANGELOG.md
generated
vendored
Normal file
6
vendor/github.com/go-logr/logr/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# CHANGELOG
|
||||
|
||||
## v1.0.0-rc1
|
||||
|
||||
This is the first logged release. Major changes (including breaking changes)
|
||||
have occurred since earlier tags.
|
||||
17
vendor/github.com/go-logr/logr/CONTRIBUTING.md
generated
vendored
Normal file
17
vendor/github.com/go-logr/logr/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Contributing
|
||||
|
||||
Logr is open to pull-requests, provided they fit within the intended scope of
|
||||
the project. Specifically, this library aims to be VERY small and minimalist,
|
||||
with no external dependencies.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This project intends to follow [semantic versioning](http://semver.org) and
|
||||
is very strict about compatibility. Any proposed changes MUST follow those
|
||||
rules.
|
||||
|
||||
## Performance
|
||||
|
||||
As a logging library, logr must be as light-weight as possible. Any proposed
|
||||
code change must include results of running the [benchmark](./benchmark)
|
||||
before and after the change.
|
||||
201
vendor/github.com/go-logr/logr/LICENSE
generated
vendored
Normal file
201
vendor/github.com/go-logr/logr/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
278
vendor/github.com/go-logr/logr/README.md
generated
vendored
Normal file
278
vendor/github.com/go-logr/logr/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# A minimal logging API for Go
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-logr/logr)
|
||||
|
||||
logr offers an(other) opinion on how Go programs and libraries can do logging
|
||||
without becoming coupled to a particular logging implementation. This is not
|
||||
an implementation of logging - it is an API. In fact it is two APIs with two
|
||||
different sets of users.
|
||||
|
||||
The `Logger` type is intended for application and library authors. It provides
|
||||
a relatively small API which can be used everywhere you want to emit logs. It
|
||||
defers the actual act of writing logs (to files, to stdout, or whatever) to the
|
||||
`LogSink` interface.
|
||||
|
||||
The `LogSink` interface is intended for logging library implementers. It is a
|
||||
pure interface which can be implemented by logging frameworks to provide the actual logging
|
||||
functionality.
|
||||
|
||||
This decoupling allows application and library developers to write code in
|
||||
terms of `logr.Logger` (which has very low dependency fan-out) while the
|
||||
implementation of logging is managed "up stack" (e.g. in or near `main()`.)
|
||||
Application developers can then switch out implementations as necessary.
|
||||
|
||||
Many people assert that libraries should not be logging, and as such efforts
|
||||
like this are pointless. Those people are welcome to convince the authors of
|
||||
the tens-of-thousands of libraries that *DO* write logs that they are all
|
||||
wrong. In the meantime, logr takes a more practical approach.
|
||||
|
||||
## Typical usage
|
||||
|
||||
Somewhere, early in an application's life, it will make a decision about which
|
||||
logging library (implementation) it actually wants to use. Something like:
|
||||
|
||||
```
|
||||
func main() {
|
||||
// ... other setup code ...
|
||||
|
||||
// Create the "root" logger. We have chosen the "logimpl" implementation,
|
||||
// which takes some initial parameters and returns a logr.Logger.
|
||||
logger := logimpl.New(param1, param2)
|
||||
|
||||
// ... other setup code ...
|
||||
```
|
||||
|
||||
Most apps will call into other libraries, create structures to govern the flow,
|
||||
etc. The `logr.Logger` object can be passed to these other libraries, stored
|
||||
in structs, or even used as a package-global variable, if needed. For example:
|
||||
|
||||
```
|
||||
app := createTheAppObject(logger)
|
||||
app.Run()
|
||||
```
|
||||
|
||||
Outside of this early setup, no other packages need to know about the choice of
|
||||
implementation. They write logs in terms of the `logr.Logger` that they
|
||||
received:
|
||||
|
||||
```
|
||||
type appObject struct {
|
||||
// ... other fields ...
|
||||
logger logr.Logger
|
||||
// ... other fields ...
|
||||
}
|
||||
|
||||
func (app *appObject) Run() {
|
||||
app.logger.Info("starting up", "timestamp", time.Now())
|
||||
|
||||
// ... app code ...
|
||||
```
|
||||
|
||||
## Background
|
||||
|
||||
If the Go standard library had defined an interface for logging, this project
|
||||
probably would not be needed. Alas, here we are.
|
||||
|
||||
### Inspiration
|
||||
|
||||
Before you consider this package, please read [this blog post by the
|
||||
inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what
|
||||
he has to say, and it largely aligns with our own experiences.
|
||||
|
||||
### Differences from Dave's ideas
|
||||
|
||||
The main differences are:
|
||||
|
||||
1. Dave basically proposes doing away with the notion of a logging API in favor
|
||||
of `fmt.Printf()`. We disagree, especially when you consider things like output
|
||||
locations, timestamps, file and line decorations, and structured logging. This
|
||||
package restricts the logging API to just 2 types of logs: info and error.
|
||||
|
||||
Info logs are things you want to tell the user which are not errors. Error
|
||||
logs are, well, errors. If your code receives an `error` from a subordinate
|
||||
function call and is logging that `error` *and not returning it*, use error
|
||||
logs.
|
||||
|
||||
2. Verbosity-levels on info logs. This gives developers a chance to indicate
|
||||
arbitrary grades of importance for info logs, without assigning names with
|
||||
semantic meaning such as "warning", "trace", and "debug." Superficially this
|
||||
may feel very similar, but the primary difference is the lack of semantics.
|
||||
Because verbosity is a numerical value, it's safe to assume that an app running
|
||||
with higher verbosity means more (and less important) logs will be generated.
|
||||
|
||||
## Implementations (non-exhaustive)
|
||||
|
||||
There are implementations for the following logging libraries:
|
||||
|
||||
- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
|
||||
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
|
||||
- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
|
||||
- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
|
||||
- **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
|
||||
- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
|
||||
- **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
|
||||
- **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
|
||||
- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
|
||||
|
||||
## FAQ
|
||||
|
||||
### Conceptual
|
||||
|
||||
#### Why structured logging?
|
||||
|
||||
- **Structured logs are more easily queryable**: Since you've got
|
||||
key-value pairs, it's much easier to query your structured logs for
|
||||
particular values by filtering on the contents of a particular key --
|
||||
think searching request logs for error codes, Kubernetes reconcilers for
|
||||
the name and namespace of the reconciled object, etc.
|
||||
|
||||
- **Structured logging makes it easier to have cross-referenceable logs**:
|
||||
Similarly to searchability, if you maintain conventions around your
|
||||
keys, it becomes easy to gather all log lines related to a particular
|
||||
concept.
|
||||
|
||||
- **Structured logs allow better dimensions of filtering**: if you have
|
||||
structure to your logs, you've got more precise control over how much
|
||||
information is logged -- you might choose in a particular configuration
|
||||
to log certain keys but not others, only log lines where a certain key
|
||||
matches a certain value, etc., instead of just having v-levels and names
|
||||
to key off of.
|
||||
|
||||
- **Structured logs better represent structured data**: sometimes, the
|
||||
data that you want to log is inherently structured (think tuple-link
|
||||
objects.) Structured logs allow you to preserve that structure when
|
||||
outputting.
|
||||
|
||||
#### Why V-levels?
|
||||
|
||||
**V-levels give operators an easy way to control the chattiness of log
|
||||
operations**. V-levels provide a way for a given package to distinguish
|
||||
the relative importance or verbosity of a given log message. Then, if
|
||||
a particular logger or package is logging too many messages, the user
|
||||
of the package can simply change the v-levels for that library.
|
||||
|
||||
#### Why not named levels, like Info/Warning/Error?
|
||||
|
||||
Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
|
||||
from Dave's ideas](#differences-from-daves-ideas).
|
||||
|
||||
#### Why not allow format strings, too?
|
||||
|
||||
**Format strings negate many of the benefits of structured logs**:
|
||||
|
||||
- They're not easily searchable without resorting to fuzzy searching,
|
||||
regular expressions, etc.
|
||||
|
||||
- They don't store structured data well, since contents are flattened into
|
||||
a string.
|
||||
|
||||
- They're not cross-referenceable.
|
||||
|
||||
- They don't compress easily, since the message is not constant.
|
||||
|
||||
(Unless you turn positional parameters into key-value pairs with numerical
|
||||
keys, at which point you've gotten key-value logging with meaningless
|
||||
keys.)
|
||||
|
||||
### Practical
|
||||
|
||||
#### Why key-value pairs, and not a map?
|
||||
|
||||
Key-value pairs are *much* easier to optimize, especially around
|
||||
allocations. Zap (a structured logger that inspired logr's interface) has
|
||||
[performance measurements](https://github.com/uber-go/zap#performance)
|
||||
that show this quite nicely.
|
||||
|
||||
While the interface ends up being a little less obvious, you get
|
||||
potentially better performance, plus avoid making users type
|
||||
`map[string]string{}` every time they want to log.
|
||||
|
||||
#### What if my V-levels differ between libraries?
|
||||
|
||||
That's fine. Control your V-levels on a per-logger basis, and use the
|
||||
`WithName` method to pass different loggers to different libraries.
|
||||
|
||||
Generally, you should take care to ensure that you have relatively
|
||||
consistent V-levels within a given logger, however, as this makes deciding
|
||||
on what verbosity of logs to request easier.
|
||||
|
||||
#### But I really want to use a format string!
|
||||
|
||||
That's not actually a question. Assuming your question is "how do
|
||||
I convert my mental model of logging with format strings to logging with
|
||||
constant messages":
|
||||
|
||||
1. Figure out what the error actually is, as you'd write in a TL;DR style,
|
||||
and use that as a message.
|
||||
|
||||
2. For every place you'd write a format specifier, look to the word before
|
||||
it, and add that as a key value pair.
|
||||
|
||||
For instance, consider the following examples (all taken from spots in the
|
||||
Kubernetes codebase):
|
||||
|
||||
- `klog.V(4).Infof("Client is returning errors: code %v, error %v",
|
||||
responseCode, err)` becomes `logger.Error(err, "client returned an
|
||||
error", "code", responseCode)`
|
||||
|
||||
- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
|
||||
seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
|
||||
response when requesting url", "attempt", retries, "after
|
||||
seconds", seconds, "url", url)`
|
||||
|
||||
If you *really* must use a format string, use it in a key's value, and
|
||||
call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to
|
||||
reflect over type %T")` becomes `logger.Info("unable to reflect over
|
||||
type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
|
||||
this is necessary should be few and far between.
|
||||
|
||||
#### How do I choose my V-levels?
|
||||
|
||||
This is basically the only hard constraint: increase V-levels to denote
|
||||
more verbose or more debug-y logs.
|
||||
|
||||
Otherwise, you can start out with `0` as "you always want to see this",
|
||||
`1` as "common logging that you might *possibly* want to turn off", and
|
||||
`10` as "I would like to performance-test your log collection stack."
|
||||
|
||||
Then gradually choose levels in between as you need them, working your way
|
||||
down from 10 (for debug and trace style logs) and up from 1 (for chattier
|
||||
info-type logs.)
|
||||
|
||||
#### How do I choose my keys?
|
||||
|
||||
Keys are fairly flexible, and can hold more or less any string
|
||||
value. For best compatibility with implementations and consistency
|
||||
with existing code in other projects, there are a few conventions you
|
||||
should consider.
|
||||
|
||||
- Make your keys human-readable.
|
||||
- Constant keys are generally a good idea.
|
||||
- Be consistent across your codebase.
|
||||
- Keys should naturally match parts of the message string.
|
||||
- Use lower case for simple keys and
|
||||
[lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
|
||||
more complex ones. Kubernetes is one example of a project that has
|
||||
[adopted that
|
||||
convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
|
||||
|
||||
While key names are mostly unrestricted (and spaces are acceptable),
|
||||
it's generally a good idea to stick to printable ascii characters, or at
|
||||
least match the general character set of your log lines.
|
||||
|
||||
#### Why should keys be constant values?
|
||||
|
||||
The point of structured logging is to make later log processing easier. Your
|
||||
keys are, effectively, the schema of each log message. If you use different
|
||||
keys across instances of the same log line, you will make your structured logs
|
||||
much harder to use. `Sprintf()` is for values, not for keys!
|
||||
|
||||
#### Why is this not a pure interface?
|
||||
|
||||
The Logger type is implemented as a struct in order to allow the Go compiler to
|
||||
optimize things like high-V `Info` logs that are not triggered. Not all of
|
||||
these implementations are implemented yet, but this structure was suggested as
|
||||
a way to ensure they *can* be implemented. All of the real work is behind the
|
||||
`LogSink` interface.
|
||||
|
||||
[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
||||
54
vendor/github.com/go-logr/logr/discard.go
generated
vendored
Normal file
54
vendor/github.com/go-logr/logr/discard.go
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2020 The logr Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
// Discard returns a Logger that discards all messages logged to it. It can be
|
||||
// used whenever the caller is not interested in the logs. Logger instances
|
||||
// produced by this function always compare as equal.
|
||||
func Discard() Logger {
|
||||
return Logger{
|
||||
level: 0,
|
||||
sink: discardLogSink{},
|
||||
}
|
||||
}
|
||||
|
||||
// discardLogSink is a LogSink that discards all messages.
|
||||
type discardLogSink struct{}
|
||||
|
||||
// Verify that it actually implements the interface
|
||||
var _ LogSink = discardLogSink{}
|
||||
|
||||
func (l discardLogSink) Init(RuntimeInfo) {
|
||||
}
|
||||
|
||||
func (l discardLogSink) Enabled(int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (l discardLogSink) Info(int, string, ...interface{}) {
|
||||
}
|
||||
|
||||
func (l discardLogSink) Error(error, string, ...interface{}) {
|
||||
}
|
||||
|
||||
func (l discardLogSink) WithValues(...interface{}) LogSink {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l discardLogSink) WithName(string) LogSink {
|
||||
return l
|
||||
}
|
||||
501
vendor/github.com/go-logr/logr/logr.go
generated
vendored
Normal file
501
vendor/github.com/go-logr/logr/logr.go
generated
vendored
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// This design derives from Dave Cheney's blog:
|
||||
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
||||
|
||||
// Package logr defines a general-purpose logging API and abstract interfaces
|
||||
// to back that API. Packages in the Go ecosystem can depend on this package,
|
||||
// while callers can implement logging with whatever backend is appropriate.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// Logging is done using a Logger instance. Logger is a concrete type with
|
||||
// methods, which defers the actual logging to a LogSink interface. The main
|
||||
// methods of Logger are Info() and Error(). Arguments to Info() and Error()
|
||||
// are key/value pairs rather than printf-style formatted strings, emphasizing
|
||||
// "structured logging".
|
||||
//
|
||||
// With Go's standard log package, we might write:
|
||||
// log.Printf("setting target value %s", targetValue)
|
||||
//
|
||||
// With logr's structured logging, we'd write:
|
||||
// logger.Info("setting target", "value", targetValue)
|
||||
//
|
||||
// Errors are much the same. Instead of:
|
||||
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
|
||||
//
|
||||
// We'd write:
|
||||
// logger.Error(err, "failed to open the pod bay door", "user", user)
|
||||
//
|
||||
// Info() and Error() are very similar, but they are separate methods so that
|
||||
// LogSink implementations can choose to do things like attach additional
|
||||
// information (such as stack traces) on calls to Error(). Error() messages are
|
||||
// always logged, regardless of the current verbosity. If there is no error
|
||||
// instance available, passing nil is valid.
|
||||
//
|
||||
// Verbosity
|
||||
//
|
||||
// Often we want to log information only when the application in "verbose
|
||||
// mode". To write log lines that are more verbose, Logger has a V() method.
|
||||
// The higher the V-level of a log line, the less critical it is considered.
|
||||
// Log-lines with V-levels that are not enabled (as per the LogSink) will not
|
||||
// be written. Level V(0) is the default, and logger.V(0).Info() has the same
|
||||
// meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
|
||||
// Error messages do not have a verbosity level and are always logged.
|
||||
//
|
||||
// Where we might have written:
|
||||
// if flVerbose >= 2 {
|
||||
// log.Printf("an unusual thing happened")
|
||||
// }
|
||||
//
|
||||
// We can write:
|
||||
// logger.V(2).Info("an unusual thing happened")
|
||||
//
|
||||
// Logger Names
|
||||
//
|
||||
// Logger instances can have name strings so that all messages logged through
|
||||
// that instance have additional context. For example, you might want to add
|
||||
// a subsystem name:
|
||||
//
|
||||
// logger.WithName("compactor").Info("started", "time", time.Now())
|
||||
//
|
||||
// The WithName() method returns a new Logger, which can be passed to
|
||||
// constructors or other functions for further use. Repeated use of WithName()
|
||||
// will accumulate name "segments". These name segments will be joined in some
|
||||
// way by the LogSink implementation. It is strongly recommended that name
|
||||
// segments contain simple identifiers (letters, digits, and hyphen), and do
|
||||
// not contain characters that could muddle the log output or confuse the
|
||||
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
|
||||
// quotes, etc).
|
||||
//
|
||||
// Saved Values
|
||||
//
|
||||
// Logger instances can store any number of key/value pairs, which will be
|
||||
// logged alongside all messages logged through that instance. For example,
|
||||
// you might want to create a Logger instance per managed object:
|
||||
//
|
||||
// With the standard log package, we might write:
|
||||
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
||||
// targetValue, object.Namespace, object.Name)
|
||||
//
|
||||
// With logr we'd write:
|
||||
// // Elsewhere: set up the logger to log the object name.
|
||||
// obj.logger = mainLogger.WithValues(
|
||||
// "name", obj.name, "namespace", obj.namespace)
|
||||
//
|
||||
// // later on...
|
||||
// obj.logger.Info("setting foo", "value", targetValue)
|
||||
//
|
||||
// Best Practices
|
||||
//
|
||||
// Logger has very few hard rules, with the goal that LogSink implementations
|
||||
// might have a lot of freedom to differentiate. There are, however, some
|
||||
// things to consider.
|
||||
//
|
||||
// The log message consists of a constant message attached to the log line.
|
||||
// This should generally be a simple description of what's occurring, and should
|
||||
// never be a format string. Variable information can then be attached using
|
||||
// named values.
|
||||
//
|
||||
// Keys are arbitrary strings, but should generally be constant values. Values
|
||||
// may be any Go value, but how the value is formatted is determined by the
|
||||
// LogSink implementation.
|
||||
//
|
||||
// Key Naming Conventions
|
||||
//
|
||||
// Keys are not strictly required to conform to any specification or regex, but
|
||||
// it is recommended that they:
|
||||
// * be human-readable and meaningful (not auto-generated or simple ordinals)
|
||||
// * be constant (not dependent on input data)
|
||||
// * contain only printable characters
|
||||
// * not contain whitespace or punctuation
|
||||
// * use lower case for simple keys and lowerCamelCase for more complex ones
|
||||
//
|
||||
// These guidelines help ensure that log data is processed properly regardless
|
||||
// of the log implementation. For example, log implementations will try to
|
||||
// output JSON data or will store data for later database (e.g. SQL) queries.
|
||||
//
|
||||
// While users are generally free to use key names of their choice, it's
|
||||
// generally best to avoid using the following keys, as they're frequently used
|
||||
// by implementations:
|
||||
// * "caller": the calling information (file/line) of a particular log line
|
||||
// * "error": the underlying error value in the `Error` method
|
||||
// * "level": the log level
|
||||
// * "logger": the name of the associated logger
|
||||
// * "msg": the log message
|
||||
// * "stacktrace": the stack trace associated with a particular log line or
|
||||
// error (often from the `Error` message)
|
||||
// * "ts": the timestamp for a log line
|
||||
//
|
||||
// Implementations are encouraged to make use of these keys to represent the
|
||||
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
||||
// would be necessary to represent at least message and timestamp as ordinary
|
||||
// named values).
|
||||
//
|
||||
// Break Glass
|
||||
//
|
||||
// Implementations may choose to give callers access to the underlying
|
||||
// logging implementation. The recommended pattern for this is:
|
||||
// // Underlier exposes access to the underlying logging implementation.
|
||||
// // Since callers only have a logr.Logger, they have to know which
|
||||
// // implementation is in use, so this interface is less of an abstraction
|
||||
// // and more of way to test type conversion.
|
||||
// type Underlier interface {
|
||||
// GetUnderlying() <underlying-type>
|
||||
// }
|
||||
//
|
||||
// Logger grants access to the sink to enable type assertions like this:
|
||||
// func DoSomethingWithImpl(log logr.Logger) {
|
||||
// if underlier, ok := log.GetSink()(impl.Underlier) {
|
||||
// implLogger := underlier.GetUnderlying()
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Custom `With*` functions can be implemented by copying the complete
|
||||
// Logger struct and replacing the sink in the copy:
|
||||
// // WithFooBar changes the foobar parameter in the log sink and returns a
|
||||
// // new logger with that modified sink. It does nothing for loggers where
|
||||
// // the sink doesn't support that parameter.
|
||||
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
|
||||
// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok {
|
||||
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
|
||||
// }
|
||||
// return log
|
||||
// }
|
||||
//
|
||||
// Don't use New to construct a new Logger with a LogSink retrieved from an
|
||||
// existing Logger. Source code attribution might not work correctly and
|
||||
// unexported fields in Logger get lost.
|
||||
//
|
||||
// Beware that the same LogSink instance may be shared by different logger
|
||||
// instances. Calling functions that modify the LogSink will affect all of
|
||||
// those.
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// New returns a new Logger instance. This is primarily used by libraries
|
||||
// implementing LogSink, rather than end users.
|
||||
func New(sink LogSink) Logger {
|
||||
logger := Logger{}
|
||||
logger.setSink(sink)
|
||||
sink.Init(runtimeInfo)
|
||||
return logger
|
||||
}
|
||||
|
||||
// setSink stores the sink and updates any related fields. It mutates the
|
||||
// logger and thus is only safe to use for loggers that are not currently being
|
||||
// used concurrently.
|
||||
func (l *Logger) setSink(sink LogSink) {
|
||||
l.sink = sink
|
||||
}
|
||||
|
||||
// GetSink returns the stored sink.
|
||||
func (l Logger) GetSink() LogSink {
|
||||
return l.sink
|
||||
}
|
||||
|
||||
// WithSink returns a copy of the logger with the new sink.
|
||||
func (l Logger) WithSink(sink LogSink) Logger {
|
||||
l.setSink(sink)
|
||||
return l
|
||||
}
|
||||
|
||||
// Logger is an interface to an abstract logging implementation. This is a
|
||||
// concrete type for performance reasons, but all the real work is passed on to
|
||||
// a LogSink. Implementations of LogSink should provide their own constructors
|
||||
// that return Logger, not LogSink.
|
||||
//
|
||||
// The underlying sink can be accessed through GetSink and be modified through
|
||||
// WithSink. This enables the implementation of custom extensions (see "Break
|
||||
// Glass" in the package documentation). Normally the sink should be used only
|
||||
// indirectly.
|
||||
type Logger struct {
|
||||
sink LogSink
|
||||
level int
|
||||
}
|
||||
|
||||
// Enabled tests whether this Logger is enabled. For example, commandline
|
||||
// flags might be used to set the logging verbosity and disable some info logs.
|
||||
func (l Logger) Enabled() bool {
|
||||
return l.sink.Enabled(l.level)
|
||||
}
|
||||
|
||||
// Info logs a non-error message with the given key/value pairs as context.
|
||||
//
|
||||
// The msg argument should be used to add some constant description to the log
|
||||
// line. The key/value pairs can then be used to add additional variable
|
||||
// information. The key/value pairs must alternate string keys and arbitrary
|
||||
// values.
|
||||
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
|
||||
if l.Enabled() {
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
withHelper.GetCallStackHelper()()
|
||||
}
|
||||
l.sink.Info(l.level, msg, keysAndValues...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs an error, with the given message and key/value pairs as context.
|
||||
// It functions similarly to Info, but may have unique behavior, and should be
|
||||
// preferred for logging errors (see the package documentations for more
|
||||
// information). The log message will always be emitted, regardless of
|
||||
// verbosity level.
|
||||
//
|
||||
// The msg argument should be used to add context to any underlying error,
|
||||
// while the err argument should be used to attach the actual error that
|
||||
// triggered this log line, if present. The err parameter is optional
|
||||
// and nil may be passed instead of an error instance.
|
||||
func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
withHelper.GetCallStackHelper()()
|
||||
}
|
||||
l.sink.Error(err, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// V returns a new Logger instance for a specific verbosity level, relative to
|
||||
// this Logger. In other words, V-levels are additive. A higher verbosity
|
||||
// level means a log message is less important. Negative V-levels are treated
|
||||
// as 0.
|
||||
func (l Logger) V(level int) Logger {
|
||||
if level < 0 {
|
||||
level = 0
|
||||
}
|
||||
l.level += level
|
||||
return l
|
||||
}
|
||||
|
||||
// WithValues returns a new Logger instance with additional key/value pairs.
|
||||
// See Info for documentation on how key/value pairs work.
|
||||
func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
|
||||
l.setSink(l.sink.WithValues(keysAndValues...))
|
||||
return l
|
||||
}
|
||||
|
||||
// WithName returns a new Logger instance with the specified name element added
|
||||
// to the Logger's name. Successive calls with WithName append additional
|
||||
// suffixes to the Logger's name. It's strongly recommended that name segments
|
||||
// contain only letters, digits, and hyphens (see the package documentation for
|
||||
// more information).
|
||||
func (l Logger) WithName(name string) Logger {
|
||||
l.setSink(l.sink.WithName(name))
|
||||
return l
|
||||
}
|
||||
|
||||
// WithCallDepth returns a Logger instance that offsets the call stack by the
|
||||
// specified number of frames when logging call site information, if possible.
|
||||
// This is useful for users who have helper functions between the "real" call
|
||||
// site and the actual calls to Logger methods. If depth is 0 the attribution
|
||||
// should be to the direct caller of this function. If depth is 1 the
|
||||
// attribution should skip 1 call frame, and so on. Successive calls to this
|
||||
// are additive.
|
||||
//
|
||||
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||
// it will be called and the result returned. If the implementation does not
|
||||
// support CallDepthLogSink, the original Logger will be returned.
|
||||
//
|
||||
// To skip one level, WithCallStackHelper() should be used instead of
|
||||
// WithCallDepth(1) because it works with implementions that support the
|
||||
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
|
||||
func (l Logger) WithCallDepth(depth int) Logger {
|
||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||
l.setSink(withCallDepth.WithCallDepth(depth))
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// WithCallStackHelper returns a new Logger instance that skips the direct
|
||||
// caller when logging call site information, if possible. This is useful for
|
||||
// users who have helper functions between the "real" call site and the actual
|
||||
// calls to Logger methods and want to support loggers which depend on marking
|
||||
// each individual helper function, like loggers based on testing.T.
|
||||
//
|
||||
// In addition to using that new logger instance, callers also must call the
|
||||
// returned function.
|
||||
//
|
||||
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||
// WithCallDepth(1) will be called to produce a new logger. If it supports a
|
||||
// WithCallStackHelper() method, that will be also called. If the
|
||||
// implementation does not support either of these, the original Logger will be
|
||||
// returned.
|
||||
func (l Logger) WithCallStackHelper() (func(), Logger) {
|
||||
var helper func()
|
||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||
l.setSink(withCallDepth.WithCallDepth(1))
|
||||
}
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
helper = withHelper.GetCallStackHelper()
|
||||
} else {
|
||||
helper = func() {}
|
||||
}
|
||||
return helper, l
|
||||
}
|
||||
|
||||
// contextKey is how we find Loggers in a context.Context.
|
||||
type contextKey struct{}
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
// notFoundError exists to carry an IsNotFound method.
|
||||
type notFoundError struct{}
|
||||
|
||||
func (notFoundError) Error() string {
|
||||
return "no logr.Logger was present"
|
||||
}
|
||||
|
||||
func (notFoundError) IsNotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
|
||||
// RuntimeInfo holds information that the logr "core" library knows which
|
||||
// LogSinks might want to know.
|
||||
type RuntimeInfo struct {
|
||||
// CallDepth is the number of call frames the logr library adds between the
|
||||
// end-user and the LogSink. LogSink implementations which choose to print
|
||||
// the original logging site (e.g. file & line) should climb this many
|
||||
// additional frames to find it.
|
||||
CallDepth int
|
||||
}
|
||||
|
||||
// runtimeInfo is a static global. It must not be changed at run time.
|
||||
var runtimeInfo = RuntimeInfo{
|
||||
CallDepth: 1,
|
||||
}
|
||||
|
||||
// LogSink represents a logging implementation. End-users will generally not
|
||||
// interact with this type.
|
||||
type LogSink interface {
|
||||
// Init receives optional information about the logr library for LogSink
|
||||
// implementations that need it.
|
||||
Init(info RuntimeInfo)
|
||||
|
||||
// Enabled tests whether this LogSink is enabled at the specified V-level.
|
||||
// For example, commandline flags might be used to set the logging
|
||||
// verbosity and disable some info logs.
|
||||
Enabled(level int) bool
|
||||
|
||||
// Info logs a non-error message with the given key/value pairs as context.
|
||||
// The level argument is provided for optional logging. This method will
|
||||
// only be called when Enabled(level) is true. See Logger.Info for more
|
||||
// details.
|
||||
Info(level int, msg string, keysAndValues ...interface{})
|
||||
|
||||
// Error logs an error, with the given message and key/value pairs as
|
||||
// context. See Logger.Error for more details.
|
||||
Error(err error, msg string, keysAndValues ...interface{})
|
||||
|
||||
// WithValues returns a new LogSink with additional key/value pairs. See
|
||||
// Logger.WithValues for more details.
|
||||
WithValues(keysAndValues ...interface{}) LogSink
|
||||
|
||||
// WithName returns a new LogSink with the specified name appended. See
|
||||
// Logger.WithName for more details.
|
||||
WithName(name string) LogSink
|
||||
}
|
||||
|
||||
// CallDepthLogSink represents a Logger that knows how to climb the call stack
|
||||
// to identify the original call site and can offset the depth by a specified
|
||||
// number of frames. This is useful for users who have helper functions
|
||||
// between the "real" call site and the actual calls to Logger methods.
|
||||
// Implementations that log information about the call site (such as file,
|
||||
// function, or line) would otherwise log information about the intermediate
|
||||
// helper functions.
|
||||
//
|
||||
// This is an optional interface and implementations are not required to
|
||||
// support it.
|
||||
type CallDepthLogSink interface {
|
||||
// WithCallDepth returns a LogSink that will offset the call
|
||||
// stack by the specified number of frames when logging call
|
||||
// site information.
|
||||
//
|
||||
// If depth is 0, the LogSink should skip exactly the number
|
||||
// of call frames defined in RuntimeInfo.CallDepth when Info
|
||||
// or Error are called, i.e. the attribution should be to the
|
||||
// direct caller of Logger.Info or Logger.Error.
|
||||
//
|
||||
// If depth is 1 the attribution should skip 1 call frame, and so on.
|
||||
// Successive calls to this are additive.
|
||||
WithCallDepth(depth int) LogSink
|
||||
}
|
||||
|
||||
// CallStackHelperLogSink represents a Logger that knows how to climb
|
||||
// the call stack to identify the original call site and can skip
|
||||
// intermediate helper functions if they mark themselves as
|
||||
// helper. Go's testing package uses that approach.
|
||||
//
|
||||
// This is useful for users who have helper functions between the
|
||||
// "real" call site and the actual calls to Logger methods.
|
||||
// Implementations that log information about the call site (such as
|
||||
// file, function, or line) would otherwise log information about the
|
||||
// intermediate helper functions.
|
||||
//
|
||||
// This is an optional interface and implementations are not required
|
||||
// to support it. Implementations that choose to support this must not
|
||||
// simply implement it as WithCallDepth(1), because
|
||||
// Logger.WithCallStackHelper will call both methods if they are
|
||||
// present. This should only be implemented for LogSinks that actually
|
||||
// need it, as with testing.T.
|
||||
type CallStackHelperLogSink interface {
|
||||
// GetCallStackHelper returns a function that must be called
|
||||
// to mark the direct caller as helper function when logging
|
||||
// call site information.
|
||||
GetCallStackHelper() func()
|
||||
}
|
||||
|
||||
// Marshaler is an optional interface that logged values may choose to
|
||||
// implement. Loggers with structured output, such as JSON, should
|
||||
// log the object return by the MarshalLog method instead of the
|
||||
// original value.
|
||||
type Marshaler interface {
|
||||
// MarshalLog can be used to:
|
||||
// - ensure that structs are not logged as strings when the original
|
||||
// value has a String method: return a different type without a
|
||||
// String method
|
||||
// - select which fields of a complex type should get logged:
|
||||
// return a simpler struct with fewer fields
|
||||
// - log unexported fields: return a different struct
|
||||
// with exported fields
|
||||
//
|
||||
// It may return any value of any type.
|
||||
MarshalLog() interface{}
|
||||
}
|
||||
17
vendor/k8s.io/klog/v2/.gitignore
generated
vendored
Normal file
17
vendor/k8s.io/klog/v2/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# OSX leaves these everywhere on SMB shares
|
||||
._*
|
||||
|
||||
# OSX trash
|
||||
.DS_Store
|
||||
|
||||
# Eclipse files
|
||||
.classpath
|
||||
.project
|
||||
.settings/**
|
||||
|
||||
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Vscode files
|
||||
.vscode
|
||||
22
vendor/k8s.io/klog/v2/CONTRIBUTING.md
generated
vendored
Normal file
22
vendor/k8s.io/klog/v2/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Contributing Guidelines
|
||||
|
||||
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
|
||||
|
||||
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||
|
||||
## Getting Started
|
||||
|
||||
We have full documentation on how to get started contributing here:
|
||||
|
||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
|
||||
|
||||
## Mentorship
|
||||
|
||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||
|
||||
## Contact Information
|
||||
|
||||
- [Slack](https://kubernetes.slack.com/messages/sig-architecture)
|
||||
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)
|
||||
191
vendor/k8s.io/klog/v2/LICENSE
generated
vendored
Normal file
191
vendor/k8s.io/klog/v2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
19
vendor/k8s.io/klog/v2/OWNERS
generated
vendored
Normal file
19
vendor/k8s.io/klog/v2/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
reviewers:
|
||||
- jayunit100
|
||||
- hoegaarden
|
||||
- andyxning
|
||||
- neolit123
|
||||
- pohly
|
||||
- yagonobre
|
||||
- vincepri
|
||||
- detiber
|
||||
approvers:
|
||||
- dims
|
||||
- thockin
|
||||
- justinsb
|
||||
- tallclair
|
||||
- piosz
|
||||
- brancz
|
||||
- lavalamp
|
||||
- serathius
|
||||
103
vendor/k8s.io/klog/v2/README.md
generated
vendored
Normal file
103
vendor/k8s.io/klog/v2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
klog
|
||||
====
|
||||
|
||||
klog is a permanent fork of https://github.com/golang/glog.
|
||||
|
||||
## Why was klog created?
|
||||
|
||||
The decision to create klog was one that wasn't made lightly, but it was necessary due to some
|
||||
drawbacks that are present in [glog](https://github.com/golang/glog). Ultimately, the fork was created due to glog not being under active development; this can be seen in the glog README:
|
||||
|
||||
> The code in this repo [...] is not itself under development
|
||||
|
||||
This makes us unable to solve many use cases without a fork. The factors that contributed to needing feature development are listed below:
|
||||
|
||||
* `glog` [presents a lot "gotchas"](https://github.com/kubernetes/kubernetes/issues/61006) and introduces challenges in containerized environments, all of which aren't well documented.
|
||||
* `glog` doesn't provide an easy way to test logs, which detracts from the stability of software using it
|
||||
* A long term goal is to implement a logging interface that allows us to add context, change output format, etc.
|
||||
|
||||
Historical context is available here:
|
||||
|
||||
* https://github.com/kubernetes/kubernetes/issues/61006
|
||||
* https://github.com/kubernetes/kubernetes/issues/70264
|
||||
* https://groups.google.com/forum/#!msg/kubernetes-sig-architecture/wCWiWf3Juzs/hXRVBH90CgAJ
|
||||
* https://groups.google.com/forum/#!msg/kubernetes-dev/7vnijOMhLS0/1oRiNtigBgAJ
|
||||
|
||||
----
|
||||
|
||||
How to use klog
|
||||
===============
|
||||
- Replace imports for `"github.com/golang/glog"` with `"k8s.io/klog/v2"`
|
||||
- Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags
|
||||
- You can now use `log_file` instead of `log_dir` for logging to a single file (See `examples/log_file/usage_log_file.go`)
|
||||
- If you want to redirect everything logged using klog somewhere else (say syslog!), you can use `klog.SetOutput()` method and supply a `io.Writer`. (See `examples/set_output/usage_set_output.go`)
|
||||
- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md))
|
||||
|
||||
**NOTE**: please use the newer go versions that support semantic import versioning in modules, ideally go 1.11.4 or greater.
|
||||
|
||||
### Coexisting with klog/v2
|
||||
|
||||
See [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2.
|
||||
|
||||
### Coexisting with glog
|
||||
This package can be used side by side with glog. [This example](examples/coexist_glog/coexist_glog.go) shows how to initialize and synchronize flags from the global `flag.CommandLine` FlagSet. In addition, the example makes use of stderr as combined output by setting `alsologtostderr` (or `logtostderr`) to `true`.
|
||||
|
||||
## Community, discussion, contribution, and support
|
||||
|
||||
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
|
||||
|
||||
You can reach the maintainers of this project at:
|
||||
|
||||
- [Slack](https://kubernetes.slack.com/messages/klog)
|
||||
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
|
||||
|
||||
----
|
||||
|
||||
glog
|
||||
====
|
||||
|
||||
Leveled execution logs for Go.
|
||||
|
||||
This is an efficient pure Go implementation of leveled logs in the
|
||||
manner of the open source C++ package
|
||||
https://github.com/google/glog
|
||||
|
||||
By binding methods to booleans it is possible to use the log package
|
||||
without paying the expense of evaluating the arguments to the log.
|
||||
Through the -vmodule flag, the package also provides fine-grained
|
||||
control over logging at the file level.
|
||||
|
||||
The comment from glog.go introduces the ideas:
|
||||
|
||||
Package glog implements logging analogous to the Google-internal
|
||||
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
|
||||
Error, Fatal, plus formatting variants such as Infof. It
|
||||
also provides V-style logging controlled by the -v and
|
||||
-vmodule=file=2 flags.
|
||||
|
||||
Basic examples:
|
||||
|
||||
glog.Info("Prepare to repel boarders")
|
||||
|
||||
glog.Fatalf("Initialization failed: %s", err)
|
||||
|
||||
See the documentation for the V function for an explanation
|
||||
of these examples:
|
||||
|
||||
if glog.V(2) {
|
||||
glog.Info("Starting transaction...")
|
||||
}
|
||||
|
||||
glog.V(2).Infoln("Processed", nItems, "elements")
|
||||
|
||||
|
||||
The repository contains an open source version of the log package
|
||||
used inside Google. The master copy of the source lives inside
|
||||
Google, not here. The code in this repo is for export only and is not itself
|
||||
under development. Feature requests will be ignored.
|
||||
|
||||
Send bug reports to golang-nuts@googlegroups.com.
|
||||
9
vendor/k8s.io/klog/v2/RELEASE.md
generated
vendored
Normal file
9
vendor/k8s.io/klog/v2/RELEASE.md
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Release Process
|
||||
|
||||
The `klog` is released on an as-needed basis. The process is as follows:
|
||||
|
||||
1. An issue is proposing a new release with a changelog since the last release
|
||||
1. All [OWNERS](OWNERS) must LGTM this release
|
||||
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
|
||||
1. The release issue is closed
|
||||
1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
|
||||
22
vendor/k8s.io/klog/v2/SECURITY.md
generated
vendored
Normal file
22
vendor/k8s.io/klog/v2/SECURITY.md
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Security Policy
|
||||
|
||||
## Security Announcements
|
||||
|
||||
Join the [kubernetes-security-announce] group for security and vulnerability announcements.
|
||||
|
||||
You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Instructions for reporting a vulnerability can be found on the
|
||||
[Kubernetes Security and Disclosure Information] page.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Information about supported Kubernetes versions can be found on the
|
||||
[Kubernetes version and version skew support policy] page on the Kubernetes website.
|
||||
|
||||
[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
|
||||
[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
|
||||
[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
|
||||
[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability
|
||||
20
vendor/k8s.io/klog/v2/SECURITY_CONTACTS
generated
vendored
Normal file
20
vendor/k8s.io/klog/v2/SECURITY_CONTACTS
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Defined below are the security contacts for this repo.
|
||||
#
|
||||
# They are the contact point for the Product Security Committee to reach out
|
||||
# to for triaging and handling of incoming issues.
|
||||
#
|
||||
# The below names agree to abide by the
|
||||
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
|
||||
# and will be removed and replaced if they violate that agreement.
|
||||
#
|
||||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||
|
||||
dims
|
||||
thockin
|
||||
justinsb
|
||||
tallclair
|
||||
piosz
|
||||
brancz
|
||||
DirectXMan12
|
||||
lavalamp
|
||||
3
vendor/k8s.io/klog/v2/code-of-conduct.md
generated
vendored
Normal file
3
vendor/k8s.io/klog/v2/code-of-conduct.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Kubernetes Community Code of Conduct
|
||||
|
||||
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
|
||||
1759
vendor/k8s.io/klog/v2/klog.go
generated
vendored
Normal file
1759
vendor/k8s.io/klog/v2/klog.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
130
vendor/k8s.io/klog/v2/klog_file.go
generated
vendored
Normal file
130
vendor/k8s.io/klog/v2/klog_file.go
generated
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// File I/O for logs.
|
||||
|
||||
package klog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxSize is the maximum size of a log file in bytes.
|
||||
var MaxSize uint64 = 1024 * 1024 * 1800
|
||||
|
||||
// logDirs lists the candidate directories for new log files.
|
||||
var logDirs []string
|
||||
|
||||
func createLogDirs() {
|
||||
if logging.logDir != "" {
|
||||
logDirs = append(logDirs, logging.logDir)
|
||||
}
|
||||
logDirs = append(logDirs, os.TempDir())
|
||||
}
|
||||
|
||||
var (
|
||||
pid = os.Getpid()
|
||||
program = filepath.Base(os.Args[0])
|
||||
host = "unknownhost"
|
||||
userName = "unknownuser"
|
||||
userNameOnce sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
if h, err := os.Hostname(); err == nil {
|
||||
host = shortHostname(h)
|
||||
}
|
||||
}
|
||||
|
||||
// shortHostname returns its argument, truncating at the first period.
|
||||
// For instance, given "www.google.com" it returns "www".
|
||||
func shortHostname(hostname string) string {
|
||||
if i := strings.Index(hostname, "."); i >= 0 {
|
||||
return hostname[:i]
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
|
||||
// logName returns a new log file name containing tag, with start time t, and
|
||||
// the name for the symlink for tag.
|
||||
func logName(tag string, t time.Time) (name, link string) {
|
||||
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
|
||||
program,
|
||||
host,
|
||||
getUserName(),
|
||||
tag,
|
||||
t.Year(),
|
||||
t.Month(),
|
||||
t.Day(),
|
||||
t.Hour(),
|
||||
t.Minute(),
|
||||
t.Second(),
|
||||
pid)
|
||||
return name, program + "." + tag
|
||||
}
|
||||
|
||||
var onceLogDirs sync.Once
|
||||
|
||||
// create creates a new log file and returns the file and its filename, which
|
||||
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
|
||||
// successfully, create also attempts to update the symlink for that tag, ignoring
|
||||
// errors.
|
||||
// The startup argument indicates whether this is the initial startup of klog.
|
||||
// If startup is true, existing files are opened for appending instead of truncated.
|
||||
func create(tag string, t time.Time, startup bool) (f *os.File, filename string, err error) {
|
||||
if logging.logFile != "" {
|
||||
f, err := openOrCreate(logging.logFile, startup)
|
||||
if err == nil {
|
||||
return f, logging.logFile, nil
|
||||
}
|
||||
return nil, "", fmt.Errorf("log: unable to create log: %v", err)
|
||||
}
|
||||
onceLogDirs.Do(createLogDirs)
|
||||
if len(logDirs) == 0 {
|
||||
return nil, "", errors.New("log: no log dirs")
|
||||
}
|
||||
name, link := logName(tag, t)
|
||||
var lastErr error
|
||||
for _, dir := range logDirs {
|
||||
fname := filepath.Join(dir, name)
|
||||
f, err := openOrCreate(fname, startup)
|
||||
if err == nil {
|
||||
symlink := filepath.Join(dir, link)
|
||||
os.Remove(symlink) // ignore err
|
||||
os.Symlink(name, symlink) // ignore err
|
||||
return f, fname, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
|
||||
}
|
||||
|
||||
// The startup argument indicates whether this is the initial startup of klog.
|
||||
// If startup is true, existing files are opened for appending instead of truncated.
|
||||
func openOrCreate(name string, startup bool) (*os.File, error) {
|
||||
if startup {
|
||||
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
return f, err
|
||||
}
|
||||
f, err := os.Create(name)
|
||||
return f, err
|
||||
}
|
||||
19
vendor/k8s.io/klog/v2/klog_file_others.go
generated
vendored
Normal file
19
vendor/k8s.io/klog/v2/klog_file_others.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package klog
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func getUserName() string {
|
||||
userNameOnce.Do(func() {
|
||||
current, err := user.Current()
|
||||
if err == nil {
|
||||
userName = current.Username
|
||||
}
|
||||
})
|
||||
|
||||
return userName
|
||||
}
|
||||
34
vendor/k8s.io/klog/v2/klog_file_windows.go
generated
vendored
Normal file
34
vendor/k8s.io/klog/v2/klog_file_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package klog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getUserName() string {
|
||||
userNameOnce.Do(func() {
|
||||
// On Windows, the Go 'user' package requires netapi32.dll.
|
||||
// This affects Windows Nano Server:
|
||||
// https://github.com/golang/go/issues/21867
|
||||
// Fallback to using environment variables.
|
||||
u := os.Getenv("USERNAME")
|
||||
if len(u) == 0 {
|
||||
return
|
||||
}
|
||||
// Sanitize the USERNAME since it may contain filepath separators.
|
||||
u = strings.Replace(u, `\`, "_", -1)
|
||||
|
||||
// user.Current().Username normally produces something like 'USERDOMAIN\USERNAME'
|
||||
d := os.Getenv("USERDOMAIN")
|
||||
if len(d) != 0 {
|
||||
userName = d + "_" + u
|
||||
} else {
|
||||
userName = u
|
||||
}
|
||||
})
|
||||
|
||||
return userName
|
||||
}
|
||||
19
vendor/k8s.io/klog/v2/klogr/README.md
generated
vendored
Normal file
19
vendor/k8s.io/klog/v2/klogr/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Minimal Go logging using klog
|
||||
|
||||
This package implements the [logr interface](https://github.com/go-logr/logr)
|
||||
in terms of Kubernetes' [klog](https://github.com/kubernetes/klog). This
|
||||
provides a relatively minimalist API to logging in Go, backed by a well-proven
|
||||
implementation.
|
||||
|
||||
Because klogr was implemented before klog itself added supported for
|
||||
structured logging, the default in klogr is to serialize key/value
|
||||
pairs with JSON and log the result as text messages via klog. This
|
||||
does not work well when klog itself forwards output to a structured
|
||||
logger.
|
||||
|
||||
Therefore the recommended approach is to let klogr pass all log
|
||||
messages through to klog and deal with structured logging there. Just
|
||||
beware that the output of klog without a structured logger is meant to
|
||||
be human-readable, in contrast to the JSON-based traditional format.
|
||||
|
||||
This is a BETA grade implementation.
|
||||
227
vendor/k8s.io/klog/v2/klogr/klogr.go
generated
vendored
Normal file
227
vendor/k8s.io/klog/v2/klogr/klogr.go
generated
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Package klogr implements github.com/go-logr/logr.Logger in terms of
|
||||
// k8s.io/klog.
|
||||
package klogr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/klog/v2"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Option is a functional option that reconfigures the logger created with New.
|
||||
type Option func(*klogger)
|
||||
|
||||
// Format defines how log output is produced.
|
||||
type Format string
|
||||
|
||||
const (
|
||||
// FormatSerialize tells klogr to turn key/value pairs into text itself
|
||||
// before invoking klog.
|
||||
FormatSerialize Format = "Serialize"
|
||||
|
||||
// FormatKlog tells klogr to pass all text messages and key/value pairs
|
||||
// directly to klog. Klog itself then serializes in a human-readable
|
||||
// format and optionally passes on to a structure logging backend.
|
||||
FormatKlog Format = "Klog"
|
||||
)
|
||||
|
||||
// WithFormat selects the output format.
|
||||
func WithFormat(format Format) Option {
|
||||
return func(l *klogger) {
|
||||
l.format = format
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a logr.Logger which serializes output itself
|
||||
// and writes it via klog.
|
||||
func New() logr.Logger {
|
||||
return NewWithOptions(WithFormat(FormatSerialize))
|
||||
}
|
||||
|
||||
// NewWithOptions returns a logr.Logger which serializes as determined
|
||||
// by the WithFormat option and writes via klog. The default is
|
||||
// FormatKlog.
|
||||
func NewWithOptions(options ...Option) logr.Logger {
|
||||
l := klogger{
|
||||
level: 0,
|
||||
prefix: "",
|
||||
values: nil,
|
||||
format: FormatKlog,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&l)
|
||||
}
|
||||
return logr.New(&l)
|
||||
}
|
||||
|
||||
type klogger struct {
|
||||
level int
|
||||
callDepth int
|
||||
prefix string
|
||||
values []interface{}
|
||||
format Format
|
||||
}
|
||||
|
||||
func (l *klogger) Init(info logr.RuntimeInfo) {
|
||||
l.callDepth += info.CallDepth
|
||||
}
|
||||
|
||||
// trimDuplicates will deduplicate elements provided in multiple KV tuple
|
||||
// slices, whilst maintaining the distinction between where the items are
|
||||
// contained.
|
||||
func trimDuplicates(kvLists ...[]interface{}) [][]interface{} {
|
||||
// maintain a map of all seen keys
|
||||
seenKeys := map[interface{}]struct{}{}
|
||||
// build the same number of output slices as inputs
|
||||
outs := make([][]interface{}, len(kvLists))
|
||||
// iterate over the input slices backwards, as 'later' kv specifications
|
||||
// of the same key will take precedence over earlier ones
|
||||
for i := len(kvLists) - 1; i >= 0; i-- {
|
||||
// initialise this output slice
|
||||
outs[i] = []interface{}{}
|
||||
// obtain a reference to the kvList we are processing
|
||||
kvList := kvLists[i]
|
||||
|
||||
// start iterating at len(kvList) - 2 (i.e. the 2nd last item) for
|
||||
// slices that have an even number of elements.
|
||||
// We add (len(kvList) % 2) here to handle the case where there is an
|
||||
// odd number of elements in a kvList.
|
||||
// If there is an odd number, then the last element in the slice will
|
||||
// have the value 'null'.
|
||||
for i2 := len(kvList) - 2 + (len(kvList) % 2); i2 >= 0; i2 -= 2 {
|
||||
k := kvList[i2]
|
||||
// if we have already seen this key, do not include it again
|
||||
if _, ok := seenKeys[k]; ok {
|
||||
continue
|
||||
}
|
||||
// make a note that we've observed a new key
|
||||
seenKeys[k] = struct{}{}
|
||||
// attempt to obtain the value of the key
|
||||
var v interface{}
|
||||
// i2+1 should only ever be out of bounds if we handling the first
|
||||
// iteration over a slice with an odd number of elements
|
||||
if i2+1 < len(kvList) {
|
||||
v = kvList[i2+1]
|
||||
}
|
||||
// add this KV tuple to the *start* of the output list to maintain
|
||||
// the original order as we are iterating over the slice backwards
|
||||
outs[i] = append([]interface{}{k, v}, outs[i]...)
|
||||
}
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
func flatten(kvList ...interface{}) string {
|
||||
keys := make([]string, 0, len(kvList))
|
||||
vals := make(map[string]interface{}, len(kvList))
|
||||
for i := 0; i < len(kvList); i += 2 {
|
||||
k, ok := kvList[i].(string)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i])))
|
||||
}
|
||||
var v interface{}
|
||||
if i+1 < len(kvList) {
|
||||
v = kvList[i+1]
|
||||
}
|
||||
keys = append(keys, k)
|
||||
vals[k] = v
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf := bytes.Buffer{}
|
||||
for i, k := range keys {
|
||||
v := vals[k]
|
||||
if i > 0 {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
buf.WriteString(pretty(k))
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(pretty(v))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func pretty(value interface{}) string {
|
||||
if err, ok := value.(error); ok {
|
||||
if _, ok := value.(json.Marshaler); !ok {
|
||||
value = err.Error()
|
||||
}
|
||||
}
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.Encode(value)
|
||||
return strings.TrimSpace(string(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (l klogger) Info(level int, msg string, kvList ...interface{}) {
|
||||
switch l.format {
|
||||
case FormatSerialize:
|
||||
msgStr := flatten("msg", msg)
|
||||
trimmed := trimDuplicates(l.values, kvList)
|
||||
fixedStr := flatten(trimmed[0]...)
|
||||
userStr := flatten(trimmed[1]...)
|
||||
klog.InfoDepth(l.callDepth+1, l.prefix, " ", msgStr, " ", fixedStr, " ", userStr)
|
||||
case FormatKlog:
|
||||
trimmed := trimDuplicates(l.values, kvList)
|
||||
if l.prefix != "" {
|
||||
msg = l.prefix + ": " + msg
|
||||
}
|
||||
klog.InfoSDepth(l.callDepth+1, msg, append(trimmed[0], trimmed[1]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l klogger) Enabled(level int) bool {
|
||||
return klog.V(klog.Level(level)).Enabled()
|
||||
}
|
||||
|
||||
func (l klogger) Error(err error, msg string, kvList ...interface{}) {
|
||||
msgStr := flatten("msg", msg)
|
||||
var loggableErr interface{}
|
||||
if err != nil {
|
||||
loggableErr = err.Error()
|
||||
}
|
||||
switch l.format {
|
||||
case FormatSerialize:
|
||||
errStr := flatten("error", loggableErr)
|
||||
trimmed := trimDuplicates(l.values, kvList)
|
||||
fixedStr := flatten(trimmed[0]...)
|
||||
userStr := flatten(trimmed[1]...)
|
||||
klog.ErrorDepth(l.callDepth+1, l.prefix, " ", msgStr, " ", errStr, " ", fixedStr, " ", userStr)
|
||||
case FormatKlog:
|
||||
trimmed := trimDuplicates(l.values, kvList)
|
||||
if l.prefix != "" {
|
||||
msg = l.prefix + ": " + msg
|
||||
}
|
||||
klog.ErrorSDepth(l.callDepth+1, err, msg, append(trimmed[0], trimmed[1]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithName returns a new logr.Logger with the specified name appended. klogr
|
||||
// uses '/' characters to separate name elements. Callers should not pass '/'
|
||||
// in the provided name string, but this library does not actually enforce that.
|
||||
func (l klogger) WithName(name string) logr.LogSink {
|
||||
if len(l.prefix) > 0 {
|
||||
l.prefix = l.prefix + "/"
|
||||
}
|
||||
l.prefix += name
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l klogger) WithValues(kvList ...interface{}) logr.LogSink {
|
||||
// Three slice args forces a copy.
|
||||
n := len(l.values)
|
||||
l.values = append(l.values[:n:n], kvList...)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l klogger) WithCallDepth(depth int) logr.LogSink {
|
||||
l.callDepth += depth
|
||||
return &l
|
||||
}
|
||||
|
||||
var _ logr.LogSink = &klogger{}
|
||||
var _ logr.CallDepthLogSink = &klogger{}
|
||||
7
vendor/modules.txt
vendored
Normal file
7
vendor/modules.txt
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# github.com/go-logr/logr v1.2.2
|
||||
## explicit; go 1.16
|
||||
github.com/go-logr/logr
|
||||
# k8s.io/klog/v2 v2.40.1
|
||||
## explicit; go 1.13
|
||||
k8s.io/klog/v2
|
||||
k8s.io/klog/v2/klogr
|
||||
Loading…
Add table
Add a link
Reference in a new issue