Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • renovate/git.autistici.org-ai3-go-common-digest
  • renovate/github.com-miekg-dns-1.x
  • renovate/github.com-prometheus-client_golang-1.x
  • renovate/golang.org-x-crypto-0.x
  • renovate/golang.org-x-net-0.x
  • v2
  • v3
8 results

Target

Select target project
  • ai3/tools/acmeserver
  • godog/acmeserver
  • svp-bot/acmeserver
3 results
Select Git revision
  • lintian-fixes
  • master
  • renovate/github.com-miekg-dns-1.x
  • renovate/golang.org-x-crypto-digest
4 results
Show changes
Showing
with 610 additions and 131 deletions
...@@ -62,6 +62,10 @@ func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer ...@@ -62,6 +62,10 @@ func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer
} }
if next = b.NextBackOff(); next == Stop { if next = b.NextBackOff(); next == Stop {
if cerr := ctx.Err(); cerr != nil {
return cerr
}
return err return err
} }
......
language: go
go:
- "1.x"
- master
env:
- TAGS=""
- TAGS="-tags purego"
script: go test $TAGS -v ./...
# xxhash # xxhash
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash) [![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Build Status](https://travis-ci.org/cespare/xxhash.svg?branch=master)](https://travis-ci.org/cespare/xxhash) [![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit xxhash is a Go implementation of the 64-bit
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a [xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
...@@ -64,4 +64,6 @@ $ go test -benchtime 10s -bench '/xxhash,direct,bytes' ...@@ -64,4 +64,6 @@ $ go test -benchtime 10s -bench '/xxhash,direct,bytes'
- [InfluxDB](https://github.com/influxdata/influxdb) - [InfluxDB](https://github.com/influxdata/influxdb)
- [Prometheus](https://github.com/prometheus/prometheus) - [Prometheus](https://github.com/prometheus/prometheus)
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache) - [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
module github.com/cespare/xxhash/v2
go 1.11
...@@ -193,7 +193,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error { ...@@ -193,7 +193,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error {
b, d.v4 = consumeUint64(b) b, d.v4 = consumeUint64(b)
b, d.total = consumeUint64(b) b, d.total = consumeUint64(b)
copy(d.mem[:], b) copy(d.mem[:], b)
b = b[len(d.mem):]
d.n = int(d.total % uint64(len(d.mem))) d.n = int(d.total % uint64(len(d.mem)))
return nil return nil
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// Register allocation: // Register allocation:
// AX h // AX h
// CX pointer to advance through b // SI pointer to advance through b
// DX n // DX n
// BX loop end // BX loop end
// R8 v1, k1 // R8 v1, k1
...@@ -16,39 +16,39 @@ ...@@ -16,39 +16,39 @@
// R12 tmp // R12 tmp
// R13 prime1v // R13 prime1v
// R14 prime2v // R14 prime2v
// R15 prime4v // DI prime4v
// round reads from and advances the buffer pointer in CX. // round reads from and advances the buffer pointer in SI.
// It assumes that R13 has prime1v and R14 has prime2v. // It assumes that R13 has prime1v and R14 has prime2v.
#define round(r) \ #define round(r) \
MOVQ (CX), R12 \ MOVQ (SI), R12 \
ADDQ $8, CX \ ADDQ $8, SI \
IMULQ R14, R12 \ IMULQ R14, R12 \
ADDQ R12, r \ ADDQ R12, r \
ROLQ $31, r \ ROLQ $31, r \
IMULQ R13, r IMULQ R13, r
// mergeRound applies a merge round on the two registers acc and val. // mergeRound applies a merge round on the two registers acc and val.
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v. // It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
#define mergeRound(acc, val) \ #define mergeRound(acc, val) \
IMULQ R14, val \ IMULQ R14, val \
ROLQ $31, val \ ROLQ $31, val \
IMULQ R13, val \ IMULQ R13, val \
XORQ val, acc \ XORQ val, acc \
IMULQ R13, acc \ IMULQ R13, acc \
ADDQ R15, acc ADDQ DI, acc
// func Sum64(b []byte) uint64 // func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT, $0-32 TEXT ·Sum64(SB), NOSPLIT, $0-32
// Load fixed primes. // Load fixed primes.
MOVQ ·prime1v(SB), R13 MOVQ ·prime1v(SB), R13
MOVQ ·prime2v(SB), R14 MOVQ ·prime2v(SB), R14
MOVQ ·prime4v(SB), R15 MOVQ ·prime4v(SB), DI
// Load slice. // Load slice.
MOVQ b_base+0(FP), CX MOVQ b_base+0(FP), SI
MOVQ b_len+8(FP), DX MOVQ b_len+8(FP), DX
LEAQ (CX)(DX*1), BX LEAQ (SI)(DX*1), BX
// The first loop limit will be len(b)-32. // The first loop limit will be len(b)-32.
SUBQ $32, BX SUBQ $32, BX
...@@ -65,14 +65,14 @@ TEXT ·Sum64(SB), NOSPLIT, $0-32 ...@@ -65,14 +65,14 @@ TEXT ·Sum64(SB), NOSPLIT, $0-32
XORQ R11, R11 XORQ R11, R11
SUBQ R13, R11 SUBQ R13, R11
// Loop until CX > BX. // Loop until SI > BX.
blockLoop: blockLoop:
round(R8) round(R8)
round(R9) round(R9)
round(R10) round(R10)
round(R11) round(R11)
CMPQ CX, BX CMPQ SI, BX
JLE blockLoop JLE blockLoop
MOVQ R8, AX MOVQ R8, AX
...@@ -100,16 +100,16 @@ noBlocks: ...@@ -100,16 +100,16 @@ noBlocks:
afterBlocks: afterBlocks:
ADDQ DX, AX ADDQ DX, AX
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8. // Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
ADDQ $24, BX ADDQ $24, BX
CMPQ CX, BX CMPQ SI, BX
JG fourByte JG fourByte
wordLoop: wordLoop:
// Calculate k1. // Calculate k1.
MOVQ (CX), R8 MOVQ (SI), R8
ADDQ $8, CX ADDQ $8, SI
IMULQ R14, R8 IMULQ R14, R8
ROLQ $31, R8 ROLQ $31, R8
IMULQ R13, R8 IMULQ R13, R8
...@@ -117,18 +117,18 @@ wordLoop: ...@@ -117,18 +117,18 @@ wordLoop:
XORQ R8, AX XORQ R8, AX
ROLQ $27, AX ROLQ $27, AX
IMULQ R13, AX IMULQ R13, AX
ADDQ R15, AX ADDQ DI, AX
CMPQ CX, BX CMPQ SI, BX
JLE wordLoop JLE wordLoop
fourByte: fourByte:
ADDQ $4, BX ADDQ $4, BX
CMPQ CX, BX CMPQ SI, BX
JG singles JG singles
MOVL (CX), R8 MOVL (SI), R8
ADDQ $4, CX ADDQ $4, SI
IMULQ R13, R8 IMULQ R13, R8
XORQ R8, AX XORQ R8, AX
...@@ -138,19 +138,19 @@ fourByte: ...@@ -138,19 +138,19 @@ fourByte:
singles: singles:
ADDQ $4, BX ADDQ $4, BX
CMPQ CX, BX CMPQ SI, BX
JGE finalize JGE finalize
singlesLoop: singlesLoop:
MOVBQZX (CX), R12 MOVBQZX (SI), R12
ADDQ $1, CX ADDQ $1, SI
IMULQ ·prime5v(SB), R12 IMULQ ·prime5v(SB), R12
XORQ R12, AX XORQ R12, AX
ROLQ $11, AX ROLQ $11, AX
IMULQ R13, AX IMULQ R13, AX
CMPQ CX, BX CMPQ SI, BX
JL singlesLoop JL singlesLoop
finalize: finalize:
...@@ -179,9 +179,9 @@ TEXT ·writeBlocks(SB), NOSPLIT, $0-40 ...@@ -179,9 +179,9 @@ TEXT ·writeBlocks(SB), NOSPLIT, $0-40
MOVQ ·prime2v(SB), R14 MOVQ ·prime2v(SB), R14
// Load slice. // Load slice.
MOVQ b_base+8(FP), CX MOVQ b_base+8(FP), SI
MOVQ b_len+16(FP), DX MOVQ b_len+16(FP), DX
LEAQ (CX)(DX*1), BX LEAQ (SI)(DX*1), BX
SUBQ $32, BX SUBQ $32, BX
// Load vN from d. // Load vN from d.
...@@ -199,7 +199,7 @@ blockLoop: ...@@ -199,7 +199,7 @@ blockLoop:
round(R10) round(R10)
round(R11) round(R11)
CMPQ CX, BX CMPQ SI, BX
JLE blockLoop JLE blockLoop
// Copy vN back to d. // Copy vN back to d.
...@@ -208,8 +208,8 @@ blockLoop: ...@@ -208,8 +208,8 @@ blockLoop:
MOVQ R10, 16(AX) MOVQ R10, 16(AX)
MOVQ R11, 24(AX) MOVQ R11, 24(AX)
// The number of bytes written is CX minus the old base pointer. // The number of bytes written is SI minus the old base pointer.
SUBQ b_base+8(FP), CX SUBQ b_base+8(FP), SI
MOVQ CX, ret+32(FP) MOVQ SI, ret+32(FP)
RET RET
...@@ -6,41 +6,52 @@ ...@@ -6,41 +6,52 @@
package xxhash package xxhash
import ( import (
"reflect"
"unsafe" "unsafe"
) )
// Notes: // In the future it's possible that compiler optimizations will make these
// XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
// If that happens, even if we keep these functions they can be replaced with
// the trivial safe code.
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
// //
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ // var b []byte
// for some discussion about these unsafe conversions. // bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
// bh.Len = len(s)
// bh.Cap = len(s)
// //
// In the future it's possible that compiler optimizations will make these // Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
// unsafe operations unnecessary: https://golang.org/issue/2205. // weight to this sequence of expressions that any function that uses it will
// not be inlined. Instead, the functions below use a different unsafe
// conversion designed to minimize the inliner weight and allow both to be
// inlined. There is also a test (TestInlining) which verifies that these are
// inlined.
// //
// Both of these wrapper functions still incur function call overhead since they // See https://github.com/golang/go/issues/42739 for discussion.
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
// for strings to squeeze out a bit more speed. Mid-stack inlining should
// eventually fix this.
// Sum64String computes the 64-bit xxHash digest of s. // Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy. // It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 { func Sum64String(s string) uint64 {
var b []byte b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
return Sum64(b) return Sum64(b)
} }
// WriteString adds more data to d. It always returns len(s), nil. // WriteString adds more data to d. It always returns len(s), nil.
// It may be faster than Write([]byte(s)) by avoiding a copy. // It may be faster than Write([]byte(s)) by avoiding a copy.
func (d *Digest) WriteString(s string) (n int, err error) { func (d *Digest) WriteString(s string) (n int, err error) {
var b []byte d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) // d.Write always returns len(s), nil.
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data // Ignoring the return output and returning these fixed values buys a
bh.Len = len(s) // savings of 6 in the inliner's cost model.
bh.Cap = len(s) return len(s), nil
return d.Write(b) }
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
} }
...@@ -30,8 +30,8 @@ import ( ...@@ -30,8 +30,8 @@ import (
// It returns one of the following: // It returns one of the following:
// (0, nil) - watchdog isn't enabled or we aren't the watched PID. // (0, nil) - watchdog isn't enabled or we aren't the watched PID.
// (0, err) - an error happened (e.g. error converting time). // (0, err) - an error happened (e.g. error converting time).
// (time, nil) - watchdog is enabled and we can send ping. // (time, nil) - watchdog is enabled and we can send ping. time is delay
// time is delay before inactive service will be killed. // before inactive service will be killed.
func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) { func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
wusec := os.Getenv("WATCHDOG_USEC") wusec := os.Getenv("WATCHDOG_USEC")
wpid := os.Getenv("WATCHDOG_PID") wpid := os.Getenv("WATCHDOG_PID")
......
...@@ -65,7 +65,8 @@ being called, or called more than once, as well as concurrent calls to ...@@ -65,7 +65,8 @@ being called, or called more than once, as well as concurrent calls to
Unfortunately this package is not perfect either. It's possible that it is Unfortunately this package is not perfect either. It's possible that it is
still missing some interfaces provided by the go core (let me know if you find still missing some interfaces provided by the go core (let me know if you find
one), and it won't work for applications adding their own interfaces into the one), and it won't work for applications adding their own interfaces into the
mix. mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
`http.ResponseWriter` and type-assert the result to its other interfaces.
However, hopefully the explanation above has sufficiently scared you of rolling However, hopefully the explanation above has sufficiently scared you of rolling
your own solution to this problem. httpsnoop may still break your application, your own solution to this problem. httpsnoop may still break your application,
......
...@@ -3,7 +3,6 @@ package httpsnoop ...@@ -3,7 +3,6 @@ package httpsnoop
import ( import (
"io" "io"
"net/http" "net/http"
"sync"
"time" "time"
) )
...@@ -36,17 +35,23 @@ func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Me ...@@ -36,17 +35,23 @@ func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Me
// sugar on top of this func), but is a more usable interface if your // sugar on top of this func), but is a more usable interface if your
// application doesn't use the Go http.Handler interface. // application doesn't use the Go http.Handler interface.
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics { func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
m := Metrics{Code: http.StatusOK}
m.CaptureMetrics(w, fn)
return m
}
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
// but allows one to customize starting Metrics object.
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
var ( var (
start = time.Now() start = time.Now()
m = Metrics{Code: http.StatusOK}
headerWritten bool headerWritten bool
lock sync.Mutex
hooks = Hooks{ hooks = Hooks{
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc { WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
return func(code int) { return func(code int) {
next(code) next(code)
lock.Lock()
defer lock.Unlock()
if !headerWritten { if !headerWritten {
m.Code = code m.Code = code
headerWritten = true headerWritten = true
...@@ -57,8 +62,7 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri ...@@ -57,8 +62,7 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri
Write: func(next WriteFunc) WriteFunc { Write: func(next WriteFunc) WriteFunc {
return func(p []byte) (int, error) { return func(p []byte) (int, error) {
n, err := next(p) n, err := next(p)
lock.Lock()
defer lock.Unlock()
m.Written += int64(n) m.Written += int64(n)
headerWritten = true headerWritten = true
return n, err return n, err
...@@ -68,8 +72,7 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri ...@@ -68,8 +72,7 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri
ReadFrom: func(next ReadFromFunc) ReadFromFunc { ReadFrom: func(next ReadFromFunc) ReadFromFunc {
return func(src io.Reader) (int64, error) { return func(src io.Reader) (int64, error) {
n, err := next(src) n, err := next(src)
lock.Lock()
defer lock.Unlock()
headerWritten = true headerWritten = true
m.Written += n m.Written += n
return n, err return n, err
...@@ -79,6 +82,5 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri ...@@ -79,6 +82,5 @@ func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metri
) )
fn(Wrap(w, hooks)) fn(Wrap(w, hooks))
m.Duration = time.Since(start) m.Duration += time.Since(start)
return m
} }
module github.com/felixge/httpsnoop
go 1.13
...@@ -74,243 +74,275 @@ func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { ...@@ -74,243 +74,275 @@ func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
// combination 1/32 // combination 1/32
case !i0 && !i1 && !i2 && !i3 && !i4: case !i0 && !i1 && !i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
}{rw} }{rw, rw}
// combination 2/32 // combination 2/32
case !i0 && !i1 && !i2 && !i3 && i4: case !i0 && !i1 && !i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Pusher http.Pusher
}{rw, rw} }{rw, rw, rw}
// combination 3/32 // combination 3/32
case !i0 && !i1 && !i2 && i3 && !i4: case !i0 && !i1 && !i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
io.ReaderFrom io.ReaderFrom
}{rw, rw} }{rw, rw, rw}
// combination 4/32 // combination 4/32
case !i0 && !i1 && !i2 && i3 && i4: case !i0 && !i1 && !i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 5/32 // combination 5/32
case !i0 && !i1 && i2 && !i3 && !i4: case !i0 && !i1 && i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
}{rw, rw} }{rw, rw, rw}
// combination 6/32 // combination 6/32
case !i0 && !i1 && i2 && !i3 && i4: case !i0 && !i1 && i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
http.Pusher http.Pusher
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 7/32 // combination 7/32
case !i0 && !i1 && i2 && i3 && !i4: case !i0 && !i1 && i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 8/32 // combination 8/32
case !i0 && !i1 && i2 && i3 && i4: case !i0 && !i1 && i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 9/32 // combination 9/32
case !i0 && i1 && !i2 && !i3 && !i4: case !i0 && i1 && !i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
}{rw, rw} }{rw, rw, rw}
// combination 10/32 // combination 10/32
case !i0 && i1 && !i2 && !i3 && i4: case !i0 && i1 && !i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Pusher http.Pusher
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 11/32 // combination 11/32
case !i0 && i1 && !i2 && i3 && !i4: case !i0 && i1 && !i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 12/32 // combination 12/32
case !i0 && i1 && !i2 && i3 && i4: case !i0 && i1 && !i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 13/32 // combination 13/32
case !i0 && i1 && i2 && !i3 && !i4: case !i0 && i1 && i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 14/32 // combination 14/32
case !i0 && i1 && i2 && !i3 && i4: case !i0 && i1 && i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 15/32 // combination 15/32
case !i0 && i1 && i2 && i3 && !i4: case !i0 && i1 && i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 16/32 // combination 16/32
case !i0 && i1 && i2 && i3 && i4: case !i0 && i1 && i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
// combination 17/32 // combination 17/32
case i0 && !i1 && !i2 && !i3 && !i4: case i0 && !i1 && !i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
}{rw, rw} }{rw, rw, rw}
// combination 18/32 // combination 18/32
case i0 && !i1 && !i2 && !i3 && i4: case i0 && !i1 && !i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Pusher http.Pusher
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 19/32 // combination 19/32
case i0 && !i1 && !i2 && i3 && !i4: case i0 && !i1 && !i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 20/32 // combination 20/32
case i0 && !i1 && !i2 && i3 && i4: case i0 && !i1 && !i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 21/32 // combination 21/32
case i0 && !i1 && i2 && !i3 && !i4: case i0 && !i1 && i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 22/32 // combination 22/32
case i0 && !i1 && i2 && !i3 && i4: case i0 && !i1 && i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 23/32 // combination 23/32
case i0 && !i1 && i2 && i3 && !i4: case i0 && !i1 && i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 24/32 // combination 24/32
case i0 && !i1 && i2 && i3 && i4: case i0 && !i1 && i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
// combination 25/32 // combination 25/32
case i0 && i1 && !i2 && !i3 && !i4: case i0 && i1 && !i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 26/32 // combination 26/32
case i0 && i1 && !i2 && !i3 && i4: case i0 && i1 && !i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Pusher http.Pusher
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 27/32 // combination 27/32
case i0 && i1 && !i2 && i3 && !i4: case i0 && i1 && !i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 28/32 // combination 28/32
case i0 && i1 && !i2 && i3 && i4: case i0 && i1 && !i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
// combination 29/32 // combination 29/32
case i0 && i1 && i2 && !i3 && !i4: case i0 && i1 && i2 && !i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 30/32 // combination 30/32
case i0 && i1 && i2 && !i3 && i4: case i0 && i1 && i2 && !i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
http.Pusher http.Pusher
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
// combination 31/32 // combination 31/32
case i0 && i1 && i2 && i3 && !i4: case i0 && i1 && i2 && i3 && !i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
// combination 32/32 // combination 32/32
case i0 && i1 && i2 && i3 && i4: case i0 && i1 && i2 && i3 && i4:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
http.Pusher http.Pusher
}{rw, rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw, rw}
} }
panic("unreachable") panic("unreachable")
} }
...@@ -320,6 +352,10 @@ type rw struct { ...@@ -320,6 +352,10 @@ type rw struct {
h Hooks h Hooks
} }
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header { func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil { if w.h.Header != nil {
...@@ -383,3 +419,18 @@ func (w *rw) Push(target string, opts *http.PushOptions) error { ...@@ -383,3 +419,18 @@ func (w *rw) Push(target string, opts *http.PushOptions) error {
} }
return f(target, opts) return f(target, opts)
} }
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}
...@@ -68,115 +68,131 @@ func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { ...@@ -68,115 +68,131 @@ func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
// combination 1/16 // combination 1/16
case !i0 && !i1 && !i2 && !i3: case !i0 && !i1 && !i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
}{rw} }{rw, rw}
// combination 2/16 // combination 2/16
case !i0 && !i1 && !i2 && i3: case !i0 && !i1 && !i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
io.ReaderFrom io.ReaderFrom
}{rw, rw} }{rw, rw, rw}
// combination 3/16 // combination 3/16
case !i0 && !i1 && i2 && !i3: case !i0 && !i1 && i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
}{rw, rw} }{rw, rw, rw}
// combination 4/16 // combination 4/16
case !i0 && !i1 && i2 && i3: case !i0 && !i1 && i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 5/16 // combination 5/16
case !i0 && i1 && !i2 && !i3: case !i0 && i1 && !i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
}{rw, rw} }{rw, rw, rw}
// combination 6/16 // combination 6/16
case !i0 && i1 && !i2 && i3: case !i0 && i1 && !i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 7/16 // combination 7/16
case !i0 && i1 && i2 && !i3: case !i0 && i1 && i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 8/16 // combination 8/16
case !i0 && i1 && i2 && i3: case !i0 && i1 && i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 9/16 // combination 9/16
case i0 && !i1 && !i2 && !i3: case i0 && !i1 && !i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
}{rw, rw} }{rw, rw, rw}
// combination 10/16 // combination 10/16
case i0 && !i1 && !i2 && i3: case i0 && !i1 && !i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 11/16 // combination 11/16
case i0 && !i1 && i2 && !i3: case i0 && !i1 && i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 12/16 // combination 12/16
case i0 && !i1 && i2 && i3: case i0 && !i1 && i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 13/16 // combination 13/16
case i0 && i1 && !i2 && !i3: case i0 && i1 && !i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{rw, rw, rw} }{rw, rw, rw, rw}
// combination 14/16 // combination 14/16
case i0 && i1 && !i2 && i3: case i0 && i1 && !i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 15/16 // combination 15/16
case i0 && i1 && i2 && !i3: case i0 && i1 && i2 && !i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
}{rw, rw, rw, rw} }{rw, rw, rw, rw, rw}
// combination 16/16 // combination 16/16
case i0 && i1 && i2 && i3: case i0 && i1 && i2 && i3:
return struct { return struct {
Unwrapper
http.ResponseWriter http.ResponseWriter
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
http.Hijacker http.Hijacker
io.ReaderFrom io.ReaderFrom
}{rw, rw, rw, rw, rw} }{rw, rw, rw, rw, rw, rw}
} }
panic("unreachable") panic("unreachable")
} }
...@@ -186,6 +202,10 @@ type rw struct { ...@@ -186,6 +202,10 @@ type rw struct {
h Hooks h Hooks
} }
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header { func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil { if w.h.Header != nil {
...@@ -241,3 +261,18 @@ func (w *rw) ReadFrom(src io.Reader) (int64, error) { ...@@ -241,3 +261,18 @@ func (w *rw) ReadFrom(src io.Reader) (int64, error) {
} }
return f(src) return f(src)
} }
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}
run: run:
deadline: 5m timeout: 1m
tests: true
linters: linters:
disable-all: true disable-all: true
enable: enable:
- dupl - asciicheck
- goconst - deadcode
- gocyclo - errcheck
- forcetypeassert
- gocritic
- gofmt - gofmt
- golint - goimports
- gosimple
- govet - govet
- ineffassign - ineffassign
- interfacer
- lll
- misspell - misspell
- nakedret - revive
- staticcheck
- structcheck - structcheck
- unparam - typecheck
- unused
- varcheck - varcheck
linters-settings: issues:
dupl: exclude-use-default: false
threshold: 400 max-issues-per-linter: 0
lll: max-same-issues: 10
line-length: 170
gocyclo:
min-complexity: 15
golint:
min-confidence: 0.85
\ No newline at end of file
# CHANGELOG
## v1.0.0-rc1
This is the first logged release. Major changes (including breaking changes)
have occurred since earlier tags.
# 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.
# A minimal logging API for Go
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](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)
- **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr)
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
- **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting)
- **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)
- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
## 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
/*
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
}